OpenUI

OpenUI (OpenUI)

OpenUI는 언어 모델이 openui-lang이라는 선언형 형식으로 완전한 인터랙티브 UI를 만들어내게 하는 생성 UI 라이브러리예요. 에이전트가 채팅 메시지 대신 카드·차트·테이블·탭·폼이 담긴 컴포넌트 트리를 반환하고, Renderer가 그것을 실제 React UI로 바꿔줍니다. 이 통합은 모델이 데이터 분석가이자 UI 디자이너 역할을 하는 데이터가 풍부한 출력 — 리포트, 대시보드, 데이터 탐색기 — 에 특히 잘 어울려요.

출처: LangChain 공식 문서 — openui

동작 방식 (How it works)

  1. 시스템 프롬프트 생성 — 시작 시 openuiLibrary.prompt()를 호출해서 모델이 유효한 컴포넌트 트리를 쓰도록 도와주는 완전한 openui-lang 레퍼런스를 만들어요.
  2. 첫 메시지에 주입 — 새 대화가 시작될 때 시스템 프롬프트를 여는 시스템 메시지로 보내요.
  3. 모델이 openui-lang 작성 — 모델이 산문 대신 root = Stack([header, kpis, chart]) 같은 프로그램으로 응답하죠.
  4. Renderer로 렌더링 — 텍스트를 OpenUI의 Renderer와 컴포넌트 라이브러리에 넘겨 트리를 파싱하고 렌더링해요.

설치 및 스타일 (Installation & styles)

OpenUI의 번들 스타일을 CSS 엔트리 포인트나 루트 컴포넌트에서 직접 import하세요.

시스템 프롬프트 생성 (Generate the system prompt)

OpenUI는 openuiLibrary.prompt() 함수를 제공하는데, 모든 컴포넌트 시그니처·문법 규칙·스트리밍 팁·예제가 담긴 완전한 openui-lang 레퍼런스를 생성해요. 모듈 로드 시 한 번만 호출하세요. preamble로 기본 페르소나를 덮어쓰고, additionalRules로 작업별 제약을 주입할 수도 있습니다.

useStream으로 시스템 프롬프트 주입 (Inject the system prompt via useStream)

새 스레드마다 시스템 프롬프트를 첫 메시지로 보내요. stream.messages.length === 0으로 새 스레드를 감지하고 system 메시지를 앞에 붙이면 됩니다. 이후 턴에는 주입을 건너뛰어 스레드 기록에 프롬프트가 중복되지 않게 해요.

Renderer로 렌더링 (Render with the Renderer)

AI 메시지의 텍스트 콘텐츠를 RendereropenuiLibrary와 함께 바로 넘겨요. 활성 스트림 동안 isStreaming={true}를 넘기면 정의가 도착하면서 미해결 참조를 Renderer가 우아하게 처리할 수 있습니다.

openui-lang 형식 (The openui-lang format)

모델은 JSON spec이 아니라 프로그램을 작성해요. 모든 문장이 할당(assignment)이고, root가 엔트리 포인트예요. 공식 프롬프트가 이 형식을 가르치는데, 호이스팅(hoisting)을 포함해요 — root를 먼저 써서 UI 셸이 즉시 나타나게 하는 기법이죠. 호이스팅을 켜면(권장) root 줄이 먼저 쓰여 페이지 구조가 즉시 나타나고, 모델이 각 섹션을 정의하면서 채워집니다.

점진적 렌더링 유틸리티 (Progressive rendering utilities)

useStreamRenderer에 직접 연결하면 스트리밍 토큰마다 재렌더링이 일어나고, 응답당 수백 번의 no-op 재파싱이 발생해요. 차트 컴포넌트는 데이터가 아직 도착하지 않았을 때 크래시가 나기도 하죠. 아래 유틸리티들이 이런 문제를 해결합니다. 전체 블록을 프로젝트에 복사하고 <Renderer>stable을 넘기세요.

후속 질의 (Follow-up queries)

OpenUI의 Button 컴포넌트는 continue_conversation 액션 타입을 지원해요. 사용자가 후속 버튼을 클릭하면 RendereronAction을 발화시키고, AIMessageView가 버튼의 라벨을 입력창에 입력하는 것과 똑같은 코드 경로로 다음 사용자 메시지로 제출합니다. 시스템 프롬프트의 additionalRules로 모든 리포트에 "Explore Further" 섹션을 추가할 수 있어요.

Deep Agents로 병렬 대시보드 구축 (Build a parallel dashboard with Deep Agents)

위 흐름은 하나의 OpenUI 프로그램을 하나의 서페이스로 렌더링해요. 더 풍부한 앱에서는 Deep Agents 코디네이터가 여러 전문 에이전트에게 위임하고, 각 에이전트가 자신의 OpenUI 패널을 하나의 useStream 연결 위에서 동시에 스트림하게 할 수 있어요. OpenUI 병렬 대시보드 예제는 하나의 대시보드 브리프를 독립적으로 스트리밍되는 Stripe·PostHog·GitHub·Calendar 패널로 바꿔주는데, 커스텀 그래프나 스트림 역다중화 코드가 필요 없어요.

서버(패널 프롬프트 생성)와 클라이언트(Renderer prop)에서 같은 라이브러리 객체를 쓰면, 모델이 알게 되는 컴포넌트가 렌더러가 그릴 수 있는 컴포넌트와 항상 일치합니다.

코디네이터와 패널 에이전트 정의 (Define the coordinator and panel agents)

createDeepAgent라우팅만 담당하는 코디네이터를 만들어요. 브리프에 필요한 전문가들을 고르고 모든 전문가의 task() 호출을 한 메시지로 내보내서 패널들이 동시에 실행되게 하죠. 각 패널 서브에이전트는 사전 생성된 하나의 OpenUI 시스템 프롬프트를 공유하고, 자기 데이터 도메인의 도구만 받아요. 코디네이터는 절대 openui-lang을 쓰지 않아요. 각 패널 에이전트는 도구를 호출한 뒤, 모델이 나머지 문장을 끝내기 전에 렌더러가 그릴 수 있도록 root로 시작하는 완전한 프로그램 하나를 반환합니다.

그래프 등록과 프론트엔드 렌더링 (Register the graph & render panels)

langgraph.json이 내보낸 코디네이터를 가리키게 하고, 프론트엔드에서는 하나의 useStream 연결이 코디네이터와 모든 패널을 나릅니다. 패널은 하드코딩되지 않아요. 각 병렬 task() 호출이 stream.subagents 스냅샷으로 표면화되죠. 각 스냅샷에 대해 useMessages(stream, snapshot) 프로젝션을 스코프해서 패널이 자기 서브에이전트의 메시지만 받고, 그 OpenUI 프로그램을 격리된 Renderer에 공급합니다. SDK가 서브에이전트 토큰 이벤트를 루트 스토어 밖에 두고 각 Panel이 스냅샷 신원으로 메모이즈되기 때문에, 한 패널의 토큰이 다른 패널을 재렌더링하지 않아요.

모범 사례 (Best practices)

  • 시스템 프롬프트는 모듈 로드 시 생성 — React 컴포넌트 안이 아니라요. 프롬프트가 수 킬로바이트라 한 번만 계산해야 해요.
  • 새 스레드에서만 시스템 프롬프트 주입stream.messages.length === 0을 확인하고 이후 턴엔 건너뛰어 스레드 기록에서 프롬프트 중복을 피하세요.
  • 호이스팅 순서 사용root = Stack([...])를 먼저 쓰면 UI 셸이 즉시 나타나고 모델이 각 섹션을 정의하며 점진적으로 채워져요.
  • 완전한 문장 단위로 게이트 — 토큰마다 Renderer를 재렌더링하지 말고, 완전한 문장(name = ComponentCall(...))이 도착했을 때만 갱신하세요.
  • 렌더링 전 차트 데이터 검증 — 차트 컴포넌트는 안정적인 스냅샷에 포함되기 전에 Series와 라벨 배열이 정의되어야 해요.
  • camelCase 변수명 유지 — openui-lang 파서는 camelCase 식별자만 받아들여요. 시스템 프롬프트의 additionalRules에서 이를 강조하세요.
  • 패널을 한 메시지로 위임 — Deep Agents 전문가로 팬아웃할 때 모든 task() 호출을 단일 코디네이터 메시지로 내보내 패널이 하나씩이 아니라 동시에 스트림되게 하세요.
  • 각 패널을 자기 서브에이전트로 스코프stream.subagents에서 패널을 발견하고 각 스냅샷을 useMessages(stream, snapshot)에 넘겨 패널이 자기 서브에이전트 출력만 렌더링하게 하세요.

더 알아보기 (Learn more)