- AG-UI Messages๋ ์์ด์ ํธ์ ์ฌ์ฉ์ ๊ฐ ๋ํ ํ์คํ ๋ฆฌ๋ฅผ ๊ตฌ์ฑํ๋ ์์ ๋จ์ ๊ฐ์ฒด
roleํ๋๋ฅผ discriminator๋ก ์ผ๋ tagged union ๊ตฌ์กฐ (user/assistant/system/developer/tool/activity/reasoning)- LLM ํ๋ก๋ฐ์ด๋(OpenAI/Anthropic/Bedrock ๋ฑ)์ ๋ ๋ฆฝ์ ์ธ ๋ฒค๋ ์ค๋ฆฝ ํฌ๋งท
- ์คํธ๋ฆฌ๋ฐ ์ด๋ฒคํธ(
TEXT_MESSAGE_*,TOOL_CALL_*,REASONING_*)๋ฅผ ๋์ ํ์ฌ materialize๋๋ ์ต์ข ์ํ ์ค๋ ์ท - Generative UI ์คํ(A2UI/Open-JSON-UI/MCP-UI)์ ์ค์ด ๋๋ฅด๋ ์์ก ๊ฒฝ๋ก
ํด๋น ๊ฐ๋ ์ด ํ์ํ ์ด์
- LLM ํ๋ก๋ฐ์ด๋๋ง๋ค ๋ฉ์์ง ํฌ๋งท์ด ์ ๊ฐ๊ฐ โ ํด๋ผ์ด์ธํธ ๋ณ๊ฒฝ ์์ด ๋ฐฑ์๋ ๋ชจ๋ธ ๊ต์ฒด ๊ฐ๋ฅํด์ผ ํจ
- ์คํธ๋ฆฌ๋ฐ ์๋ต์ ๋ํ ํ์คํ ๋ฆฌ๋ก ๋์ ํด์ผ ํจ โ ์ด๋ฒคํธ(ํ๋ฆ)์ ๋ฉ์์ง(์ํ)์ ๋ถ๋ฆฌ ํ์
- ํด ํธ์ถ / ๋ด๋ถ ์ถ๋ก / UI ์ํ / ๋ฉํฐ๋ชจ๋ฌ ์ ๋ ฅ ๋ฑ ์ด์ง์ ์์๋ฅผ ํ๋์ ๋ํ ํ์๋ผ์ธ์ ํตํฉ โ 7๊ฐ์ง role๋ก ์๋ฏธ ๋ถ๋ฆฌ
- ZDR(Zero Data Retention) ๊ฐ์ ์ํฐํ๋ผ์ด์ฆ ์ปดํ๋ผ์ด์ธ์ค ํ๊ฒฝ์์ ์๋ฒ๊ฐ ์ํ ์ ์ฅ ๋ถ๊ฐ โ ํด๋ผ์ด์ธํธ๊ฐ ์ํธํ๋ ์ํ๋ฅผ ์บ๋ฆฌ์ด๋ก ์ด๋ฐ
AS-IS: ๋ฒค๋ ์ข ์์ ์ธ ๋จ์ผ ํฌ๋งท
sequenceDiagram autonumber participant UI as Frontend participant API as OpenAI API UI->>API: OpenAI ์ ์ฉ ํฌ๋งท ์์ฒญ API-->>UI: OpenAI ์ ์ฉ ์คํธ๋ฆฌ๋ฐ ์๋ต Note over UI,API: Anthropic์ผ๋ก ๊ฐ์ํ๋ ค๋ฉด<br/>ํด๋ผ์ด์ธํธ ์ ์ฒด ์์
TO-BE: AG-UI ์ถ์ํ ๋ ์ด์ด (3๊ณ์ธต ๋ถ๋ฆฌ)
sequenceDiagram autonumber participant UI as Frontend participant AD as AG-UI Adapter participant FW as Framework participant LLM as Vendor LLM Note over UI,AD: Layer 1 - AG-UI Protocol ๊ตฌ๊ฐ UI->>AD: AG-UI Message (๋ฒค๋ ์ค๋ฆฝ) Note over AD: ๋ณํ 1 - Adapter ์ฑ ์<br/>aguiMessagesToLangChain AD->>FW: Framework Format<br/>LangChain HumanMessage ๋ฑ Note over FW: ๋ณํ 2 - Framework ์ฑ ์<br/>AG-UI ๋ฌด๊ด FW->>LLM: Vendor API JSON<br/>OpenAI chat.completions ๋ฑ LLM-->>FW: streaming chunks FW-->>AD: framework events AD-->>UI: AG-UI Events (ํ์ค ์คํธ๋ฆผ) Note over UI,AD: ๋ชจ๋ธ ๋ณ๊ฒฝ ์ ํด๋ผ์ด์ธํธ ๋ฌด๋ณ๊ฒฝ
ํต์ฌ: ํ๋ก ํธ์๋๋ Layer 1(AG-UI Protocol) ๋ง ์๋ฉด ๋๊ณ , ๋ด๋ถ์ ํ๋ ์์ํฌ/๋ฒค๋ ๋ณ๊ฒฝ์ Adapter ๊ต์ฒด๋ง์ผ๋ก ํก์๋๋ค.
Message ํ์ 7๊ฐ์ง โ ์ญํ ๊ณผ ๊ฐ์์ฑ
| Role | UI ํ์ | LLM ์ ๋ฌ | ์ฉ๋ |
|---|---|---|---|
user | ํ์ | ์ ๋ฌ | ์ฌ์ฉ์ ์ ๋ ฅ (ํ ์คํธ or ๋ฉํฐ๋ชจ๋ฌ) |
assistant | ํ์ | ์ ๋ฌ (๋ค์ ํด ์ปจํ ์คํธ) | AI ์๋ต + ํด ํธ์ถ |
system | ์จ๊น | ์ ๋ฌ | ํ๋ซํผ ๋ ๋ฒจ ์ง์ (๋ณด์, ํญ์ ์ ์ฉ) |
developer | ์จ๊น | ์ ๋ฌ | ์ฑ ๋ ๋ฒจ ์ง์ (system๋ณด๋ค ์ฝํจ, user๋ณด๋ค ๊ฐํจ) |
tool | ์ปค์คํ | ์ ๋ฌ | ํด ์คํ ๊ฒฐ๊ณผ (toolCallId๋ก ๋งํฌ) |
reasoning | ์ ํ์ ์์ฝ | ์ ๋ฌ (encryptedValue๋ก) | ๋ด๋ถ chain-of-thought |
activity | ์ปค์คํ UI | ์ ๋ฌ ์ ํจ | ํ๋ก ํธ์๋ ์ ์ฉ ์ํ |
types.ts:161-169 ์์ z.discriminatedUnion("role", [...])๋ก ๊ตฌํ๋จ.
โMessage ํ์ โ์ด ์๋ฏธํ๋ ๊ฒ
AG-UI์์ Message๋ **โ๋ํ ํ์คํ ๋ฆฌ์ ๋์ ๋๋ ์์ ๊ฐ์ฒดโ**๋ฅผ ์๋ฏธํ๋ฉฐ, ์ฌ์ฉ์์ ๋ฐฑ์๋ ์ฌ์ด๋ฅผ ์ค๊ฐ๋ ํธ๋ํฝ ์ ๋ถ๋ฅผ ๋ปํ์ง๋ ์๋๋ค.
AG-UI ๋ฐ์ดํฐ ๋ถ๋ฅ
โ
โโ Message (์ํ/๋์ ) โ RunAgentInput.messages ๋ฐฐ์ด์ ์์
โ โโ LLM ์ ๋ฌ: user, assistant, system, developer, tool, reasoning
โ โโ ํ๋ก ํธ์๋ ์ ์ฉ: activity
โ
โโ Event (ํ๋ฆ/์ผํ์ฑ) โ ํต์ ์ค ํ๋ฌ๊ฐ๋ ์ ํธ
โ โโ TEXT_MESSAGE_START/CONTENT/END
โ โโ TOOL_CALL_START/ARGS/END
โ โโ STATE_SNAPSHOT/DELTA
โ โโ RUN_STARTED/FINISHED
โ
โโ ๊ธฐํ
โโ Tool (LLM์ด ํธ์ถ ๊ฐ๋ฅํ ํจ์ ์คํค๋ง)
โโ Context (์ถ๊ฐ ์ปจํ
์คํธ ๋ฐ์ดํฐ)
Event๋ Message๋ฅผ ๊ตฌ์ถํ๊ธฐ ์ํ ์คํธ๋ฆผ ์กฐ๊ฐ์ด๊ณ , ์ด๋ฒคํธ ์ํ์ค๊ฐ ๋์ ๋์ด ๊ฒฐ๊ตญ ํ๋์ Message ๊ฐ์ฒด๋ก materialize๋๋ค.
system / developer์ ์๋ฏธ
๋ role ๋ชจ๋ ์ฌ์ฉ์ UI์ ๋ ธ์ถ๋์ง ์์ผ๋ฉฐ, LLM ์ ๋ ฅ์ฉ ์ง์์ฌํญ์ด๋ค. OpenAI๊ฐ 2024๋ ๋ง ๋์ ํ instruction hierarchy ๊ฐ๋ ์ด ๊ทธ๋๋ก ๋ฐ์๋ ์ค๊ณ๋ก, ์ฐ์ ์์๋ ๋ค์๊ณผ ๊ฐ๋ค.
์ฐ์ ์์ (๋์ โ ๋ฎ์)
1. system โ ํ๋ซํผ/OS ๋ ๋ฒจ ๊ท์น (ํญ์ ์ ์ฉ๋๋ ์์ ์ฅ์น)
2. developer โ ์ฑ ๊ฐ๋ฐ์๊ฐ ์ถ๊ฐํ๋ ์ง์ (์ธ์
/์๋๋ฆฌ์ค๋ณ)
3. user โ ์ต์ข
์ฌ์ฉ์ ์
๋ ฅ
utils.ts:207์์ ๋ role์ ๋์ผํ๊ฒ instruction ์์ญ์ผ๋ก ์ฒ๋ฆฌํ๋ค.
โ๋๋ฒ๊ทธ ๋ก๊ทธโ๋ โํด์ด ๋๋ฌ์์ UI์ ํ์โ ๊ฐ์ ์๋๋ฆฌ์ค๋ developer๊ฐ ์๋๋ผ activity ๋ฉ์์ง ๋๋ STEP_FINISHED ์ด๋ฒคํธ์ ์์ญ์ด๋ค.
UserMessage์ ๋ฉํฐ๋ชจ๋ฌ ๊ตฌ์กฐ
export const UserMessageSchema = BaseMessageSchema.extend({
role: z.literal("user"),
content: z.union([z.string(), z.array(InputContentSchema)]),
});content๊ฐ string | InputContent[] ์ ๋์จ์ธ ๊ฒ์ด ํต์ฌ. ํ
์คํธ๋ง ๋ณด๋ด๋ ๋ ๊ฑฐ์ ๊ฒฝ๋ก์ ๋ฉํฐ๋ชจ๋ฌ ๊ฒฝ๋ก๋ฅผ ํ๋์ ๋ฉ์์ง ๊ตฌ์กฐ๋ก ํตํฉํ๋ค.
InputContent 2๋จ ๊ณ์ธต
InputContent (discriminatedUnion by "type")
โโโ TextInputContent { type: "text", text }
โโโ ImageInputContent { type: "image", source, metadata? }
โโโ AudioInputContent { type: "audio", source, metadata? }
โโโ VideoInputContent { type: "video", source, metadata? }
โโโ DocumentInputContent { type: "document", source, metadata? }
InputContentSource (discriminatedUnion by "type")
โโโ { type: "data", value: base64, mimeType } โ ์ธ๋ผ์ธ ๋ฐ์ดํฐ
โโโ { type: "url", value: url, mimeType? } โ ์ธ๋ถ ์ฐธ์กฐ
๋์ฉ๋ ํ์ผ์ URL ์ฐธ์กฐ, ์์ ํ์ผ์ ์ธ๋ผ์ธ base64๋ก ๋ณด๋ด๋ ์ต์ ํ ๋ถ๊ธฐ๋ฅผ ํ๋กํ ์ฝ ๋ ๋ฒจ์์ ์ด์ด๋๋ค.
๋ฉํฐ๋ชจ๋ฌ ์์ฒญ์ด ๋ชจ๋ธ๊น์ง ๋๋ฌํ๋ ์ ์ฒด ํ์ดํ๋ผ์ธ
์ฌ์ฉ์๊ฐ [TextInputContent, VideoInputContent]๋ฅผ ์
๋ก๋ํ ๊ฒฝ์ฐ:
sequenceDiagram autonumber participant U as User participant FE as Frontend participant AD as AG-UI Adapter participant LC as LangChain Runtime participant API as OpenAI API U->>FE: ํ ์คํธ์ ๋น๋์ค ์ ๋ก๋ FE->>AD: AG-UI UserMessage<br/>content - text + video ๋ฐฐ์ด Note over AD: convertAguiMultimodalToLangchain<br/>mediaSourceToUrl AD->>LC: LangChain HumanMessage<br/>content - text + image_url ๋ฐฐ์ด Note over LC: LangChain ๋ด๋ถ์์<br/>OpenAI API ํฌ๋งท์ผ๋ก ์ฌ๋ณํ LC->>API: OpenAI chat.completions<br/>content - text + image_url ๋ฐฐ์ด API-->>LC: streaming response LC-->>AD: LangChain events AD-->>FE: AG-UI Events ํ์คํ FE-->>U: ๋ ๋๋ง
utils.ts:119-151 ์ convertAguiMultimodalToLangchain:
function convertAguiMultimodalToLangchain(content: InputContent[]) {
for (const item of content) {
if (item.type === "text") {
langchainContent.push({ type: "text", text: item.text });
}
else if (MEDIA_CONTENT_TYPES.has(item.type)) {
const url = mediaSourceToUrl(mediaItem.source);
langchainContent.push({
type: "image_url", // LangChain์ ์ ๋ถ image_url๋ก ํตํฉ
image_url: { url },
});
}
}
}ํ๋กํ ์ฝ ๋ ๋ฒจ๊ณผ ์ด๋ํฐ ๋ ๋ฒจ์ ํ์ ๋ณด์กด ์ฐจ์ด
| ๋ ๋ฒจ | ํ์ ๊ตฌ๋ถ | ๋น๊ณ |
|---|---|---|
| AG-UI ํ๋กํ ์ฝ | image/audio/video/document ๊ตฌ๋ถ ๋ณด์กด | ๋ฏธ๋ ๋๋น ์ค๊ณ |
| LangChain ์ด๋ํฐ | ๋ชจ๋ image_url๋ก ๋ญ๊ฐฌ | utils.ts:71-73 ์ฃผ์์ด ๋ช ์ |
| ์ต์ข ๋ชจ๋ธ API | ๋ชจ๋ธ ์ง์ ์ฌ๋ถ์ ๋ฐ๋ผ ๋ค๋ฆ | GPT-4o๋ ์ด๋ฏธ์ง, ๋น๋์ค๋ ์ง์ ์ ํ์ |
AG-UI๊ฐ VideoInputContent, AudioInputContent๋ฅผ ๋ฏธ๋ฆฌ ์ ์ํ ๊ฒ์ ๋ชจ๋ธ/ํ๋ ์์ํฌ๊ฐ ๋ฐ๋ผ์ฌ ๋๋ฅผ ๋๋นํ ํ์ฅ์ฑ ํฌ์์ด๋ค.
Adapter ๋ ์ด์ด โ ํ๋ ์์ํฌ ํฌ๋งท ๋ณํ์ ์ฑ ์ ์ฃผ์ฒด
AG-UI๋ ๋ฒค๋ ์ค๋ฆฝ ํฌ๋งท๊ณผ ์ค์ ํ๋ ์์ํฌ ์ฌ์ด์ ๋ณํ์ ๊ณต์์ ์ผ๋ก ์ ๊ณตํ๋ฉฐ, ๊ทธ ๊ตฌํ์ฒด๊ฐ *Agent ํด๋์ค๋ค์ด๋ค.
ํต์ฌ ์์น: ํ๋ก ํธ์๋๋ ์ค์ง AG-UI ํ๋กํ ์ฝ๋ง ์ฌ์ฉํด ํต์ ํ๊ณ , Adapter๊ฐ ํ๋ ์์ํฌ ํฌ๋งท์ผ๋ก ๋ณํํ๋ ์ญํ ์ ์ ๋ดํ๋ค. ๋๋ถ์ ๋ชจ๋ธ/ํ๋ ์์ํฌ๋ฅผ ๊ต์ฒดํด๋ ํ๋ก ํธ์๋๋ ๋ฌด๋ณ๊ฒฝ์ด๋ค.
โ ๏ธ ํท๊ฐ๋ฆฌ๊ธฐ ์ฌ์ด ์ง์ โ โํ๋ ์์ํฌโ์ โ๋ฒค๋โ๋ ๋ค๋ฅด๋ค
์ด ์น์ ์ ์ฝ์ ๋ ๊ฐ์ฅ ๋ง์ด ํผ๋๋๋ ์ง์ ์ **โAG-UI๊ฐ ๋ฒค๋(OpenAI)์ ์ง์ ๋ฉ์์ง๋ฅผ ๋ณด๋ธ๋คโ**๊ณ ์๊ฐํ๋ ๊ฒ์ด๋ค. ์ค์ ๋ก๋ ์ฌ์ด์ **โํ๋ ์์ํฌโ**๋ผ๋ ์ค๊ฐ ๊ณ์ธต์ด ํ๋ ๋ ์๋ค.
์ฉ์ด ๊ตฌ๋ถ:
| ์ฉ์ด | ์ ์ฒด | ์์ |
|---|---|---|
| AG-UI Protocol | ๋ฒค๋ยทํ๋ ์์ํฌ ์ค๋ฆฝ ํ์ค ํฌ๋งท | types.ts์ MessageSchema |
| ํ๋ ์์ํฌ (Framework) | AI ์ค์ผ์คํธ๋ ์ด์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ (์ค๊ฐ ๊ณ์ธต) | LangChain, LangGraph, Mastra, Vercel AI SDK, LlamaIndex |
| ๋ฒค๋ (Vendor / Provider) | ์ค์ LLM API ์ ๊ณต์ (์ต์ข ๋ชฉ์ ์ง) | OpenAI, Anthropic, AWS Bedrock, Google |
์ ํผ๋๋๋๊ฐ? โ๋ฒค๋์ฌ๋ง๋ค ์ด๋ฒคํธ๊ฐ ๋ค๋ฅด๋คโ๋ ๋ง์ ๋ง์ง๋ง, AG-UI๊ฐ ๊ทธ๊ฑธ ์ง์ ํด๊ฒฐํ์ง๋ ์๋๋ค. ๋ฒค๋ ์ถ์ํ๋ ํ๋ ์์ํฌ๊ฐ ์ด๋ฏธ ํด๋ ์ผ์ด๊ณ , AG-UI๋ ๊ทธ ์์ ํ ์ธต ๋ ์นํ ํ๋ก ํธ์๋์ฉ ์ถ์ํ๋ค.
3๊ณ์ธต ๊ตฌ์กฐ โ ๋ฐ์ดํฐ๊ฐ ๊ฑฐ์น๋ ์ค์ ๊ฒฝ๋ก
flowchart TB L1["Layer 1 - AG-UI Protocol<br/>๋ฒค๋/ํ๋ ์์ํฌ ์ค๋ฆฝ ํ์ค<br/>์์ - role assistant, toolCalls, camelCase"] L2["Layer 2 - Framework Format<br/>LangChain, ai-sdk, Mastra ๋ฑ<br/>์์ - type ai, tool_calls, snake_case"] L3["Layer 3 - Vendor API Format<br/>OpenAI, Anthropic, Bedrock ๋ฑ<br/>์์ - chat.completions JSON"] L1 <==>|"๋ณํ 1<br/>AG-UI Adapter๊ฐ ๋ด๋น"| L2 L2 <==>|"๋ณํ 2<br/>Framework ๋ด๋ถ ๋ด๋น<br/>AG-UI ๋ฌด๊ด"| L3 classDef agui fill:#b3d9ff,stroke:#0066cc,color:#000 classDef framework fill:#ffd9b3,stroke:#cc6600,color:#000 classDef vendor fill:#b3ffb3,stroke:#009900,color:#000 class L1 agui class L2 framework class L3 vendor
AG-UI Message๊ฐ LLM์ ๋๋ฌํ๊ธฐ๊น์ง โ ๋ณํ์ 2ํ ์ผ์ด๋๋ค
ํ ๋ฒ์ ์ฌ์ฉ์ ์์ฒญ์ด LLM๊น์ง ๋๋ฌํ๋ ค๋ฉด ์ด 2๋ฒ์ ํฌ๋งท ๋ณํ์ด ํ์ํ๋ค.
flowchart LR A["AG-UI Message<br/>role assistant<br/>toolCalls array"] B["LangChain Message<br/>type ai<br/>tool_calls array"] C["OpenAI API JSON<br/>role assistant<br/>tool_calls array"] LLM(("LLM")) A ==>|"๋ณํ 1<br/>Adapter<br/>aguiMessagesToLangChain"| B B ==>|"๋ณํ 2<br/>Framework<br/>ChatOpenAI ๋ด๋ถ"| C C ==>|"HTTP ์ ์ก<br/>Framework"| LLM classDef agui fill:#b3d9ff,stroke:#0066cc,color:#000 classDef framework fill:#ffd9b3,stroke:#cc6600,color:#000 classDef vendor fill:#b3ffb3,stroke:#009900,color:#000 class A agui class B framework class C vendor
๋ณํ ํ์ ์์ฝ:
| ๋ณํ | ๋ด๋น์ | AG-UI ์ฑ ์? |
|---|---|---|
| โ AG-UI Message โ ํ๋ ์์ํฌ Message | Adapter (LangGraphAgent ๋ฑ) | โ Yes |
| โก ํ๋ ์์ํฌ Message โ ๋ฒค๋ API JSON | Framework ๋ด๋ถ (ChatOpenAI ๋ฑ) | โ No |
์ค์ ํฌ๋งท ์ฐจ์ด โ ์ฝ๋๋ก ํ์ธ
๊ฐ์ โassistant ๋ฉ์์งโ๊ฐ 3๊ณ์ธต์์ ์ด๋ป๊ฒ ๋ค๋ฅด๊ฒ ์๊ฒผ๋์ง ๋น๊ตํ๋ฉด 3๊ณ์ธต์ ์กด์ฌ๊ฐ ํ์คํ ๋ณด์ธ๋ค.
Layer 1: AG-UI Protocol ํฌ๋งท โ types.ts:127-131
{
id: "msg_1",
role: "assistant", // ์๋ฌธ์ role
content: "๋ต๋ณ",
toolCalls: [ // camelCase
{ id, type: "function",
function: { name, arguments: "{\"city\":\"tokyo\"}" } // arguments๋ JSON ๋ฌธ์์ด
}
]
}Layer 2: Framework (LangChain) ํฌ๋งท โ utils.ts:241-253
{
id,
type: "ai", // "ai" (AG-UI๋ "assistant")
role: "assistant",
content: "๋ต๋ณ",
tool_calls: [ // snake_case
{ id,
name: "weather",
args: { city: "tokyo" }, // args๋ ํ์ฑ๋ ๊ฐ์ฒด
type: "tool_call",
}
]
}Layer 3: Vendor (OpenAI) API ํฌ๋งท
{
"role": "assistant",
"content": "๋ต๋ณ",
"tool_calls": [
{ "id": "call_1",
"type": "function",
"function": {
"name": "weather",
"arguments": "{\"city\":\"tokyo\"}"
}
}
]
}์ฃผ์ ์ฐจ์ด์ ์์ฝ:
| ํ๋ | Layer 1 (AG-UI) | Layer 2 (LangChain) | Layer 3 (OpenAI) |
|---|---|---|---|
| role ํ๊ธฐ | role: "assistant" | type: "ai" | role: "assistant" |
| ํด ๋ฐฐ์ด ์ด๋ฆ | toolCalls | tool_calls | tool_calls |
| ํด ์ธ์ | arguments: "{...}" (๋ฌธ์์ด) | args: {...} (๊ฐ์ฒด) | arguments: "{...}" (๋ฌธ์์ด) |
| ๋ค์ด๋ฐ | camelCase | snake_case | snake_case |
์ฌ๋ฏธ์๋ ์ : Layer 1(AG-UI)๊ณผ Layer 3(OpenAI)์ ๋ฌํ๊ฒ ๋ฎ์์ง๋ง Layer 2(LangChain)๋ ๋ค๋ฅด๋ค. LangChain์ด ์๊ธฐ๋ง์ ๋ ์ ํฌ๋งท์ ์ฐ๊ธฐ ๋๋ฌธ์ด๋ฉฐ, ์ด๊ฒ์ด โํ๋ ์์ํฌ ํฌ๋งทโ์ด๋ผ๋ ์ค๊ฐ ๊ณ์ธต์ด ์ค์ฌํ๋ ์ฆ๊ฑฐ๋ค.
โ ๏ธ ํท๊ฐ๋ฆฌ๊ธฐ ์ฌ์ด ์ง์ โ โ์ ํ๋ ์์ํฌ๋ฅผ ๊ฑฐ์ณ ๊ฐ๋? ๋ฐ๋ก ๋ฒค๋๋ก ๋ณด๋ด๋ฉด ์ ๋๋?โ
์ด๋ก ์ ์ผ๋ก๋ Adapter๊ฐ AG-UI โ OpenAI JSON์ผ๋ก ๋ฐ๋ก ๋ณํํ ์๋ ์๋ค. ํ์ง๋ง ๊ทธ๋ฌ์ง ์๋ ์ด์ :
| ๋ฌธ์ | ํ๋ ์์ํฌ ๊ฒฝ์ ์ |
|---|---|
| ๋ฒค๋ ์ถ์ํ๋ฅผ ๋๊ฐ ํ๋? | LangChain์ด ์ด๋ฏธ 20+ ๋ฒค๋ ์ง์ โ ๊ณต์ง๋ก ์์ |
| ์ LLM ์ถ๊ฐ๋๋ฉด? | LangChain์ด ์ง์ํ๊ธฐ๋ง ํ๋ฉด AG-UI Adapter ์์ ๋ถํ์ |
| ๋ณต์กํ ๊ธฐ๋ฅ(RAG, ๋ฉํฐ์คํ )? | ํ๋ ์์ํฌ๊ฐ ์ด๋ฏธ ๊ตฌํ โ AG-UI๊ฐ ์ฌ๋ฐ๋ช ์ ํด๋ ๋จ |
| ์ฝ๋ ๋จ์์ฑ | Adapter๋ โLangChain๊ณผ๋งโ ๋ํํ๋ฉด ๋จ |
์ฆ AG-UI๊ฐ โํ๋ ์์ํฌ ๋ ๋ฒจโ์์ ๋ฉ์ถ๋ ๊ฑด ์ค๊ณ์ ํํ์ด ์๋๋ผ ์ ๋ต์ ์ ํ์ด๋ค. ์ด๋ฏธ LangChain/ai-sdk/Mastra๊ฐ ๋ฒค๋ ์ถ์ํ๋ฅผ ์์ฑํด๋๊ธฐ ๋๋ฌธ์, AG-UI๋ ๊ทธ ์ถ์ํ๋ฅผ ํ๋ก ํธ์๋๊น์ง ์ฐ์ฅํ๋ ์ญํ ๋ง ํ๋ฉด ๋๋ค.
์ ์ฒด ํต์ ํ๋ฆ (Adapter๊ฐ ํฌ๋งท ๋ณํ์ ์ ๋ด)
sequenceDiagram autonumber participant FE as Frontend participant AD as AG-UI Adapter participant FW as Framework Runtime participant LLM as LLM API rect rgba(100, 180, 255, 0.15) Note over FE,AD: AG-UI ํ๋กํ ์ฝ ๊ตฌ๊ฐ<br/>ํ๋ก ํธ์๋๋ ์ด ํฌ๋งท๋ง ์๋ฉด ๋จ FE->>AD: AG-UI Message - role, content, toolCalls end rect rgba(255, 180, 100, 0.15) Note over AD: Adapter๊ฐ ํฌ๋งท ๋ณํ ์ ๋ด<br/>aguiMessagesToLangChain AD->>AD: AG-UI to Framework Native<br/>ํฌ๋งท ๋ณํ end rect rgba(180, 255, 180, 0.15) Note over AD,LLM: ํ๋ ์์ํฌ/๋ชจ๋ธ ๊ตฌ๊ฐ<br/>ํ๋ก ํธ์๋๋ ์ ํ์ ์์ AD->>FW: Framework-native Message<br/>LangChain HumanMessage ๋ฑ FW->>LLM: Vendor-specific JSON<br/>OpenAI chat.completions ๋ฑ LLM-->>FW: streaming chunks FW-->>AD: framework events end rect rgba(255, 180, 100, 0.15) Note over AD: Adapter๊ฐ ์ญ๋ณํ ์ ๋ด<br/>framework event to AG-UI Event AD->>AD: Framework Native to AG-UI<br/>ํฌ๋งท ์ญ๋ณํ end rect rgba(100, 180, 255, 0.15) AD-->>FE: AG-UI Events<br/>TEXT_MESSAGE_*, TOOL_CALL_* ๋ฑ Note over FE,AD: AG-UI ํ๋กํ ์ฝ ๊ตฌ๊ฐ end
๊ตฌ๊ฐ๋ณ ์ฑ ์ ๋ถ๋ฆฌ:
| ๊ตฌ๊ฐ | ํฌ๋งท | ์ฑ ์ ์ฃผ์ฒด |
|---|---|---|
| Frontend โ Adapter | AG-UI Message / Events (ํ์ค) | AG-UI Protocol |
| Adapter ๋ด๋ถ | AG-UI โ Framework ๋ณํ | Adapter (LangGraphAgent ๋ฑ) |
| Adapter โ Framework Runtime | LangChain/ai-sdk ๋ค์ดํฐ๋ธ ํฌ๋งท | Framework |
| Framework โ LLM API | OpenAI/Anthropic JSON | Framework ๋ด๋ถ |
์ฆ LLM ๊ต์ฒด(OpenAI โ Claude) ์ Framework ๋ด๋ถ์์๋ง ๋ณ๊ฒฝ์ด ์ผ์ด๋๊ณ , ํ๋ ์์ํฌ ๊ต์ฒด(LangChain โ Mastra) ์์๋ Adapter ๊ตฌํ์ฒด๋ง ๋ฐ๊พธ๋ฉด ํ๋ก ํธ์๋๋ ๋ฌด๋ณ๊ฒฝ์ด๋ค.
Adapter์ ๋ ๊ฐ์ง ๋ฒ ์ด์ค ํด๋์ค
โ HttpAgent ์์ โ ๋จ์ ์ค๊ณ (๋ฐฑ์๋๊ฐ ์ด๋ฏธ AG-UI ํฌ๋งท์ผ๋ก ์๋ต):
// integrations/crew-ai/typescript/src/index.ts
export class CrewAIAgent extends HttpAgent {}
export class AgnoAgent extends HttpAgent { ... }
export class PydanticAIAgent extends HttpAgent { ... }โก AbstractAgent ์์ โ ํ ์ปค์คํ
(๋ค์ดํฐ๋ธ ํฌ๋งท ์๋ฐฉํฅ ๋ณํ ํ์):
// integrations/langgraph/typescript/src/agent.ts:124
export class LangGraphAgent extends AbstractAgent {
run(input: RunAgentInput): Observable<ProcessedEvents> {
// input.messages (AG-UI) โ LangChain messages ๋ณํ
// LangChain ์คํ
// LangChain ์ด๋ฒคํธ โ AG-UI Events ์ญ๋ณํ ์คํธ๋ฆฌ๋ฐ
}
}์ฑ ์ ๋ฒ์์ ๊ฒฝ๊ณ
๋ฐ์ดํฐ๊ฐ 3๋ฒ ๋ชจ์์ ๋ฐ๊พธ๋ฉฐ LLM๊น์ง ๋๋ฌํ๋๋ฐ, ๊ฐ ๋ณํ ๋จ๊ณ๋ฅผ ๋๊ฐ ์ฑ ์์ง๋์ง๊ฐ ํต์ฌ์ด๋ค.
flowchart LR A["AG-UI Message"] B["LangChain Message"] C["OpenAI API JSON"] A -->|"๋ณํ 1 - LangGraphAgent<br/>AG-UI Adapter"| B B -->|"๋ณํ 2 - LangChain ChatOpenAI<br/>ํ๋ ์์ํฌ ๋ด๋ถ"| C classDef format fill:#f5f5f5,stroke:#666,color:#000 class A,B,C format
AG-UI๊ฐ ์ฑ ์์ง๋ ๊ฒ / ์๋ ๊ฒ:
flowchart LR subgraph AGUI_SCOPE["AG-UI Adapter ์ฑ ์ ๋ฒ์"] direction LR A2["AG-UI Message"] ==>|"๋ณํ 1"| B2["LangChain Message"] end subgraph FW_SCOPE["Framework ๋ด๋ถ ์ฑ ์ - AG-UI ๋ฌด๊ด"] direction LR B3["LangChain Message"] ==>|"๋ณํ 2"| C2["OpenAI API JSON"] end AGUI_SCOPE -.๊ฒฝ๊ณ.-> FW_SCOPE classDef adapter fill:#ffd9b3,stroke:#cc6600,color:#000 classDef framework fill:#b3ffb3,stroke:#009900,color:#000 class A2,B2 adapter class B3,C2 framework
ํ ์ค ์์ฝ: Adapter๋ โAG-UI โ ํ๋ ์์ํฌ ํฌ๋งทโ๊น์ง๋ง ์ฑ ์์ง๋ค. ๊ทธ ์ดํ ํ๋ ์์ํฌ๊ฐ ์ด๋ค LLM API๋ฅผ ์ฐ๋ (OpenAI/Anthropic/Bedrock) ๊ทธ๊ฑด ํ๋ ์์ํฌ์ ๋ชซ์ด์ง AG-UI๊ฐ ๊ด์ฌํ ์ผ์ด ์๋๋ค.
VercelAISDKAgent์ฒ๋ผ ํ๋ ์์ํฌ ์์ฒด๊ฐ ๋ฉํฐ ํ๋ก๋ฐ์ด๋๋ฅผ ํตํฉ ์ง์ํ๋ ๊ฒฝ์ฐ, ๋ณํ โก๊ฐ ํ๋ ์์ํฌ ๋ด๋ถ์ ์ด๋ฏธ ๋
น์ ์์ด Adapter๋ ๋ณํ โ ๋ง ์ ๊ฒฝ ์ฐ๋ฉด ๋๋ค.
๋ณ๋ ฌ Tool Calls์ ์์ ๋ณด์กด
N๊ฐ์ ํด์ด ๋ณ๋ ฌ ์คํ๋ ๋ ์๋ฃ ์์์ ์๊ด์์ด ์์(START) ์์๋ก ๋ฐฐ์ด ์์น๊ฐ ๊ณ ์ ๋๋ค.
ํต์ฌ ์๋ฆฌ: TOOL_CALL_START๊ฐ ์ฌ๋กฏ์ ์ ์
case EventType.TOOL_CALL_START: {
const { toolCallId, toolCallName, parentMessageId } = event;
const targetMessage = resolveOrCreateAssistantMessage(...);
targetMessage.toolCalls ??= [];
// ๋ฐฐ์ด์ push โ ์ด ์์ ์ ์์๊ฐ ๊ฒฐ์ ๋จ
targetMessage.toolCalls.push({
id: toolCallId,
type: "function",
function: { name: toolCallName, arguments: "" },
});
}ํ์๋ผ์ธ ์์
์๊ฐ ์ด๋ฒคํธ toolCalls ๋ฐฐ์ด ์ํ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
t=0 TOOL_CALL_START(id:1) [1(๋น์ด์์)]
t=1 TOOL_CALL_START(id:2) [1, 2(๋น์ด์์)]
t=2 TOOL_CALL_START(id:3) [1, 2, 3(๋น์ด์์)]
t=3 TOOL_CALL_START(id:4) [1, 2, 3, 4(๋น์ด์์)]
t=4 TOOL_CALL_ARGS(id:3, "...") [1, 2, 3(์ฑ์์ง), 4]
t=5 TOOL_CALL_END(id:3) [1, 2, 3(์๋ฃ), 4]
t=10 TOOL_CALL_ARGS(id:1, "...") [1(์ฑ์์ง), 2, 3, 4]
t=11 TOOL_CALL_END(id:1) [1(์๋ฃ), 2, 3, 4]
3๋ฒ์ด ๋จผ์ ์๋ฃ๋์ด๋ ๋ฐฐ์ด ์์น๋ ๊ทธ๋๋ก 3๋ฒ์งธ ์ฌ๋กฏ์ ์งํจ๋ค. TOOL_CALL_ARGS์ TOOL_CALL_END๋ toolCallId๋ก ์๊ธฐ ์ฌ๋กฏ์ ์ฐพ์ ๋ฟ ์์น๋ฅผ ๋ณ๊ฒฝํ์ง ์๋๋ค(default.ts:336).
์ค๊ณ ์๋
- LLM ์๋ต ์์ ๋ณด์กด: LLM์ด ์๋ํ ์์๊ฐ UI์๋ ๋ฐ์๋์ด์ผ ํจ
- ์ฌ์ ๋ ฌ ์ UI ๊น๋นก์ ๋ฐฉ์ง: React ๋ฐฐ์ด ํค ์์ ๋ณ๊ฒฝ์ด ๋ฆฌ๋ ๋ ํญํ ์ ๋ฐ
- toolCallId๋ก ์ ํฉ์ฑ ๋ณด์ฅ: ์์ ์ ์งํด๋ ๊ฒฐ๊ณผ ๋งค์นญ(
ToolMessage.toolCallId)์ id ๊ธฐ๋ฐ์ด๋ฏ๋ก ๋ฌด๊ฒฐ
์์ธ: ๋ง์ ํตํฉ์ด ์์ฐจ ์คํ์ ๊ฐ์
agent.ts:87 ๋ฑ ๋๋ถ๋ถ์ LangGraph ์์ ๋:
parallel_tool_calls: false, // ๋ ์ด์ค ์ปจ๋์
ํํผ๋ก ๊ธฐ๋ณธ๊ฐ์ ์ค์ ํด ์ค์ ๋ก๋ ๋ณ๋ ฌ ์คํ ์์ฒด๊ฐ ์กฐ์ฌ์ค๋ฝ๊ฒ ์ฌ์ฉ๋๋ค.
Tool Call Linking ํจํด
์ธ ๊ฐ์ ID๊ฐ ์๋ก ๋ค๋ฅธ ์ญํ ์ ํ๋ฉฐ ๋ณ๋ ฌ ์คํ ๊ฒฐ๊ณผ๋ฅผ ์ ํํ ๋งค์นญํ๋ค.
flowchart LR subgraph AM["AssistantMessage"] AM_ID["id - msg_2<br/>๋ฉ์์ง ์์ฒด์ ID"] TC_ID["toolCalls 0๋ฒ์งธ id - call_1<br/>ํธ์ถ ์์ฒญ ID"] end subgraph TM["ToolMessage"] TM_ID["id - result_1<br/>๊ฒฐ๊ณผ ๋ฉ์์ง ID"] TC_REF["toolCallId - call_1<br/>์ฐธ์กฐ"] end TC_ID -. "๋งํฌ - ID ๋งค์นญ" .-> TC_REF classDef request fill:#ffe6cc,stroke:#d79b00,color:#000 classDef result fill:#d5e8d4,stroke:#82b366,color:#000 class AM_ID,TC_ID request class TM_ID,TC_REF result
์์ ๋ ๋ฆฝ์ ์ผ๋ก ๋งค์นญ๋๋ฏ๋ก assistant๊ฐ N๊ฐ ํด์ ๋ณ๋ ฌ ๋ฐํํด๋ ๊ฒฐ๊ณผ ์ถฉ๋ ์์ด ์ฒ๋ฆฌ ๊ฐ๋ฅํ๋ค.
encryptedValue โ ZDR/store: false ํ๊ฒฝ์ ์ํ ์บ๋ฆฌ์ด
encryptedValue๋ UI ๋ ๋๋ง์ฉ์ด ์๋๋ผ LLM ์๋ฒ์ ๋ค์ ์
๋ ฅํ๊ธฐ ์ํ ๋ถํฌ๋ช
ํ ํฐ์ด๋ค.
ํ์/์ ์ฅ/์ ๋ฌ์ 3์ถ ๋ถ๋ฆฌ
| ๊ฒฝ๋ก | UI ๋ ธ์ถ | ํด๋ผ์ด์ธํธ ์ ์ฅ | LLM ์ฌ์ ์ก |
|---|---|---|---|
content (ํ๋ฌธ ์์ฝ) | ๋ณด์ | ๊ฐ๋ฅ | ๊ฐ๋ฅ |
encryptedValue (์ํธ๋ฌธ) | ์ ๋ณด์ | ๊ทธ๋๋ก ๋ณด๊ด | ๊ทธ๋๋ก ์๋ณต |
// Client stores only:
// - The encrypted blob (cannot decrypt)
// - The summary text (no sensitive details)
// Full reasoning is never persisted in plaintext
์ ํ์ํ๊ฐ: ์ผ๋ฐ ์ํ vs ZDR ์ ์ฑ
์ผ๋ฐ (์๋ฒ๊ฐ reasoning ์ ์ฅ ๊ฐ๋ฅ):
[1ํด] User: "๋ณต์กํ ๋ฌธ์ "
LLM: reasoning 1000 ํ ํฐ ์์ฑ โ Answer
์๋ฒ ์ ์ฅ โ
[2ํด] User: "์?"
LLM: ์ ์ฅ๋ reasoning ์ฐธ์กฐ โ ๋ต๋ณ
ZDR / store: false (์๋ฒ ์ ์ฅ ๊ธ์ง):
[1ํด] User: "๋ณต์กํ ๋ฌธ์ "
LLM: reasoning โ ์ํธํ(์๊ธฐ ํค) โ encryptedValue
ํด๋ผ์ด์ธํธ์ ์ ์ก (๋ด์ฉ ๋ชจ๋ฆ, ์ ์ฅ๋ง)
์๋ฒ ์ ์ฅ ์ ํจ โ
[2ํด] User: "์?"
ํด๋ผ์ด์ธํธ: encryptedValue ๊ทธ๋๋ก ์ฌ์ ์ก
LLM: ๋ณตํธํ โ ์๋ณธ reasoning ๋ณต์ โ ๋ต๋ณ โ
encryptedValue ์์ด 2ํด์ ๋ณด๋ด๋ฉด LLM์ด reasoning์ ์ฒ์๋ถํฐ ์ฌ๊ณ์ฐ โ ๋น์ฉ 2๋ฐฐ + ๊ฒฐ๊ณผ ๋ถ์ผ์น ๊ฐ๋ฅ.
์ธ ์ฃผ์ฒด์ ์ญํ
- LLM ์๋ฒ: ์ํธํ/๋ณตํธํ ํค ์์ (์ํ๋ ์ ์ฅ ์ ํจ)
- ํด๋ผ์ด์ธํธ: ์ํธ๋ฌธ ๋ณด๊ด๋ง (๋ด์ฉ ์ดํด ๋ถ๊ฐ)
- AG-UI Protocol: ์์ชฝ์ ์ค๊ณ
3๋จ๊ณ ๋ผ์ดํ์ฌ์ดํด
sequenceDiagram autonumber participant A as Agent participant C as Client Note over A,C: Turn 1 A->>C: REASONING_MESSAGE_START - msg-456 A->>C: REASONING_MESSAGE_CONTENT - ์์ฒญ ๋ถ์ ์ค - UI ์์ฝ A->>C: REASONING_MESSAGE_END A->>C: REASONING_ENCRYPTED_VALUE<br/>subtype message<br/>entityId msg-456<br/>encryptedValue eyJhbG... Note over C: UI์๋ ์์ฝ๋ง ๋ ๋<br/>encryptedValue๋ ๊ฐ์ฒด ํ๋์๋ง ์ ์ฅ Note over A,C: Turn 2 C->>A: RunAgentInput.messages์ reasoning ๋ฉ์์ง ํฌํจ<br/>id msg-456, role reasoning,<br/>content ์์ฝ, encryptedValue eyJhbG... Note over A: encryptedValue ๋ณตํธํํ์ฌ<br/>์๋ณธ CoT ๋ณต์ A->>C: ๋งฅ๋ฝ ์ด์ด์ ์๋ต
default.ts:1092-1100 ์ ํด๋ผ์ด์ธํธ ์ธก ์ฒ๋ฆฌ:
// subtype is "message"
const message = messages.find((m) => m.id === entityId);
if (message?.role !== "activity" && message) {
message.encryptedValue = encryptedValue; // ๊ฐ์ฒด ํ๋์๋ง ์ ์ฅ, ๋ ๋ ์ ํจ
}์ธ์ ์ค์ ๋ก ์จ์ผ ํ๋๊ฐ
| ์ํฉ | ํ์? |
|---|---|
| ์ผ๋ฐ ์ฑ๋ด | ๋ถํ์ |
OpenAI store: true (๊ธฐ๋ณธ๊ฐ) | ๋ถํ์ |
OpenAI store: false + reasoning ๋ชจ๋ธ (o1/o3) | ํ์ |
| ๊ธ์ต/์๋ฃ/๋ฒ๋ฅ ์ํฐํ๋ผ์ด์ฆ | ํ์ |
| ZDR ๊ณ์ฝ ๊ณ ๊ฐ | ํ์ |
์ผ๋ฐ ๊ฐ๋ฐ์๋ ๋๋ถ๋ถ ์ ๊ฒฝ ์ธ ํ์ ์๊ณ , ์ปดํ๋ผ์ด์ธ์ค ํ๊ฒฝ ์ ์ฉ ๊ธฐ๋ฅ์ด๋ค. ๋ค๋ง AG-UI ํ๋กํ ์ฝ ์ค๊ณ์ ํ๋๊ฐ ์์ ๋ ธ์ถ๋์ด ์์ผ๋ฏ๋ก ์กด์ฌ ์ด์ ๋ฅผ ์ดํดํด๋๋ ๊ฒ์ด ์ค์ํ๋ค.
์ ์ฌ ํจํด: Tool Call์๋ encryptedValue
export const ToolCallSchema = z.object({
id: z.string(),
type: z.literal("function"),
function: FunctionCallSchema,
encryptedValue: z.string().optional(), // ํด ์ ํ ์ด์ ๋ฅผ ์ํธํ ๋ณด์กด
});โ์ ์ด ํด์ ์ด ์ธ์๋ก ํธ์ถํ๋์งโ์ ๊ทผ๊ฑฐ๋ฅผ ZDR ํ๊ฒฝ์์๋ ๋ณด์กด ๊ฐ๋ฅ.
ActivityMessage โ ํ๋ก ํธ์๋ ์ ์ฉ Generative UI ์ฌ๋กฏ
export const ActivityMessageSchema = z.object({
id: z.string(),
role: z.literal("activity"),
activityType: z.string(), // ํ๋ก ํธ์๋๊ฐ ์ปดํฌ๋ํธ ์ ํ
content: z.record(z.any()), // ์์ ์คํค๋ง
});์ LLM์ ๋ณด๋ด์ง ์๋๊ฐ
โ๊ฒ์ ์คโฆโ, โ3๋จ๊ณ ์ค 2๋จ๊ณ ์งํโ ๊ฐ์ UI ์ํ๋ฅผ LLM ํ์คํ ๋ฆฌ์ ๋ฃ์ผ๋ฉด:
- ํ ํฐ ๋ญ๋น โ ๋ค์ ํด์ ์ฌ์ ๋ ฅ
- LLM ํผ๋ โ โ์ด JSON์ ๋ญ์ง?โ ํด์ ์๋
default.ts:578-582 ์์ ๋ช ์์ ์ผ๋ก ์๋ฒ ์ ์ก ์ ์ธ:
const isClientOnlyRole = (role: string) =>
role === "activity" || role === "reasoning";Custom UI Schema ์์ 3๊ฐ์ง
ํจํด A: ์ฒดํฌ๋ฆฌ์คํธ (PLAN)
{
id: "activity_plan_1",
role: "activity",
activityType: "PLAN",
content: {
title: "๋ฆฌ์์น ์์
",
tasks: [
{ id: "t1", label: "๋
ผ๋ฌธ ๊ฒ์", status: "pending" },
{ id: "t2", label: "์์ฝ ์์ฑ", status: "pending" },
]
}
}์งํ ์ค ACTIVITY_DELTA๋ก JSON Patch ์
๋ฐ์ดํธ:
{
type: EventType.ACTIVITY_DELTA,
messageId: "activity_plan_1",
activityType: "PLAN",
patch: [
{ op: "replace", path: "/tasks/0/status", value: "done" }
]
}ํจํด B: ๊ฒ์ ์งํ ์ํ (SEARCH)
{
id: "activity_search_1",
role: "activity",
activityType: "SEARCH",
content: {
query: "AG-UI protocol spec",
status: "running",
results: [
{ title: "AG-UI Docs", url: "...", snippet: "..." },
],
metrics: { totalFound: 2, searchTimeMs: 342 }
}
}ํจํด C: ๋ฉํฐ ์คํ ์งํ (SCRAPE)
{
id: "activity_scrape_1",
role: "activity",
activityType: "SCRAPE",
content: {
url: "https://example.com",
currentStep: 2,
totalSteps: 4,
steps: [
{ label: "ํ์ด์ง ๋ก๋", status: "done", duration: 120 },
{ label: "DOM ํ์ฑ", status: "done", duration: 45 },
{ label: "๋ฐ์ดํฐ ์ถ์ถ", status: "running" },
{ label: "์ ์ ๋ฐ ์ ์ฅ", status: "pending" },
]
}
}ํ๋ก ํธ์๋ ๋์คํจ์น ํจํด
function ActivityRenderer({ message }: { message: ActivityMessage }) {
switch (message.activityType) {
case "PLAN": return <PlanActivity content={message.content} />;
case "SEARCH": return <SearchActivity content={message.content} />;
case "SCRAPE": return <ScrapeActivity content={message.content} />;
default: return <GenericActivity content={message.content} />;
}
}Record<string, any>์ ์์ ๋๋ activityType ๋จ์๋ก ํ์ด ์์จ์ ์ผ๋ก ์คํค๋ง๋ฅผ ์ค๊ณํ๊ณ , JSON Patch๋ก ์ฆ๋ถ ์
๋ฐ์ดํธ๋ฅผ ๋ฐ๋ ํ๋กํ ์ฝ์ ๋ฌถ์ด์ง ์์ ํ์ฅ์ ์ด๋ค.
Generative UI โ JSON/HTML ์ง์ ์ ๋ฌ
AG-UI๋ โ๋ ๋๋ง ์คํโ์ด ์๋๋ผ **โ์์ก ๊ฒฝ๋กโ**๋ค. JSON์ ๊ทธ๋๋ก ๋๋ฅด๋ ๊ฒ์ด ๊ธฐ๋ณธ ๊ธฐ๋ฅ์ด๋ฉฐ, generative-ui-specs.mdx:18-20:
AG-UI is not a generative UI specification โ itโs a User Interaction protocolโฆ AG-UI natively supports all of the above generative UI specs and allows developers to define their own custom generative UI standards as well.
4๊ฐ์ง ์ ๋ฌ ๋ฐฉ์ ๋น๊ต
| ๋ฐฉ๋ฒ | ๋ฌด์์ค ์ ๋ฌ | ๋ณด์ | ์ ์ฐ์ฑ | ์ฌ์ฉ ํจํด |
|---|---|---|---|---|
| ActivityMessage + JSON | ๊ฐ๋ฅ | ์์ (ํ๋ก ํธ๊ฐ ๋ ๋) | ์ปดํฌ๋ํธ ํ์ | ๊ถ์ฅ ํ์ค |
| Tool Call ๊ธฐ๋ฐ | ๊ฐ๋ฅ | ์์ | LLM์ด ํธ์ถํด์ผ | tool-based generative UI |
| Raw HTML | ๊ฐ๋ฅ | XSS ์ํ | ์์ ์์ | ๋น๊ถ์ฅ |
| MCP-UI (iframe) | ๊ฐ๋ฅ | ์๋๋ฐ์ค | ์นํ์ด์ง๊ธ | Microsoft+Shopify ์คํ |
๊ณต์ Generative UI ์คํ
generative-ui-specs.mdx:12-16:
| ์คํ | ์ ์ง๋ณด์ | ํน์ง |
|---|---|---|
| A2UI | JSONL ๊ธฐ๋ฐ ์คํธ๋ฆฌ๋ฐ, ํ๋ซํผ ๋ฌด๊ด | |
| Open-JSON-UI | OpenAI | ๋ด๋ถ declarative UI ์คํค๋ง ํ์คํ |
| MCP-UI | Microsoft + Shopify | iframe ๊ธฐ๋ฐ ์์ ๊ฐ๋ฐฉ ์คํ |
AG-UI๋ ์ด ๋ชจ๋ ์คํ์ ๋ค์ดํฐ๋ธ ์ง์ํ๋ฉฐ, ActivityMessage.content์ Record<string, any>๊ฐ ๊ฐ ์คํ์ JSON์ ๊ทธ๋๋ก ์ค์ด ๋๋ฅธ๋ค.
Tool-based Generative UI ์์
const systemMessage = new SystemMessage({
content: 'Help the user with writing Haikus. If the user asks for a haiku,
use the generate_haiku tool to display the haiku to the user.'
});LLM์ด generate_haiku({title, lines}) ํด ํธ์ถ โ ์ค์ ์คํ ์์ โ ํด ํธ์ถ ์์ฒด๊ฐ UI ์ง์. ํ๋ก ํธ์๋๊ฐ tool call์ arguments๋ฅผ ๋ฐ์ ์ปดํฌ๋ํธ๋ก ๋ ๋๋ง.
Raw HTML ์ ๋ฌ ์ ์ฃผ์
// ๋ณด์ ์ฃผ์: dangerouslySetInnerHTML ํจํด
yield {
type: "ACTIVITY_SNAPSHOT",
messageId: "html-1",
activityType: "RAW_HTML",
content: { html: "<div class='card'>...</div>" }
}LLM์ด ์์ฑํ HTML์ ๊ทธ๋๋ก ๋ ๋ํ๋ฉด <script> ์ฃผ์
๊ฐ๋ฅ. ์ค๋ฌด์์๋ JSON ์คํค๋ง๋ฅผ ์ฐ๊ณ ํ๋ก ํธ์๋๊ฐ ์์ ํ๊ฒ ์ปดํฌ๋ํธ๋ก ๋ ๋ํ๋ ๊ฒ์ด ํ์ค์ด๋ฉฐ, ์นํ์ด์ง๊ธ ์์ ๋๊ฐ ํ์ํ๋ฉด MCP-UI์ iframe ์๋๋ฐ์ค๋ฅผ ์ด๋ค.
์ ์ฒด ์ํ์ค โ ํด ํธ์ถ ํฌํจ end-to-end ํ๋ฆ
์ฌ์ฉ์๊ฐ โ๋์ฟ ๋ ์จ ์๋ ค์คโ๋ฅผ ๋ฌผ์์ ๋์ ์ ๊ตฌ๊ฐ ํ๋ฆ.
sequenceDiagram autonumber participant U as User participant FE as Frontend participant AD as AG-UI Adapter participant LG as LangGraph participant API as OpenAI API U->>FE: ๋์ฟ ๋ ์จ ์๋ ค์ค FE->>AD: POST /run<br/>AG-UI UserMessage ํฌํจ Note over AD: aguiMessagesToLangChain ๋ณํ AD->>LG: graph.stream with LangChain messages LG->>API: OpenAI chat.completions - LC ๋ด๋ถ ๋ณํ API-->>LG: streaming - choice.delta.tool_calls LG-->>AD: LangGraph events Note over AD: LangGraph event๋ฅผ AG-UI Event๋ก ๋ณํ AD-->>FE: SSE - RUN_STARTED AD-->>FE: SSE - TEXT_MESSAGE_START AD-->>FE: SSE - TOOL_CALL_START - weather AD-->>FE: SSE - TOOL_CALL_ARGS - city tokyo AD-->>FE: SSE - TOOL_CALL_END FE->>FE: ํ๋ก ํธ์๋ ํด ์คํ<br/>๋๋ ์๋ฒ ์ฌํธ์ถ FE->>AD: ํด ๊ฒฐ๊ณผ ํฌํจ ์ฌ์์ฒญ AD->>LG: ํด ๊ฒฐ๊ณผ์ ๋์ ๋ฉ์์ง LG->>API: 2์ฐจ ์ถ๋ก API-->>LG: ์ต์ข ์๋ต LG-->>AD: streaming AD-->>FE: SSE - TEXT_MESSAGE_CONTENT<br/>delta - ์ค๋ ๋์ฟ๋ AD-->>FE: SSE - TEXT_MESSAGE_END AD-->>FE: SSE - RUN_FINISHED FE->>U: ์ค๋ ๋์ฟ๋ 15๋
๋๊ธฐํ ๋ฉ์ปค๋์ฆ โ Snapshot vs Streaming
MESSAGES_SNAPSHOT (์์ ๊ต์ฒด)
์ ์ฒด ๋ํ ๋ฐฐ์ด์ ํ ๋ฒ์ ๊ต์ฒด. ์ด๊ธฐํ / ์ฌ์ฐ๊ฒฐ / ๋๊ท๋ชจ ๋ณ๊ฒฝ ์ ์ฌ์ฉ.
default.ts:551-596 ์ ๋ณํฉ ๋ก์ง์ด ์ฃผ๋ชฉํ ๋งํจ:
// activity / reasoning ๋ฉ์์ง๋ ์๋ฒ ์ค๋
์ท์ ์์ด๋ ์ ์ง
const isClientOnlyRole = (role: string) =>
role === "activity" || role === "reasoning";
messages = messages
.filter((m) => isClientOnlyRole(m.role) || snapshotMap.has(m.id))
.map((m) => (isClientOnlyRole(m.role) ? m : snapshotMap.get(m.id)!));ํด๋ผ์ด์ธํธ ์ ์ฉ role์ ์ค๋ ์ท์ด ๋ฎ์ด์ฐ์ง ์๋๋ค โ ํ๋ก ํธ์๋ ์ํ ๋ณด์กด์ ์ํ ์ค๊ณ.
Streaming (์ ์ง ๊ตฌ์ถ)
TEXT_MESSAGE_START โ { messageId, role }
TEXT_MESSAGE_CONTENT โ { messageId, delta } (Nํ)
TEXT_MESSAGE_END โ { messageId }
TOOL_CALL_START โ TOOL_CALL_ARGS (JSON ์กฐ๊ฐ๋ค) โ TOOL_CALL_END
์ฃผ์: TOOL_CALL_ARGS.delta๋ JSON ๋ฌธ์์ด์ ์กฐ๊ฐ์ด๋ผ ์ค๊ฐ ํ์ฑ ์ ๊นจ์ง๋ค. END๊น์ง ์๊ณ JSON.parse๊ฐ ์์น์ด๋ฉฐ default.ts:405-410 ๊ตฌํ๋ ๊ทธ๋ ๊ฒ ๋์ด ์๋ค.
๋ฒค๋ ์ค๋ฆฝ์ฑ์ ๊ตฌ์ฒด์ ์ด๋
flowchart LR CLIENT["Client"] PROTO["AG-UI<br/>(ํ์ค ํฌ๋งท)"] ADAPTER["Adapter"] F1["OpenAI format"] F2["Anthropic format"] F3["Custom format"] M1(("OpenAI<br/>๋ชจ๋ธ")) M2(("Claude<br/>๋ชจ๋ธ")) M3(("Custom<br/>๋ชจ๋ธ")) CLIENT <--> PROTO PROTO <--> ADAPTER ADAPTER <--> F1 ADAPTER <--> F2 ADAPTER <--> F3 F1 <--> M1 F2 <--> M2 F3 <--> M3 classDef agui fill:#b3d9ff,stroke:#0066cc,color:#000 classDef adapter fill:#ffd9b3,stroke:#cc6600,color:#000 classDef vendor fill:#e1d5e7,stroke:#9673a6,color:#000 classDef model fill:#f8cecc,stroke:#b85450,color:#000 class CLIENT,PROTO agui class ADAPTER adapter class F1,F2,F3 vendor class M1,M2,M3 model
ํด๋ผ์ด์ธํธ๋ AG-UI ํฌ๋งท๋ง ์๋ฉด ๋๋ฏ๋ก ๋ชจ๋ธ ์ค์์นญ์ด ์ด๋ํฐ ๊ต์ฒด๋ก ๋๋๋ค. MCP/A2A์ ๊ตฌ๋ถ๋๋ AG-UI๋ง์ ํฌ์ง์ ์ด ์ฌ๊ธฐ์ ๊ตฌ์ฒด์ ์ผ๋ก ๊ตฌํ๋์ด ์๋ค.
์ฒดํฌ ํฌ์ธํธ
AssistantMessage.content๊ฐ optional์ธ ์ด์ โ toolCalls๋ง์ผ๋ก๋ ์๋ฏธ๊ฐ ์ฑ๋ฆฝToolMessage์nameํ๋๊ฐ ์๋ ์ด์ โ BaseMessage๋ฅผ extend ์ ํ๊ณ ๋ ๋ฆฝ ์คํค๋ง. role+toolCallId๋ก ์ถฉ๋ถActivityMessage.content๊ฐRecord<string, any>์ธ ์ด์ โ ์ปค์คํ UI ์คํค๋ง๋ฅผ ์์ ๋กญ๊ฒ ์์ฉ- Streaming ์ค ์ฐ๊ฒฐ ๋๊น ๋ณต๊ตฌ โ ์ฌ์ฐ๊ฒฐ ํ
MESSAGES_SNAPSHOT์ผ๋ก ๋๊ธฐํ encryptedValue์๋ช โ ๋ค์ ํด ์์ฒญ ์ ์๋ณธ ๊ทธ๋๋ก ์ฌ์ ์ก, ํด๋ผ์ด์ธํธ๋ ๋ด์ฉ ๋ฌด๊ด- ๋ฌธ์ โ ์ฝ๋ ๋ค์ด๋ฐ ๋ถ์ผ์น โ
encryptedContent(๋ฌธ์)๋ ์ค์ ๋กencryptedValue(์ฝ๋). ์ฝ๋๊ฐ ์ ๋ต
์ฐธ๊ณ ๋ฌธ์
- AG-UI Messages ๊ณต์ ๋ฌธ์
- AG-UI Reasoning (encryptedValue ์์ธ)
- AG-UI Generative UI Specs
- types.ts โ Zod ์คํค๋ง ์ ์
- default.ts โ ํด๋ผ์ด์ธํธ ์ด๋ฒคํธ ์ ์ฉ ๋ก์ง
- utils.ts โ ๋ฉํฐ๋ชจ๋ฌ ๋ณํ ๋ ํผ๋ฐ์ค
- agent.ts โ Adapter ๊ตฌํ ๋ ํผ๋ฐ์ค