본문으로 건너뛰기
5분 AI.
TRENDSHIFT DAILY #29 딥다이브 · 2026-10-23 분석

tambo 딥다이브
— AI의 선택을 내가 만든 React 화면으로 바꾸는 법

SDK

빠른 판단

무엇
등록된 React 컴포넌트를 AI가 선택하고 props를 스트리밍으로 채우는 생성형 UI SDK
이럴 때
기존 화면 부품을 대화형 앱에 연결해 요청에 맞는 카드·폼·화면을 표시하고 갱신할 때
입력
설명·스키마가 등록된 React 컴포넌트와 사용자 대화
결과
선택된 컴포넌트·스트리밍 props·대화 상태
도입 전 확인
기본 흐름은 새 소스 생성이 아니라 개발자가 등록한 컴포넌트 선택·갱신이다
사용자가 “지역별 매출을 보여 줘”라고 말하면, AI가 등록된 차트와 입력값을 골라 실제 React 화면으로 연결합니다. 이 글은 그 뒤의 스키마 변환, 부분 JSON, 이벤트 상태 관리, 실행 잠금을 소스에서 따라가는 초보자용 공부자료입니다. TypeScript·TSX 약 22.1만 줄의 모노레포에서 필요한 길만 골라 읽습니다. 저장소 tambo-ai/tambo · 스타 11,200 · 포크 570 · 선정: TrendShift Daily #29. 통계는 2026-10-23 GitHub 기준, 구현은 커밋 6e2c1bf 기준입니다.
목차
  1. 프로젝트 한줄 요약
  2. 왜 주목받는가
  3. 기술 스택 전체 지도
  4. 아키텍처 심화 분석
  5. 디렉토리 구조 해부
  6. 학습 포인트 (기술별)
  7. 하드웨어 / 시스템 요구사항
  8. 직접 해볼 수 있는 실습 과제
  9. 관련 기술 심화 학습 로드맵
  10. 핵심 키워드 사전
  11. 참고 링크

1프로젝트 한줄 요약

자연어 요청과 기존 React 컴포넌트 사이의 연결 장치

tambo는 개발자가 등록한 React 컴포넌트를 AI가 선택하고, 그 컴포넌트의 props를 스트리밍으로 채우도록 돕는 생성형 UI 도구 모음입니다. “AI가 UI를 만든다”는 표현만으로는 핵심을 놓치기 쉽습니다. 여기서 기본 흐름은 새 React 소스 파일을 생성하는 일이 아닙니다. 화면의 코드는 개발자가 준비하고, AI는 사용할 화면의 이름과 그 화면에 넣을 데이터를 결정합니다.

기초 용어
생성형 UI와 props
생성형 UI는 AI의 응답이 문장에 그치지 않고 차트·카드·입력 화면으로 표현되는 방식입니다. props는 React 컴포넌트에 전달하는 입력값입니다. 같은 SalesCard라도 제목과 금액을 다르게 전달하면 서로 다른 매출 카드가 됩니다.
한 문장 비유

개발자가 메뉴와 주방을 만들면, AI가 주문을 해석해 알맞은 요리를 고릅니다

컴포넌트는 주방이 이미 만들 줄 아는 요리입니다. 설명문은 메뉴판, 스키마는 “이 요리는 인원수와 맵기 정보를 받아야 한다”는 주문 양식입니다. AI는 메뉴에 있는 항목과 필요한 입력값을 고르고, React는 준비된 조리법대로 화면을 그립니다.

메뉴 선택이 잘되어도 주방이 잘못된 재료를 처리할 수 있으면 문제가 생깁니다. 따라서 개발자는 데이터 검증, 비어 있는 props, 권한이 필요한 동작을 함께 설계해야 합니다.

1-1. 한 번의 요청을 다섯 단계로 보기

  1. 개발자가 컴포넌트 함수, 이름, 설명, propsSchema를 등록합니다.
  2. SDK가 컴포넌트 메타데이터와 사용자 메시지를 API로 보냅니다.
  3. 백엔드가 모델에 사용할 UI 도구와 대화 이력을 전달합니다.
  4. 모델이 고른 컴포넌트와 props 변경분이 이벤트로 내려옵니다.
  5. 브라우저가 이벤트를 상태로 누적하고 등록된 React 컴포넌트를 렌더링합니다.

이 연결은 react-sdk/src/providers/tambo-registry-provider.tsx의 registerComponent(), packages/client/src/utils/registry-conversion.ts의 toAvailableComponent(), react-sdk/src/v1/components/v1-component-renderer.tsx의 ComponentRenderer에서 확인할 수 있습니다.

1-2. SDK와 서버는 서로 다른 부품이다

부품실제 위치담당하는 일
React SDKreact-sdk/컴포넌트 등록, 훅, React 상태, 렌더링
범용 클라이언트packages/client/이벤트 처리, 도구 실행 등 React 밖에서도 쓸 수 있는 로직
Cloud APIapps/api/사용자·대화·실행 관리, 모델 호출 연결, 스트리밍 응답
Cloud 대시보드apps/web/프로젝트와 설정을 관리하는 Next.js 앱

따라서 SDK만 읽으면 컴포넌트 사용법은 배울 수 있지만, 대화가 저장되거나 동시 실행이 막히는 원리는 보이지 않습니다. 이 글은 브라우저부터 데이터베이스까지 연결해 읽습니다.

먼저 확인할 운영 상태
README 기준: Tambo Cloud는 종료 예정입니다

이 커밋의 README.md와 SELF-HOSTING.md는 신규 가입 중단, 2026-10-31까지 호스팅 API 운영 후 중단, 2026-11-30 사용자 데이터 삭제를 공지합니다. README 안의 “5분 시작”이나 호스팅 선택지 설명은 이 공지와 함께 읽어야 합니다.

운영 일정의 출처는 해당 커밋의 README 공지입니다. 학습은 로컬 소스와 가짜 데이터로 진행하고, 서버를 운영하려면 자체 호스팅 구성을 검토합니다.

2왜 주목받는가

관심을 끄는 구현상의 이유와 비교할 때 주의할 점

2-1. 챗봇의 답변이 제품의 실제 화면으로 이어진다

일반적인 채팅 UI에서는 “이번 달 매출이 1,250만 원입니다”라는 답변이 끝입니다. tambo에서는 같은 요청이 매출 카드나 차트의 입력값으로 연결됩니다. 이미 만든 디자인 시스템을 활용할 수 있고, 사용자가 결과 화면에서 다음 행동을 할 수 있다는 점이 관심을 끌 만합니다.

TrendShift Daily #29는 2026-10-23 기준 순위입니다. 아래 설명은 구현에 근거한 주목할 만한 설계에 대한 해석입니다. 순위만으로 인기의 원인이나 실제 사용 기업 수를 판단할 수는 없습니다.

2-2. 연결해야 할 여러 문제를 한 저장소에서 공부할 수 있다

도구 호출을 UI에 연결하려면 컴포넌트 목록, 입력 양식, 생성 중인 JSON, 대화 저장, 실행 취소가 필요합니다. tambo는 이를 SDK와 서버로 나눠 구현합니다. 이 레포의 학습 가치는 모델 호출 한 줄보다, 모델의 불완전한 응답을 제품 상태로 바꾸는 연결부에 있습니다.

비교 관점직접 조립하는 경우tambo의 이점과 부담
문자열 채팅 UI응답 문자열을 메시지 목록에 표시하는 흐름부터 구현컴포넌트 선택과 props 갱신까지 연결. 그만큼 상태와 서버 구성이 복잡함
Vercel AI SDK 기반 조립모델·도구 스트림 위에 앱의 UI 매핑과 저장 정책을 설계등록 목록, UI 이벤트, 대화 실행 계층을 제공. 내부에서도 ai 패키지를 사용함
CopilotKit·assistant-uiREADME가 비교 대상으로 드는 에이전트·채팅 UI 도구들tambo는 자기 컴포넌트 등록과 props 스트리밍을 전면에 둠. 타 제품의 현재 지원 범위는 별도 확인 필요
앱 소스 생성 방식생성된 코드를 검토하고 빌드하는 과정이 중심이 레포의 기본 경로는 실행 중 등록 컴포넌트와 입력값을 고름. 허용 목록 설계가 핵심

이 표는 사용 방식과 개발자가 맡는 일을 비교한 것입니다. 타 제품에 관한 설명의 출처는 이 커밋의 README입니다. 특히 README의 경쟁 비교표에는 백엔드까지 MIT라고 적힌 행이 있으나 실제 Cloud 패키지는 Apache-2.0입니다. MCP 지원 유무나 특정 프레임워크 종속성에 관한 타 제품 평가는 그대로 옮기지 않았습니다.

2-3. 생성되는 화면과 계속 수정되는 화면을 구분한다

두 가지 화면의 수명

대화 결과로 생긴 카드, 앱에 이미 떠 있는 메모

생성형 컴포넌트: 답변에 매출 카드를 추가하는 식입니다. 메시지 안의 컴포넌트 블록을 ComponentRenderer가 읽어 화면을 그립니다.

상호작용 가능한 컴포넌트: 이미 있는 메모나 작업판을 AI가 갱신하도록 등록합니다. 실제 공개 함수는 withTamboInteractable이며 구현은 react-sdk/src/hoc/with-tambo-interactable.tsx에 있습니다.

README 예시에는 withInteractable이라는 이름이 남아 있습니다. 그러나 이 커밋의 공개 진입점인 react-sdk/src/v1/index.ts에서 내보내는 이름은 withTamboInteractable입니다. 예제를 붙여 넣기 전에 패키지 버전과 공개 진입점을 읽는 습관을 배울 수 있는 사례입니다.

3기술 스택 전체 지도

이름 나열 대신, 각 기술이 어디에서 무슨 일을 하는지 보기

다음 버전은 커밋 6e2c1bf의 의존성 파일 기준입니다. ^는 고정 버전이 아니라 허용 범위이고, package-lock.json은 구체적인 의존성 버전을 기록합니다. package.json의 선언 범위, 루트 overrides, 잠금 파일에 기록된 버전을 함께 봐야 실제 의존성 구성을 이해할 수 있습니다.

기초 용어
의존성 · 피어 의존성 · 잠금 파일
의존성은 패키지가 사용하는 외부 코드입니다. 피어 의존성은 “내 앱 쪽에서 이 라이브러리를 함께 제공해 달라”는 호환성 조건입니다. 잠금 파일은 설치할 구체적인 버전들을 기록합니다. 범위를 적은 선언문과 구체적인 설치 계획서를 구분하세요.

3-1. 프론트엔드와 React SDK

기술선언 범위와 잠금 버전역할·근거 파일
ReactSDK 피어 ^18.0.0 || ^19.0.0 / 루트 강제·잠금 19.2.6훅·Context·컴포넌트. react-sdk/package.json, 루트 package.json
Next.js웹 선언 ^16.2.12 / 잠금 루트 항목 16.3.0Cloud 대시보드. apps/web/package.json
TanStack Query^5.90.16API 조회·변경·캐시. SDK 내부 래퍼는 react-sdk/src/hooks/react-query-hooks.ts
Tailwind CSS웹 ^3.4.19웹 UI 스타일. 컴포넌트 라이브러리와 SDK 코어를 구분해서 읽기
tRPC웹 ^11.17.0대시보드 내부 API의 타입 연결. 외부 SDK용 REST API와 역할이 다름
ZodSDK 피어 ^3.25.76 || ^4입력 스키마 작성. 공개 API는 JSON Schema·Standard Schema 경로도 포함
MCP SDK^1.30.0도구·리소스 연결. React SDK와 범용 클라이언트 의존성에 존재
부분 JSONpartial-json ^0.1.7문자열이 덜 도착한 동안 props를 구성. SDK 선언 기준

패키지 버전도 구별해야 합니다. @tambo-ai/react는 1.4.0, @tambo-ai/client는 1.3.0, Cloud 웹은 0.136.0, API는 0.146.1입니다. 릴리스 이름 web-v0.136.0을 React SDK의 버전으로 읽으면 안 됩니다.

3-2. 백엔드·데이터 저장

기술선언 버전구체적 용도와 위치
NestJS@nestjs/core ^11.1.26컨트롤러·서비스·주입·가드. apps/api/package.json
Vercel AI SDKAPI ai ^6.0.185 / backend ^6.0.138AISdkClient가 streamText()·generateText() 사용
모델 어댑터@ai-sdk/openai ^3.0.48, @ai-sdk/anthropic ^3.0.64루트 및 packages/backend/package.json. 모델 제공자별 구현 연결
Drizzle ORMdrizzle-orm ^0.45.2관계형 테이블·타입·쿼리. packages/db/src/schema.ts
PostgreSQL 드라이버pg ^8.21.0DB 접속. packages/db/package.json
마이그레이션drizzle-kit ^0.31.10DB 구조를 바꾸는 도구. 선언은 packages/db/package.json
AG-UI클라이언트 @ag-ui/core ^0.0.57실행·텍스트·도구 이벤트의 공통 형식
JSON Patch클라이언트 fast-json-patch 3.1.1props 변경분 적용. packages/client/src/utils/json-patch.ts
관측 도구API @sentry/nestjs ^10.57.0, backend langfuse ^3.38.6오류·모델 실행 추적 관련 의존성. 선언만으로 모든 배포에서 활성화됐다고 볼 수 없음

3-3. 인프라·빌드·검증

도구확인값무엇을 배울까
Node.js·npm최소 Node >=22·npm >=11 / 지정값 22.22.0·11.7.0devEngines, .node-version, packageManager를 나눠 읽기
TypeScript루트 ^5.9.3 / 잠금 5.9.3패키지 간 타입 계약과 빌드 경계
Turborepo루트 ^2.9.16 / 잠금 2.10.8turbo.json의 선행 작업과 결과 캐시
테스트·형식SDK jest ^30.4.2 / 루트 prettier ^3.8.3컴포넌트·이벤트·서버 테스트를 각 패키지에 배치
컨테이너node:22-alpine, postgres:17, minio/minio:latestDockerfile 다단계 빌드와 Compose 서비스 연결. 이미지 태그는 불변 고정값이 아님
CIGitHub Actions의 lint·test·e2e·build.github/workflows/ci.yml에서 품질 검사 흐름 읽기

turbo.json의 build.dependsOn: ["^build"]는 의존 패키지를 먼저 빌드하라는 뜻입니다. API 개발 작업은 interruptible: true이며 core·backend·db의 소스 변경을 입력으로 지정합니다. React SDK는 CJS와 ESM 출력을 따로 만들고, API Dockerfile은 turbo prune으로 필요한 워크스페이스를 추립니다.

소스 읽기 요령

패키지 이름을 외우기보다 “React가 필요 없는 코드인가?”, “DB에 접근하는가?”, “브라우저 밖에 남겨야 하는가?”를 묻습니다. 이 세 질문으로 SDK·범용 클라이언트·코어·백엔드·DB 패키지의 경계를 상당 부분 설명할 수 있습니다.

4아키텍처 심화 분석

브라우저에서 모델·DB까지 이어지는 경로와 네 가지 설계 패턴

4-1. 먼저 전체 시스템 지도를 펼치기

[사용자: “지역별 매출을 보여 줘”] | v [내 React 앱 / react-sdk] TamboProvider +-- TamboRegistryProvider: 이름·스키마·실제 컴포넌트 +-- TamboStreamProvider: useReducer로 대화 상태 관리 +-- useTamboThreadInput / useTamboSendMessage | | 메시지 + availableComponents + tools v [외부 TypeScript SDK / HTTP 클라이언트] | v [apps/api / NestJS] V1Controller.createRun() +-- 가드: 프로젝트·사용자·대화 범위 확인 +-- V1Service.startRun(): DB 실행 잠금 +-- V1Service.executeRun() | v ThreadsService.advanceThread() ----> [PostgreSQL / packages/db] | threads / messages / runs v [packages/backend / TamboBackend] runDecisionLoop() / AISdkClient.complete() | v [외부 LLM 제공자] | | 모델 응답과 도구 입력 조각 v ComponentStreamTracker: 시작 / props 변경 / 완료 | | SSE: AG-UI + tambo 사용자 정의 이벤트 v [브라우저: streamReducer() -> ComponentRenderer] | +-- 등록된 React 컴포넌트 렌더링 +-- 클라이언트 도구 실행 -> 결과를 다음 run에 전송 [apps/web / Next.js] ----> 프로젝트·설정 관리 [MinIO / S3 호환 저장소] <---- 첨부 파일 기능을 위한 별도 저장 경로

위 그림은 일반 LLM 경로를 중심으로 단순화한 것입니다. V1Service.executeRun()은 ThreadsService.advanceThread()와 비동기 큐를 연결하고, TamboBackend.runDecisionLoop()는 설정된 클라이언트 종류에 따라 일반 결정 루프 또는 에이전트 경로를 선택합니다. MCP 서버도 연결 위치에 따라 브라우저 쪽과 서버 쪽 경로가 달라질 수 있습니다.

API와 React 사이에 있는 @tambo-ai/typescript-sdk는 이 레포의 워크스페이스가 아닌 외부 패키지입니다. 메시지 전송 훅과 범용 클라이언트가 이 패키지의 메서드를 호출합니다. API 동작을 읽을 때는 호출부와 외부 패키지 내부 구현을 구분해야 합니다.

4-2. 패턴 ① 등록 목록과 스키마로 실행 경계 만들기

registerComponent()는 validateAndPrepareComponent()로 이름과 스키마를 확인한 뒤 실제 React 함수와 직렬화된 props 정의를 목록에 저장합니다. 서버로 전송할 때 toAvailableComponent()는 이 중 이름·설명·JSON Schema만 추립니다.

// packages/client/src/utils/registry-conversion.ts 발췌
return {
  name: component.name,
  description: component.description,
  propsSchema: component.props,
};

함수 자체를 JSON으로 보내지 않는 것이 핵심입니다. 모델은 “이름이 SalesCard인 화면이 있고 이런 입력을 받는다”는 설명을 보고 선택합니다. 브라우저는 응답의 이름으로 로컬 목록에서 진짜 함수를 찾습니다.

ComponentRenderer는 getComponentFromRegistry()로 대상을 찾고 React.createElement(registeredComponent.component, validatedProps)로 요소를 만듭니다. 알 수 없는 이름 등으로 이 과정에서 예외가 나면 로그를 남기고 기본값 null 또는 지정된 대체 화면을 반환합니다.

비유

택배 주문서에 공장 설계도를 넣는 대신 상품 코드와 옵션만 적는 방식입니다. 목록은 상품 코드와 실제 제품을 연결하고, 스키마는 옵션 입력 양식을 설명합니다. 허용 목록 밖의 이름은 정상 제품으로 연결되지 않습니다.

검증을 과장하지 않기
스키마 선언과 모든 값의 강제 검증은 다릅니다

ComponentRenderer는 Standard Schema 검증 분기에서 실패를 경고하고 원래 props로 계속 그리며, 비동기 검증도 건너뜁니다. 일반 등록 경로의 props는 JSON Schema로 변환되어 저장되므로 그 분기의 isStandardSchema() 조건이 항상 성립하는 것도 아닙니다.

즉, props를 무조건 신뢰하거나 이 렌더러를 보안 검증 장치로 생각하면 안 됩니다. 특히 데이터 변경 권한은 실제 도구나 서버에서 검증해야 합니다. 컴포넌트 함수 내부에서 나중에 발생한 렌더링 오류까지 이 요소 생성용 try/catch가 전부 잡는다는 뜻도 아닙니다.

학습 질문: React 함수와 전송용 설명을 한 객체에서 출발시킨 뒤 왜 따로 분리할까요? 브라우저 전용 코드, 직렬화 가능한 데이터, 신뢰할 수 없는 모델 출력을 각각 구분해 보세요.

4-3. 패턴 ② 부분 입력을 변경 이벤트로 바꾸는 스트리밍 어댑터

packages/backend/src/services/decision-loop/decision-loop-service.ts의 runDecisionLoop()는 UI 도구를 분류하고, 시스템 메시지와 이력을 준비한 다음 llmClient.complete({ stream: true, ... })를 호출합니다. AISdkClient는 AI SDK의 streamText() 결과인 fullStream을 순회합니다.

UI 도구 이름에는 show_component_ 접두사가 쓰입니다. 예를 들어 모델의 도구 입력이 한 글자씩 도착하면 {"title":"서처럼 아직 완성되지 않은 JSON이 생깁니다. 여기서 매번 JSON.parse()만 호출하면 계속 실패하므로, 생성 중인 상태를 다룰 별도 경로가 필요합니다.

// 교육용 흐름 예시: 아직 닫히지 않은 입력도 도착할 수 있습니다.
도착 조각 1: {"title":"서
도착 조각 2: 울 매출","amount":
도착 조각 3: 1250}

시작: tambo.component.start
변경: tambo.component.props_delta
완료: tambo.component.end

packages/backend/src/util/component-streaming.ts의 ComponentStreamTracker.processJsonDelta()는 조각을 누적하고 partial-json으로 현재 해석 가능한 객체를 얻습니다. 이전 객체와 비교해 변경분을 JSON Patch로 내보냅니다. finalize()에서는 완성된 입력을 JSON.parse()로 읽고 객체인지 확인합니다.

부분 해석은 입력을 끝까지 검증했다는 뜻이 아닙니다. props별 완료 상태도 “새 속성이 나타났는가” 같은 스트리밍 규칙으로 추적합니다. 화면은 중간에 없는 필드와 바뀌는 문자열을 견뎌야 하며, 저장·결제처럼 확정 입력이 필요한 작업은 이 중간 상태에 바로 연결하면 안 됩니다.

이벤트담긴 의미브라우저에서 할 일
시작tambo.component.start
이 ID와 이름의 컴포넌트를 시작
빈 props 객체를 가진 블록 준비
속성 변경tambo.component.props_delta
기존 props에 적용할 변경 목록
같은 컴포넌트의 props 갱신
완료tambo.component.end
최종 props와 선택적 최종 state
최종 상태와 완료 표시 반영
// JSON Patch의 교육용 예시
기존 props: {"title":"서울","amount":1000}
변경 목록: [{"op":"replace","path":"/amount","value":1250}]
적용 결과: {"title":"서울","amount":1250}

createJsonPatchPath()는 키에 들어 있는 /와 ~를 JSON Pointer 규칙대로 이스케이프합니다. 예를 들어 속성 이름이 sales/kr라면 경로는 /sales~1kr입니다. 단순히 슬래시를 붙이는 코드와 표준을 지키는 코드의 차이를 배울 수 있습니다.

누적 크기 제한 상수는 10 * 1024 * 1024입니다. 소스 주석은 10MB라고 부르지만 실제 비교는 delta.length의 누적값이므로 엄밀한 UTF-8 바이트 수 제한과는 다릅니다. 설정값뿐 아니라 그 값을 어떤 단위로 비교하는지도 읽어야 합니다.

4-4. 패턴 ③ 이벤트와 리듀서로 화면 상태 재구성하기

packages/client/src/utils/event-accumulator.ts의 streamReducer(state, action)는 실행 시작·완료, 텍스트 조각, 도구 호출, 컴포넌트 변경 이벤트를 현재 대화 상태에 적용합니다. React 쪽 TamboStreamProvider는 이 함수를 useReducer()에 연결합니다.

스트리밍 UI는 문자열을 길게 만드는 작업이 아니라, 이벤트 순서에 맞춰 같은 화면의 상태를 계속 갱신하는 작업입니다.

기초 용어
리듀서와 낙관적 UI
리듀서는 “현재 상태 + 사건 → 다음 상태”를 계산하는 함수입니다. 낙관적 UI는 서버 확인을 기다리기 전에 사용자의 입력을 먼저 보여 주는 방식입니다. 빠르게 보이는 대신 임시 ID와 실제 ID를 나중에 맞추는 처리가 필요합니다.

새 대화는 PLACEHOLDER_THREAD_ID = "placeholder"로 시작합니다. 메시지를 바로 보여 준 뒤 RUN_STARTED에서 실제 thread ID를 받으면, 리듀서는 임시 메시지를 실제 대화로 옮기고 임시 대화를 비웁니다. 사용자가 이미 다른 대화로 이동했다면 현재 선택을 무조건 빼앗지 않도록 조건도 둡니다.

// 교육용 의사 코드: 실제 함수는 streamReducer()
state = reducer(state, 사용자_메시지_이벤트);
state = reducer(state, 실제_대화_ID를_담은_RUN_STARTED);
state = reducer(state, 컴포넌트_시작_이벤트);
state = reducer(state, props_변경_이벤트);
state = reducer(state, 컴포넌트_완료_이벤트);

props 변경에는 applyJsonPatch()를 쓰며, 내부의 applyPatch(target, operations, true, false)는 패치 검증을 켜고 원본 직접 수정을 끕니다. 같은 객체를 몰래 바꾸면 React의 변경 감지와 이전 상태 비교가 꼬일 수 있기 때문입니다.

범용 TamboClient에는 getState()와 subscribe()도 있습니다. 외부 상태 저장소 형태를 배우기 좋은 코드지만, 현재 React 스트림 Provider의 실제 구현은 useReducer입니다. 인터페이스가 useSyncExternalStore와 호환된다는 설명을 곧바로 “현재 React SDK가 그 훅을 쓴다”로 바꾸면 안 됩니다.

ComponentRenderer의 안내처럼 렌더링 목록의 key를 content.id로 안정적으로 유지하는 것도 중요합니다. props가 갱신될 때마다 다른 key를 주면 같은 카드가 새 카드로 교체되어 입력 상태를 잃을 수 있습니다.

4-5. 패턴 ④ 가드와 원자적 DB 갱신으로 실행 수명 관리하기

이벤트 처리가 정확해도 같은 대화에서 두 요청이 동시에 모델을 돌리면 상태가 충돌할 수 있습니다. V1Service.startRun()는 트랜잭션 안에서 실행 잠금을 획득하고, 도구 결과 저장·대기 도구 목록 정리·실행 생성·현재 run ID 설정을 묶습니다.

// packages/db/src/operations/runs.ts의 acquireRunLock() 핵심을 단순화
UPDATE threads
SET run_status = 'waiting'
WHERE id = :threadId AND run_status = 'idle'
RETURNING id;

실제 코드는 Drizzle 쿼리이며 위 SQL은 원리를 설명한 것입니다. 조회 후 따로 “실행 중”이라고 쓰는 대신 아직 idle일 때만 갱신합니다. 갱신된 행이 없으면 잠금을 얻지 못한 것입니다. 대기 중이던 도구 호출 목록도 스냅샷과 일치할 때만 지워, 중간에 바뀐 상태를 덮어쓰지 않습니다.

releaseRunLockIfCurrent()는 currentRunId === runId인 경우에만 잠금을 풉니다. 오래된 실행의 취소 처리가 뒤늦게 도착해 새 실행의 잠금을 풀어 버리는 문제를 막는 조건입니다.

이보다 앞에서는 V1ThreadInProjectGuard.canActivate()가 ensureThreadByProjectId()를 통해 프로젝트와 사용자 contextKey에 맞는 대화인지 확인합니다. API 키가 있다고 모든 사용자의 대화를 읽을 수 있게 해서는 안 됩니다. userKey 문자열 자체가 브라우저 사용자의 신원을 증명하는 토큰은 아니라는 점도 구별해야 합니다.

응답 경계도 세심합니다. V1Controller.createRun()은 Content-Type: text/event-stream으로 스트림을 열고 응답 완료 전에 연결이 닫히면 cancelRun()을 요청합니다. 헤더를 보낸 뒤 생긴 오류는 새 HTTP 오류 응답 대신 RUN_ERROR 이벤트로 보냅니다.

비유

입장권 검사와 회의실 예약은 서로 다른 문제입니다. 가드는 “이 사람이 이 방에 들어와도 되는가”를 확인하고, 실행 잠금은 “같은 방을 두 팀이 동시에 사용하지 않는가”를 확인합니다. 둘 중 하나만 있어서는 안전한 대화 실행이 되지 않습니다.

4-6. 클라이언트 도구를 만나면 어디로 돌아갈까

모든 도구가 서버에서 실행되는 것은 아닙니다. react-sdk/src/v1/hooks/use-tambo-v1-send-message.ts는 입력 대기 이벤트를 만나면 executeAllPendingTools()로 등록된 클라이언트 도구를 실행합니다. executeToolsAndContinue()는 결과와 previousRunId를 담아 다음 스트림을 요청합니다.

범용 클라이언트에도 같은 책임을 가진 packages/client/src/utils/send-message.ts가 있습니다. 이 구현에서는 완료되지 않은 도구의 ID를 추적한 후 결과를 다음 요청에 연결합니다. “한 번의 질문 = 한 번의 HTTP 요청”으로만 생각하면 이 왕복을 이해하기 어렵습니다.

컴포넌트 props는 화면을 그리기 위한 입력이고, 도구는 데이터를 가져오거나 동작을 수행하는 함수입니다. 두 가지가 모두 모델의 도구 호출 형식과 연결될 수 있어도, 실제 실행 장소와 부작용은 구분해서 설계해야 합니다.

5디렉토리 구조 해부

폴더 이름보다 책임과 읽는 순서를 기억하기
tambo/ ├── react-sdk/ React 공개 SDK │ └── src/ │ ├── index.ts 현재는 v1/index를 내보냄 │ ├── v1/ 공개 Provider·훅·렌더러 │ ├── providers/ 등록·인증·상호작용 Context │ ├── schema/ 스키마 변환 관련 코드 │ ├── hoc/ withTamboInteractable │ └── mcp/ React 측 MCP 연결 ├── packages/ │ ├── client/ React에 종속되지 않는 클라이언트 │ ├── core/ 공통 모델·순수 유틸리티 │ ├── backend/ 모델 어댑터·결정 루프·스트리밍 │ ├── db/ Drizzle 스키마·쿼리·마이그레이션 │ ├── react-ui-base/ React UI 기본 구성요소 │ ├── ui-registry/ 배포할 UI 컴포넌트와 관련 코드 │ ├── testing/ 공유 테스트 지원 코드 │ ├── eslint-config/ 공유 린트 설정 │ └── typescript-config/ 공유 TypeScript 설정 ├── apps/ │ ├── api/ NestJS API │ ├── web/ Cloud 관리 화면 │ ├── docs-mcp/ 문서 검색을 위한 MCP 앱 │ └── test-mcp-server/ MCP 테스트용 서버 ├── cli/ tambo 명령행 도구 ├── create-tambo-app/ 새 프로젝트 생성 진입점 ├── showcase/ 컴포넌트 시연 앱 ├── docs/ 문서 사이트·MDX 예제 ├── scripts/ 개발·자체 호스팅 보조 스크립트 ├── .github/workflows/ CI·배포·릴리스 자동화 ├── package-lock.json 구체적인 의존성 해석 결과 ├── turbo.json 작업 순서·캐시 설정 └── docker-compose.yml DB·API·웹·MinIO 서비스 정의

위 트리는 학습에 필요한 부분만 추렸습니다. package.json이 있는 저장소 루트를 기준으로 모든 파일 경로를 표기합니다.

5-1. 처음부터 끝까지 읽지 말고 요청 한 건을 추적하기

  1. react-sdk/src/index.ts에서 공개 API의 실제 출발점을 확인합니다.
  2. react-sdk/src/v1/providers/tambo-v1-provider.tsx에서 Provider 조합을 봅니다.
  3. react-sdk/src/v1/hooks/use-tambo-v1-send-message.ts에서 메시지 전송과 도구 왕복을 읽습니다.
  4. packages/client/src/utils/event-accumulator.ts에서 상태가 바뀌는 규칙을 읽습니다.
  5. apps/api/src/v1/v1.controller.ts·apps/api/src/v1/v1.service.ts에서 서버 경계를 봅니다.
  6. packages/backend/src/util/component-streaming.ts에서 모델 조각이 UI 이벤트가 되는 지점을 찾습니다.

문서를 먼저 읽더라도 마지막에는 export와 호출부로 돌아오세요. 이 커밋의 react-sdk/AGENTS.md는 예전 Provider 계층을 설명하는 부분이 있지만, 실제 공개 경로는 v1입니다. 설계 설명과 현재 구현을 대조하는 습관 자체가 중요한 학습입니다.

5-2. 추적 파일 기준 코드 규모

분류파일 수물리 줄 수
TypeScript (.ts)702134,027
TSX (.tsx)53987,407
JavaScript 계열521,921
CSS142,092
SQL95796
셸 스크립트15949
YAML 계열232,388
TS·TSX 테스트 부분24986,053 — 위 TS·TSX에 이미 포함

git ls-files의 추적 파일 목록을 확장자로 분류한 물리 줄 수입니다. 마지막 개행이 없는 파일의 마지막 줄도 1줄로 계산했습니다. 빈 줄·주석·타입 선언·테스트·예제·추적된 생성 코드가 포함되며, 의존성 설치물·Git 내부 파일·JSON 잠금 파일·Markdown·스냅샷은 이 표에서 제외했습니다.

TS·TSX 합계는 221,434줄입니다. 그중 파일명에 .test. 또는 .spec.가 있는 파일은 약 38.9%입니다. 이것은 실행 커버리지가 아닙니다. 테스트 줄 수가 많다고 모든 분기가 검증되었다고 단정할 수 없습니다.

패키지별로는 TS·TSX 기준 apps/web 45,016줄, apps/api 40,246줄, react-sdk 39,319줄, packages/backend 14,117줄, packages/client 8,558줄입니다. 각 값에도 테스트가 포함됩니다.

6학습 포인트 (기술별)

“무슨 라이브러리인가”를 “내가 설명할 수 있는 원리”로 바꾸기

6-1. React: Context 조합과 부분 입력에 강한 컴포넌트

TamboProvider는 단일 기능을 가진 여러 Provider를 조합합니다. 등록 목록, 인증, MCP, 상호작용, 스트리밍, 입력 상태를 하나의 거대한 훅에 전부 넣지 않습니다. 먼저 각 Provider가 무엇을 제공하는지 표로 적고, 그 값을 읽는 훅으로 화살표를 그려 보세요.

다음은 소스의 등록 형태를 따른 교육용 예시입니다. 모델을 호출하지 않고도 “데이터가 아직 덜 왔을 때 화면이 어떻게 보일까”를 생각할 수 있습니다. 아래 컴포넌트는 빈 값과 일부만 도착한 props도 표시하도록 구성했습니다.

import { z } from "zod";
import type { TamboComponent } from "@tambo-ai/react";

const salesSchema = z.object({
  title: z.string().describe("카드에 표시할 매출 제목"),
  amount: z.number().describe("매출액, 단위는 만 원"),
});

type SalesProps = z.infer<typeof salesSchema>;

function SalesCard({ title, amount }: Partial<SalesProps>) {
  const shownTitle = title ?? "매출을 불러오는 중";
  const shownAmount = typeof amount === "number"
    ? `${amount.toLocaleString("ko-KR")}만 원`
    : "집계 중";

  return (
    <section>
      <h3>{shownTitle}</h3>
      <p>{shownAmount}</p>
    </section>
  );
}

export const components: TamboComponent[] = [
  {
    name: "SalesCard",
    description: "지역별 또는 기간별 매출 금액 한 개를 표시한다",
    component: SalesCard,
    propsSchema: salesSchema,
  },
];

Partial<SalesProps>는 컴포넌트가 중간 입력을 받아도 표시를 유지하도록 한 교육용 선택입니다. 스키마의 최종 필드 요구와 화면의 중간 표시 정책은 서로 다른 책임입니다. 금액이 0일 때도 정상 표시되도록 진릿값 검사 대신 숫자 타입을 확인한 이유도 설명해 보세요.

6-2. TypeScript: 타입 추론과 실행 시 검증의 차이

z.infer는 스키마로부터 개발 중 타입을 얻습니다. 그러나 네트워크로 온 JSON이 그 타입을 지키는지는 별도로 확인해야 합니다. validateTool()의 속성 확인과 streamReducer()의 이벤트별 분기를 비교하면 “외부 입력을 확인하는 코드”와 “확인된 타입을 좁혀 사용하는 코드”의 차이가 보입니다.

실습 아이디어: 가짜 이벤트 세 개를 종이에 적고 type 값에 따라 어떤 속성을 읽을 수 있는지 표시하세요. RUN_STARTED의 thread ID와 텍스트 이벤트의 message ID가 왜 서로 다른지 설명할 수 있어야 합니다.

6-3. 스키마: 하나의 정의를 여러 소비자에 맞게 바꾸기

propsSchema는 개발자가 작성하기 편한 정의이고, API에 실리는 propsSchema는 JSON으로 직렬화할 수 있는 설명입니다. validateAndPrepareComponent()와 toAvailableComponent()를 차례로 읽어 같은 이름의 데이터가 어느 단계에서 어떤 형태로 바뀌는지 적어 보세요.

실습 아이디어: 필드 세 개짜리 스키마를 만들었다고 가정하고, 각 필드의 타입뿐 아니라 단위·허용 범위·설명문을 쓰세요. “금액”만 적는 것과 “원 단위의 세금 포함 금액”이라고 적는 것의 차이가 모델 선택과 데이터 해석에 영향을 줍니다.

6-4. 비동기 처리: AsyncIterator와 중간 오류

runDecisionLoop()는 async function*으로 여러 결과를 순차적으로 내보냅니다. 일반 Promise가 마지막 값 하나를 기다리는 방식이라면, 비동기 반복자는 도착하는 결과를 순서대로 처리하는 데 적합합니다. for await...of를 만날 때는 “무엇을 기다리며, 취소 신호는 어디로 전달되는가”를 함께 확인하세요.

실습 아이디어: 입력 조각이 오지 않는 경우, 중간에 끊기는 경우, 마지막 JSON이 틀린 경우를 나눠 적고 processJsonDelta()와 finalize() 중 어디에서 처리해야 할지 분류합니다.

6-5. 백엔드: HTTP 응답 수명과 DB 트랜잭션

일반 JSON API에서는 마지막에 성공 또는 실패를 한 번 돌려줍니다. SSE에서는 이미 보낸 결과를 되돌릴 수 없으므로 스트림 내부의 오류 표현이 필요합니다. V1Controller.createRun()의 headersSent 분기가 그 차이를 보여 줍니다.

DB에서는 startRun()의 트랜잭션 안과 밖을 구분해 보세요. 모델 응답이 끝날 때까지 긴 DB 트랜잭션을 유지하는 것과, 짧은 트랜잭션으로 실행 상태를 예약하는 것은 다릅니다. 여기서는 실행 준비를 원자적으로 묶은 뒤 스트리밍 작업을 이어 갑니다.

6-6. 테스트: 정상 결과보다 경계를 읽기

테스트 주제파일과 확인할 경계
부분 JSONpackages/backend/src/util/component-streaming.test.ts
미완성 JSON, 최종 파싱 실패, 크기 제한, 슬래시·물결표가 있는 키
이벤트 누적react-sdk/src/v1/utils/event-accumulator.test.ts
임시 대화 이동, 여러 컴포넌트, 메시지 병합, 이벤트 순서
패치 적용react-sdk/src/v1/utils/json-patch.test.ts
패치 실패 메시지에 연산 요약과 대상 키를 포함하는지 검사
API 응답apps/api/src/v1/__tests__/v1.controller.test.ts
컨트롤러 요청·응답 처리 시나리오

테스트를 읽을 때는 정답 문자열보다, 작성자가 어떤 실패 상황을 별도 사례로 남겼는지 먼저 보세요.

이 섹션의 도착점

내 말로 “스키마는 전송 전에 변환된다”, “중간 props는 완성된 입력과 다르다”, “이벤트와 DB 상태를 함께 관리한다”를 설명할 수 있으면 핵심을 잡은 것입니다. 모노레포의 모든 라이브러리 사용법을 한 번에 익힐 필요는 없습니다.

7하드웨어 / 시스템 요구사항

읽기, SDK 연습, 전체 서버 운영의 비용을 분리하기

이 저장소는 자체 모델 가중치를 포함한 로컬 추론 엔진이 아닙니다. 기본적인 모델 응답 경로는 외부 제공자 호출입니다. tambo 소스를 공부하는 데 GPU는 필요 없고, 모델 실행 비용과 서버 운영 비용은 별도로 계산해야 합니다.

목적확인된 요구사항자원 판단
소스만 읽기텍스트 편집기, Git, 줄 수 계산용 Python 등Node·Docker·모델 API 키 없이 가능
워크스페이스 개발루트 기준 Node >=22, npm >=11저장소 지정 버전은 Node 22.22.0·npm 11.7.0
자체 호스팅공식 안내 기준 Docker·Compose, 모델 제공자 키PostgreSQL·API·웹 및 필요한 저장소 자원 고려
GPU·VRAM일반 외부 모델 API 경로에 별도 GPU 요구 없음로컬 호환 모델 서버를 따로 붙이면 그 서버 요구량을 추가
CPU·RAM자체 호스팅 안내와 Compose에 고정 최소 사양 수치 없음함께 실행할 서비스·빌드·테스트와 동시 요청량에 따라 산정
디스크심볼릭 링크를 제외한 추적 파일 합계 81,293,032바이트, 약 77.5MiBGit·의존성·이미지·빌드 결과·DB를 포함한 설치 크기는 별도

코드 규모만으로 필요한 RAM을 결정할 수는 없습니다. 동시 Next.js 빌드와 테스트, 컨테이너, 브라우저를 함께 켜는지에 따라 필요한 자원이 달라집니다. 코드 열람과 전체 서비스 운영에 필요한 자원을 구분해서 계획하세요.

7-1. 포트는 호스트와 컨테이너 안에서 다를 수 있다

서비스개발·Compose에서 확인한 값주의할 점
Cloud 웹개발 8260 / Compose 8260:3000왼쪽은 호스트, 오른쪽은 컨테이너 포트
API기본 개발 8261 / Compose 8261:3000두 서비스가 컨테이너 안에서 각각 3000을 써도 충돌하지 않음
PostgreSQLCompose 5433:5432컨테이너 간 DB 주소는 서비스 이름 postgres와 5432
MinIO9000·9001S3 호환 저장 API와 관리 콘솔
시연·문서 앱8262·8263전체 Cloud 실행과 문서·SDK 개발 실행은 별도 작업

SELF-HOSTING.md는 웹·API·PostgreSQL 세 서비스를 중심으로 설명하지만, 실제 docker-compose.yml에는 MinIO도 정의되어 있습니다. 첨부 파일 기능은 별도의 S3 설정을 확인해야 합니다. StorageConfigService.hasStorageConfig()는 클라이언트 구성, 비어 있지 않은 버킷, 서명용 비밀값을 확인합니다.

7-2. 키와 네트워크를 이해한 뒤 실행 계획 세우기

자체 호스팅 안내에는 DB 암호, API_KEY_SECRET, PROVIDER_KEY_SECRET, NEXTAUTH_SECRET, 모델 제공자 키 등이 등장합니다. 이 이름들은 비밀값의 역할을 구분하기 위한 것이며 실제 값을 공부자료에 복사할 필요는 없습니다.

브라우저에서 사용하는 Tambo용 프로젝트 키와 모델 제공자의 비밀 API 키도 구분해야 합니다. 제공자 비밀 키는 서버에서 관리합니다. API 요청의 사용자 소유권은 프로젝트 키만으로 해결되지 않으므로 실제 앱에서는 사용자 토큰과 권한 흐름까지 설계해야 합니다.

실행 범위

아래 과제의 핵심은 가짜 데이터와 종이에 적은 상태 변화만으로도 익힐 수 있습니다. 패키지 설치, Docker 실행, DB 초기화, 모델 호출은 서비스 전체를 연결하는 단계에서 다룹니다. 실제 실행은 별도 연습 프로젝트와 본인이 관리하는 서버 구성을 준비한 뒤 진행합니다.

8직접 해볼 수 있는 실습 과제

작은 화면에서 시작해 이벤트와 동시성으로 확장하는 다섯 과제

아래 읽기 명령은 저장소 루트에서 실행합니다. 확장 실습은 별도 학습 프로젝트에서 진행하며, react-sdk/의 구현과 비교해 볼 수 있습니다. 제공자 API나 운영 서버에 요청하지 않아도 모든 과제의 핵심 원리를 확인할 수 있습니다.

실습의 합격 기준은 “AI가 그럴듯하게 답했다”가 아니라, 불완전한 입력과 실패 상황에서도 상태 변화를 설명할 수 있는가입니다.

과제 1 · 등록 경계 이해

매출 카드의 주문 양식 만들기 입문

목표: 6절의 SalesCard에 region 필드를 추가한다고 가정하고, 화면 props·스키마·컴포넌트 설명문을 함께 작성합니다.

순서: 사용할 데이터 세 개를 적고, 스키마를 정의하고, registerComponent()에서 서버 전송용 객체까지의 모양을 종이에 그립니다. 확장 실습에서는 새 학습 프로젝트의 정적 화면으로 카드만 만듭니다.

완료 기준: 실제 React 함수가 서버 전송용 JSON에 들어가지 않는 이유와, 필드의 이름·타입·단위가 모델에게 어떻게 전달되는지 설명할 수 있습니다.

읽을 곳: react-sdk/src/util/registry-validators.ts, packages/client/src/utils/registry-conversion.ts.

과제 2 · 중간 상태 설계

데이터가 덜 와도 깨지지 않는 화면 입문

목표: 완성 props만 가정하지 않고 빈 값·일부 값·최종 값에 대한 화면을 설계합니다.

순서: 아래 네 입력의 표시 결과를 먼저 적습니다. 새 학습 화면에서 수동으로 입력값을 바꾸는 방식으로 확장할 수 있으며 모델 연결은 필요 없습니다.

{}
{"title":"서울"}
{"title":"서울","amount":0}
{"title":"서울","amount":1250}

완료 기준: 0이 “집계 중”으로 사라지지 않고, 제목만 들어온 순간에도 예외 없이 의미 있는 표시를 유지합니다. 최종 데이터 검증과 로딩 표시의 책임도 각각 설명합니다.

읽을 곳: react-sdk/src/v1/components/v1-component-renderer.tsx.

과제 3 · 대화 상태 추적

임시 대화가 실제 대화가 되는 순간 중급

목표: 메시지를 즉시 보여 주면서도 서버가 발급한 대화 ID로 연결하는 원리를 이해합니다.

순서: placeholder에 사용자 메시지를 넣은 상태를 그린 뒤, RUN_STARTED에서 실제 ID를 받았을 때의 threadMap과 currentThreadId를 적습니다. 사용자가 이미 다른 대화로 이동한 경우도 추가합니다.

확장: 별도 파일에 필요한 세 가지 액션만 받는 작은 순수 리듀서를 직접 작성합니다. 레포 전체를 복사하지 말고 임시 대화 생성·이동·선택 규칙만 재현합니다.

완료 기준: 사용자 메시지가 중복되지 않고, 다른 대화를 보던 사용자의 선택도 유지됩니다.

읽을 곳: packages/client/src/utils/event-accumulator.ts의 streamReducer()와 RUN_STARTED 분기.

과제 4 · 패치 형식 이해

슬래시가 들어 있는 props 키 다루기 중급

목표: JSON 객체의 키와 JSON Pointer 경로의 차이를 이해합니다.

순서: 키가 sales/kr, rate~raw, 빈 문자열인 객체를 만들었다고 가정합니다. 각각의 한 단계 패치 경로를 적고, 아래 정답과 비교합니다.

키 sales/kr  -> 경로 /sales~1kr
키 rate~raw  -> 경로 /rate~0raw
키 ""        -> 경로 /

확장: 새 학습 파일에 문자열 이스케이프 함수를 작성하고, 바꾸기 전 객체를 보존하면서 변경분을 적용하는 간단한 사례를 설계합니다. 전체 RFC 구현을 한 번에 만들 필요는 없습니다.

완료 기준: /sales/kr와 /sales~1kr가 가리키는 위치가 다른 이유, ~ 치환 순서에 주의해야 하는 이유를 설명합니다.

읽을 곳: packages/backend/src/util/component-streaming.ts의 createJsonPatchPath(), 같은 폴더의 테스트.

과제 5 · 서버 실행 수명 설계

동시에 들어온 두 요청과 뒤늦은 취소 고급

목표: 실행 시작의 충돌과 이전 실행의 늦은 정리 작업을 구분해 막습니다.

순서: 같은 idle 대화에 요청 A·B가 도착하는 시간표를 그립니다. 조건부 갱신으로 A만 잠금을 얻은 뒤 A가 완료되고 C가 시작했다고 가정합니다. 이때 뒤늦게 A의 취소 정리가 도착하면 어떤 조건으로 C를 보호해야 할지 적습니다.

확장: 별도 파일의 가짜 저장소에 runStatus와 currentRunId만 두고, 기대 run ID가 일치할 때만 해제하는 규칙을 구현합니다. 실제 DB를 사용하지 않아도 순서별 기대 결과를 표로 검증할 수 있습니다.

완료 기준: A·B가 동시에 성공하지 않고, A의 정리 작업이 C의 잠금을 해제하지 않으며, 충돌을 재시도 가능한 실패로 구분합니다.

읽을 곳: packages/db/src/operations/runs.ts의 acquireRunLock()·releaseRunLockIfCurrent(), V1Service.startRun().

읽기 전용으로 호출부 찾기

# 저장소 루트에서 사용하는 읽기 명령
git rev-parse --short HEAD
rg -n 'export \*|TamboProvider' react-sdk/src/index.ts
rg -n 'registerComponent|validateAndPrepareComponent' react-sdk/src
rg -n 'processJsonDelta|finalize' packages/backend/src/util/component-streaming.ts
rg -n 'acquireRunLock|releaseRunLockIfCurrent' packages/db/src/operations/runs.ts

검색 결과가 많으면 이름을 하나 골라 정의 파일과 호출부를 번갈아 읽으세요. 함수 정의만 읽으면 입력이 어디서 왔는지 놓치고, 호출부만 읽으면 실패 조건을 놓칩니다.

9관련 기술 심화 학습 로드맵

매주 작은 산출물을 남기는 6주 경로

주차는 학습 분량을 나누기 위한 제안이며 고정 일정이 아닙니다. React 경험이 적다면 앞의 두 주를 충분히 반복하세요. 모델을 연결하기 전에 가짜 입력으로 화면과 이벤트 처리를 완성하면, 실패 원인을 한 번에 하나씩 배울 수 있습니다.

주차배울 주제소스와 주간 산출물
1주차React props·컴포넌트·안정적인 keyComponentRenderer 읽기. 서로 다른 입력을 받는 매출 카드와 네 가지 중간 화면 설계
2주차TypeScript·Zod·JSON Schemaregistry-validators.ts와 registry-conversion.ts. 함수 객체와 전송용 JSON을 분리한 도식
3주차이벤트·리듀서·불변성event-accumulator.ts. 임시 대화가 실제 ID로 옮겨지는 상태표와 작은 리듀서
4주차비동기 반복자·부분 JSON·JSON Patchcomponent-streaming.ts. 조각별 props·패치·완료 상태를 적은 추적표
5주차NestJS 가드·SSE·트랜잭션v1.controller.ts·runs.ts. 동시 요청과 취소의 순서도
6주차모노레포·컨테이너·운영 경계turbo.json·Dockerfile·Compose. 서비스·포트·키·저장소 역할을 구분한 배포 계획서

다음 단계로 넘어가기 전 확인할 질문

  1. 등록한 React 함수는 브라우저에 남고 서버에는 어떤 필드만 전달되나요?
  2. 필수 props가 아직 안 왔을 때 화면은 무엇을 표시하나요?
  3. 같은 이벤트 목록을 처리했을 때 상태 변화 과정을 재현할 수 있나요?
  4. 스트림을 연 뒤 오류가 나면 왜 일반 HTTP 오류로 다시 응답하기 어렵나요?
  5. 사용자 권한 확인과 동일 대화의 동시 실행 방지는 어떻게 다른가요?
  6. SDK 패키지의 라이선스와 자체 호스팅 서버의 라이선스를 각각 확인했나요?
학습 순서의 이유

운전 연습 때 계기판·페달·도로 규칙을 익힌 다음 복잡한 교차로에 가는 것과 같습니다. 처음부터 모델·MCP·DB·인증을 모두 연결하면 어디에서 틀렸는지 찾기 어렵습니다. 화면, 이벤트, 서버 경계를 차례로 익히면 마지막 연결 단계의 질문이 훨씬 구체적이 됩니다.

10핵심 키워드 사전

이 저장소를 읽는 데 꼭 필요한 말만 정리
용어쉬운 설명
생성형 UIAI의 판단을 텍스트뿐 아니라 앱의 실제 화면 구성으로 표현하는 방식
컴포넌트입력값을 받아 화면 일부를 그리는 재사용 가능한 React 함수 등의 단위
props컴포넌트에 전달하는 제목·금액·목록 같은 입력값
등록 목록컴포넌트 이름과 실제 함수·입력 설명을 연결해 둔 명부. 코드에서는 registry
스키마데이터가 가져야 할 필드·타입·제약을 적은 양식
JSON Schema그 양식을 JSON으로 표현하는 표준. 다른 프로그램이나 모델 도구 정의에 전달하기 좋음
Standard Schema스키마 라이브러리들이 검증 기능을 공통 모양으로 제공하도록 하는 인터페이스
직렬화객체를 JSON처럼 전송·저장 가능한 형태로 바꾸는 일. React 함수를 그대로 보내는 것과 다름
도구 호출모델이 실행할 함수의 이름과 인수를 요청하는 방식. 실제 실행은 애플리케이션이 담당
클라이언트 도구브라우저 등 클라이언트 환경에서 실행하도록 등록한 함수
MCPAI 애플리케이션이 도구·리소스 등을 서버와 교환하기 위한 프로토콜
AG-UI에이전트 실행의 텍스트·도구·상태 등을 UI에 전달할 때 사용하는 이벤트 형식
SSE서버가 열어 둔 HTTP 응답으로 이벤트를 순서대로 보내는 방식. 양방향 WebSocket과 구분
델타이전 값과 비교해 달라진 부분. 이 레포에서는 텍스트 조각이나 props 변경분
부분 JSON아직 마지막 괄호나 문자열이 닫히지 않은 생성 중 입력. 완성된 JSON과 처리 정책이 다름
JSON Patch객체 전체 대신 추가·교체·삭제 같은 변경 명령을 전달하는 형식
JSON Pointer/amount처럼 JSON 안의 위치를 지정하는 경로 표기
리듀서현재 상태와 사건을 받아 다음 상태를 계산하는 함수
불변성기존 객체를 몰래 수정하는 대신 새 상태를 만들어 변경을 명확하게 하는 원칙
낙관적 UI서버 확인 전에 사용자 행동을 먼저 화면에 반영하는 기법
thread여러 메시지와 실행이 이어지는 하나의 대화 단위. CPU 스레드와 다른 뜻
run한 대화에서 모델과 도구 처리를 진행하는 실행 단위. 한 대화에 여러 run이 이어질 수 있음
Provider·ContextReact 하위 컴포넌트에 등록 목록이나 상태 같은 값을 공유하는 장치
가드컨트롤러 처리 전에 요청이 접근 조건을 충족하는지 확인하는 NestJS 장치
트랜잭션서로 연결된 DB 변경을 함께 성공시키거나 함께 취소하는 작업 단위
조건부 갱신기대 상태와 일치하는 행만 변경하는 방식. 이 레포에서는 실행 충돌 방지에 사용
모노레포여러 앱과 패키지를 하나의 저장소에서 관리하는 구조
CJS·ESMJavaScript 코드의 모듈 배포 방식 두 가지. SDK는 각각에 맞는 출력을 만듦
자체 호스팅제공자의 관리 서버 대신 자신이 관리하는 환경에서 서비스 전체를 운영하는 방식
shallow clone과거 이력 전체 대신 제한된 커밋 이력만 받은 복제본. 전체 개발 역사를 판단하기에는 부족

외워야 할 약어 목록으로 보지 마세요. “모델이 도구를 고른다 → 서버가 이벤트를 보낸다 → 리듀서가 props를 바꾼다 → React가 그린다”라는 문장에 각 용어를 넣어 설명하면 기능과 이름을 함께 기억할 수 있습니다.

11참고 링크

공식 자료, 분석한 소스, 확인 범위를 함께 남기기

공식 저장소·안내

핵심 구현 소스

관련 기술을 더 공부할 공식 자료

프로젝트 현황 (2026-10-23 GitHub·TrendShift 기준)

항목확인 내용근거·범위
프로젝트tambo-ai/tambo · React용 생성형 UI SDK2026-10-23 GitHub 기준
선정TrendShift Daily #292026-10-23 TrendShift 기준
스타 / 포크11,200 / 5702026-10-23 GitHub 기준
생성일2024-06-152026-10-23 GitHub 기준
주 언어TypeScript2026-10-23 GitHub 기준
최신 릴리스web-v0.136.0 · 2026-10-092026-10-23 GitHub 기준. 전체 패키지의 공통 버전은 아님
최근 push2026-10-212026-10-23 GitHub 기준. 특정 커밋의 작성 시각과 구분
소스 기준 커밋6e2c1bf · 2026-10-09 · chore(main): release web 0.136.0 (#2977)10월 21일 push 이전의 고정 커밋
코드 규모TS·TSX 1,241파일 / 221,434줄추적 파일의 물리 줄 수. 테스트·예제·빈 줄 포함
테스트 규모TS·TSX 테스트 249파일 / 86,053줄코드 규모에 포함. 커버리지 측정값 아님
추적 파일2,069개 / 약 77.5MiB심볼릭 링크 1개 제외. 파일 크기의 합이며 설치 크기·Git 내부 제외
라이선스GitHub 표시·루트·SDK MIT / Cloud 웹·API·backend·db Apache-2.02026-10-23 GitHub 및 각 LICENSE·package.json 기준
호스팅 상태신규 가입 중단, 2026-10-31까지 API 운영 후 중단, 2026-11-30 데이터 삭제 예정커밋 6e2c1bf의 README·자체 호스팅 공지 기준
도입 전에 알아둘 한계
고정 커밋의 구현과 현재 운영 상태를 구분하세요

메타데이터 날짜는 2026-10-23이지만 소스 기준은 2026-10-09의 6e2c1bf입니다. 최근 push 시각이 더 늦다고 해서 그 변경이 이 글에 반영된 것은 아닙니다. 이력이 제한된 shallow clone만으로 전체 커밋 수·기여자 비중·개발 속도를 판단하기는 어렵습니다.

코드 예시는 등록 구조와 중간 props 처리를 설명하는 학습용입니다. 실제 앱에 적용할 때는 사용하는 패키지 버전에 맞춰 빌드와 타입 검사를 진행하세요.

공식 README의 종료 공지, 일부 오래된 API 이름, 백엔드 라이선스 비교표는 실제 구현·라이선스 파일과 함께 읽어야 합니다. 위의 사용 방식 비교는 제품 순위나 성능 우열을 뜻하지 않습니다.

스키마는 모델 입력 계약에 도움을 주지만 렌더러의 검증 정책에는 한계가 있습니다. 자체 호스팅을 선택하면 사용자 인증, 도구 권한, 제공자 키, 백업과 운영 책임을 별도로 설계해야 합니다. README에 호스팅 선택지가 남아 있다는 이유만으로 신규 Cloud 가입이 가능하다고 안내하지 마세요.