• AG-UI State Management๋Š” Agent์™€ Frontend ๊ฐ„ ์–‘๋ฐฉํ–ฅ ๊ตฌ์กฐํ™” ๋ฐ์ดํ„ฐ ๋™๊ธฐํ™” ๋ฉ”์ปค๋‹ˆ์ฆ˜
  • JSON ๊ฐ์ฒด ๊ธฐ๋ฐ˜์˜ ๊ณต์œ  ์ƒํƒœ(shared state) ์ฑ„๋„
  • STATE_SNAPSHOT, STATE_DELTA ์ด๋ฒคํŠธ๋กœ ์ „์†ก๋˜๋Š” ํ”„๋กœํ† ์ฝœ ๊ทœ๊ฒฉ
  • ํ”„๋ ˆ์ž„์›Œํฌ/ํŠธ๋žœ์ŠคํฌํŠธ์— ๋…๋ฆฝ์ ์ธ ์ „์†ก ๊ณ„์•ฝ

ํ•ด๋‹น ๊ฐœ๋…์ด ํ•„์š”ํ•œ ์ด์œ 

  • ์ฑ„ํŒ… ํ…์ŠคํŠธ(TEXT_MESSAGE_*)๋งŒ์œผ๋กœ๋Š” ๊ตฌ์กฐํ™”๋œ UI ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋ Œ๋”๋งํ•˜๊ธฐ ์–ด๋ ต๋‹ค
  • Agent๊ฐ€ ์ œ์•ˆํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ์‚ฌ์šฉ์ž๊ฐ€ ์ˆ˜์ •ํ•ด์„œ ๋‹ค์‹œ Agent์—๊ฒŒ ๋Œ๋ ค๋ณด๋‚ด๋Š” ์–‘๋ฐฉํ–ฅ ์ฑ„๋„์ด ํ•„์š”ํ•˜๋‹ค
  • ์ง„ํ–‰ ์ค‘ ์ƒํƒœ(thinking, partial output ๋“ฑ)๋ฅผ ์‹ค์‹œ๊ฐ„ ๊ด€์ฐฐํ•  ์ˆ˜ ์žˆ์–ด์•ผ ํ•œ๋‹ค
  • Human-in-the-Loop ์›Œํฌํ”Œ๋กœ์šฐ(์Šน์ธ/๊ฑฐ์ ˆ/์ˆ˜์ •)์˜ ํ•ต์‹ฌ ์ „์ œ ์กฐ๊ฑด์ด๋‹ค

AS-IS

๋Œ€ํ™” ํ…์ŠคํŠธ ํ•˜๋‚˜์— ๋ชจ๋“  ์ •๋ณด๋ฅผ ๋‹ด์•˜์„ ๋•Œ:

sequenceDiagram
    autonumber
    participant U as User
    participant F as Frontend
    participant A as Agent
    U->>F: ์ด๋ฉ”์ผ ๋ณด๋‚ด์ค˜
    F->>A: ๋ฉ”์‹œ์ง€ ์ „์†ก
    A->>F: TEXT_MESSAGE ์•ˆ๋‚ด ๋ฌธ๊ตฌ๋งŒ ์ „๋‹ฌ
    Note over F: ํ…์ŠคํŠธ๋งŒ ๋ฐ›์Œ. ๋ฒ„ํŠผ UI ํŒŒ์‹ฑ ์–ด๋ ค์›€. ์žฌ์ „์†ก ํ”Œ๋กœ์šฐ ์—†์Œ
    F->>U: ๊ทธ๋ƒฅ ํ…์ŠคํŠธ ํ‘œ์‹œ

TO-BE

๊ตฌ์กฐํ™”๋œ state๋กœ ์ฃผ๊ณ ๋ฐ›์„ ๋•Œ:

sequenceDiagram
    autonumber
    participant U as User
    participant F as Frontend
    participant A as Agent
    U->>F: ์ด๋ฉ”์ผ ๋ณด๋‚ด์ค˜
    F->>A: RunAgentInput with initial state
    A->>F: STATE_SNAPSHOT proposal ๊ฐ์ฒด ์ „์†ก
    F->>U: EmailProposalCard ๋ Œ๋”๋ง
    U->>F: ๋‚ด์šฉ ์ˆ˜์ • ํ›„ ์Šน์ธ
    F->>A: setState๋กœ ์Šน์ธ ์ƒํƒœ ์ „์†ก
    A->>F: TOOL_CALL send_email ์ตœ์ข… ์‹คํ–‰

State vs TEXT_MESSAGE์˜ ์—ญํ•  ๊ตฌ๋ถ„

์ด๋ฒคํŠธ ์ข…๋ฅ˜๋ฅผ ์„ ํƒํ•  ๋•Œ์˜ ๊ธฐ์ค€์€ โ€œ์‚ฌ์šฉ์ž๊ฐ€ ๊ทธ ์ •๋ณด์— ๋ฐ˜์‘ํ•  ์ˆ˜ ์žˆ๋Š”๊ฐ€โ€์ด๋‹ค.

TEXT_MESSAGE_*๋Š” ๋Œ€ํ™” ๋กœ๊ทธ์˜ ์ผ๋ถ€๋กœ, ์ถ”๊ฐ€๋งŒ ๊ฐ€๋Šฅํ•œ(append-only) ํ…์ŠคํŠธ ์ŠคํŠธ๋ฆผ์ด๋‹ค. ์‚ฌ์šฉ์ž๋Š” ์ฝ๊ธฐ๋งŒ ํ•˜๊ณ  ๊ทธ ๋‚ด์šฉ์„ ์ง์ ‘ ์ˆ˜์ •ํ•˜์ง€ ์•Š๋Š”๋‹ค.

STATE_SNAPSHOT / STATE_DELTA๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ํ˜„์žฌ ์ƒํƒœ๋ฅผ ํ‘œํ˜„ํ•˜๋Š” ๊ตฌ์กฐํ™”๋œ ๋ฐ์ดํ„ฐ๋‹ค. ์ปค์Šคํ…€ UI ์ปดํฌ๋„ŒํŠธ๋กœ ๋ Œ๋”๋ง๋˜๋ฉฐ, ์‚ฌ์šฉ์ž๊ฐ€ setState๋กœ ์ˆ˜์ •ํ•˜๋ฉด ๊ทธ ๋ณ€๊ฒฝ์ด Agent ์ชฝ์œผ๋กœ ๋‹ค์‹œ ์ „ํŒŒ๋œ๋‹ค.

์ด๋ฉ”์ผ ๋ฐœ์†ก ์ œ์•ˆ์„ ์˜ˆ๋กœ ๋“ค๋ฉด, "client@example.com์—๊ฒŒ ์ด๋ฉ”์ผ์„ ๋ณด๋‚ผ๊นŒ์š”?"๋ผ๋Š” ์•ˆ๋‚ด ๋ฌธ๊ตฌ๋Š” TEXT_MESSAGE๋กœ ๋ณด๋‚ด๋˜, ์‹ค์ œ ์ˆ˜์ • ๊ฐ€๋Šฅํ•œ ์ด๋ฉ”์ผ ์ดˆ์•ˆ ๊ฐ์ฒด({recipient, subject, body})๋Š” STATE๋กœ ๋ณด๋‚ด๋Š” ๊ฒƒ์ด ์ž์—ฐ์Šค๋Ÿฝ๋‹ค. UI๋Š” state๋ฅผ ๋ณด๊ณ  ์Šน์ธ/์ˆ˜์ • ํผ์„ ๋งŒ๋“ค๊ณ , ์‚ฌ์šฉ์ž๊ฐ€ body๋ฅผ ์ˆ˜์ •ํ•˜๋ฉด ๊ทธ ๋ณ€๊ฒฝ์ด Agent์—๊ฒŒ ๋Œ์•„๊ฐ„๋‹ค.

๊ตฌ๋ถ„TEXT_MESSAGESTATE_*
๋ณธ์งˆ๋Œ€ํ™” ํ…์ŠคํŠธ๊ตฌ์กฐํ™”๋œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋ฐ์ดํ„ฐ
๋ณ€๊ฒฝ ๊ฐ€๋Šฅ์„ฑAppend-only์–‘๋ฐฉํ–ฅ ์ˆ˜์ •
UI์ฑ„ํŒ… ํ’์„ ์ปค์Šคํ…€ ์ปดํฌ๋„ŒํŠธ
๋ชฉ์ โ€๋งํ•˜๊ธฐ""์ƒํƒœ ํ‘œํ˜„โ€

๋‘ ๊ฐ€์ง€ ๋™๊ธฐํ™” ๋ฐฉ์‹: SNAPSHOT๊ณผ DELTA

AG-UI๋Š” ๋‘ ๊ฐ€์ง€ ๋ณด์™„์  ์ด๋ฒคํŠธ๋กœ state๋ฅผ ๋™๊ธฐํ™”ํ•œ๋‹ค.

STATE_SNAPSHOT ์€ state ์ „์ฒด ๊ฐ์ฒด๋ฅผ ๋‹ด๋Š” ์ด๋ฒคํŠธ๋‹ค. ์ˆ˜์‹  ์ธก์€ ๊ธฐ์กด state๋ฅผ ์™„์ „ํžˆ ๊ต์ฒดํ•œ๋‹ค.

interface StateSnapshotEvent {
  type: EventType.STATE_SNAPSHOT
  snapshot: any   // ์ „์ฒด state ๊ฐ์ฒด
}

STATE_DELTA ๋Š” JSON Patch(RFC 6902) ์—ฐ์‚ฐ ๋ฐฐ์—ด์„ ๋‹ด๋Š” ์ด๋ฒคํŠธ๋‹ค. ์ˆ˜์‹  ์ธก์€ ๊ธฐ์กด state์— ํŒจ์น˜๋ฅผ ์ ์šฉํ•œ๋‹ค.

interface StateDeltaEvent {
  type: EventType.STATE_DELTA
  delta: JsonPatchOperation[]
}
์ถ•SNAPSHOTDELTA
๋ฐ์ดํ„ฐ์ „์ฒด๋ณ€๊ฒฝ๋ถ„๋งŒ
์ ์šฉ ๋ฐฉ์‹๊ต์ฒดํŒจ์น˜ ์ ์šฉ
๋„คํŠธ์›Œํฌ ๋น„์šฉ๋†’์Œ๋‚ฎ์Œ
์ฃผ ์šฉ๋„์ดˆ๊ธฐํ™”, ์žฌ๋™๊ธฐํ™”, ๋Œ€๊ทœ๋ชจ ๋ณ€๊ฒฝ๊ณ ๋นˆ๋„ ์ฆ๋ถ„ ์—…๋ฐ์ดํŠธ

SNAPSHOT์€ ์ตœ์ดˆ์—๋งŒ ์‚ฌ์šฉ๋˜๋Š”๊ฐ€

์ผ๋ฐ˜์  ํ๋ฆ„์€ โ€œ์ตœ์ดˆ SNAPSHOT โ†’ ์ดํ›„ DELTAโ€๊ฐ€ ๋งž์ง€๋งŒ, SNAPSHOT์€ ์ค‘๊ฐ„์—๋„ ํ•„์š”์— ๋”ฐ๋ผ ๋‹ค์‹œ ๋‚˜์˜ฌ ์ˆ˜ ์žˆ๋‹ค. ๊ณต์‹ ๋ฌธ์„œ๋Š” ๋„ค ๊ฐ€์ง€ ๊ฒฝ์šฐ๋ฅผ ๋ช…์‹œํ•œ๋‹ค.

  1. ์ธํ„ฐ๋ž™์…˜ ์‹œ์ž‘ ์‹œ baseline ์ˆ˜๋ฆฝ
  2. ์—ฐ๊ฒฐ ๋Š๊น€ ํ›„ ์žฌ๋™๊ธฐํ™”
  3. ํŒจ์น˜ ์ ์šฉ์— ์‹คํŒจํ•ด ๋ถˆ์ผ์น˜๊ฐ€ ๊ฐ์ง€๋œ ๊ฒฝ์šฐ
  4. ๋Œ€๊ทœ๋ชจ ๋ณ€๊ฒฝ์œผ๋กœ delta๋ณด๋‹ค snapshot์ด ํšจ์œจ์ ์ธ ๊ฒฝ์šฐ

์˜ˆ๋ฅผ ๋“ค์–ด ๋„คํŠธ์›Œํฌ๊ฐ€ ์ž ์‹œ ๋Š๊ฒจ ์ผ๋ถ€ delta๊ฐ€ ์œ ์‹ค๋˜๋ฉด ํด๋ผ์ด์–ธํŠธ์˜ state๊ฐ€ ์„œ๋ฒ„์™€ ์–ด๊ธ‹๋‚œ๋‹ค. ์ด๋•Œ ๋‹ค์Œ delta ์ ์šฉ์ด ์‹คํŒจํ•˜๊ณ , Agent๋Š” ์ƒˆ SNAPSHOT์„ ๋‚ด๋ ค baseline์„ ๋‹ค์‹œ ์žก๋Š”๋‹ค. ์‚ฌ์šฉ์ž๊ฐ€ โ€œ์ฃผ์ œ๋ฅผ ๋ฐ”๊ฟ” ๋‹ค์‹œ ์‹œ์ž‘ํ•˜์žโ€๊ณ  ์š”์ฒญํ•˜๋Š” ๊ฒƒ์ฒ˜๋Ÿผ ๊ธฐ์กด state๊ฐ€ ์ „๋ถ€ ๋ฌด์˜๋ฏธํ•ด์งˆ ๋•Œ๋„ SNAPSHOT์„ ๋‹ค์‹œ ์‚ฌ์šฉํ•œ๋‹ค.

JSON Patch ํ˜•์‹ (RFC 6902)

DELTA ์ด๋ฒคํŠธ๋Š” ํ‘œ์ค€ JSON Patch ์—ฐ์‚ฐ ๋ฐฐ์—ด๋กœ ํ‘œํ˜„๋œ๋‹ค.

interface JsonPatchOperation {
  op: "add" | "remove" | "replace" | "move" | "copy" | "test"
  path: string    // JSON Pointer (RFC 6901)
  value?: any     // add, replace์šฉ
  from?: string   // move, copy์šฉ
}

๋Œ€ํ‘œ ์—ฐ์‚ฐ ์˜ˆ์‹œ:

{ "op": "add", "path": "/user/preferences", "value": { "theme": "dark" } }
{ "op": "replace", "path": "/conversation_state", "value": "paused" }
{ "op": "remove", "path": "/temporary_data" }
{ "op": "move", "path": "/completed_items", "from": "/pending_items/0" }

Path๋Š” / ๊ตฌ๋ถ„์ž๋กœ ๊ฐ์ฒด ๊ฒฝ๋กœ๋ฅผ ๋‚˜ํƒ€๋‚ด๋Š” JSON Pointer์ด๋ฉฐ, ๋ฐฐ์—ด ์ธ๋ฑ์Šค๋„ /0, /1 ์‹์œผ๋กœ ํ‘œํ˜„ํ•œ๋‹ค.

AG-UI ํด๋ผ์ด์–ธํŠธ๋Š” fast-json-patch ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋กœ ์ด ์—ฐ์‚ฐ๋“ค์„ ์›์ž์ ์œผ๋กœ ์ ์šฉํ•˜๊ณ , ์‹คํŒจ ์‹œ์—๋Š” ์กฐ์šฉํžˆ ์—…๋ฐ์ดํŠธ๋ฅผ ํฌ๊ธฐํ•œ ๋’ค ํ•„์š”ํ•˜๋ฉด ์ƒˆ SNAPSHOT์„ ์š”์ฒญํ•œ๋‹ค. ์›๋ณธ ์ฝ”๋“œ๋Š” sdks/typescript/packages/client/src/apply/default.ts์—์„œ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

case EventType.STATE_DELTA: {
  const { delta } = event as StateDeltaEvent;
  try {
    const result = jsonpatch.applyPatch(state, delta, true, false);
    state = result.newDocument;
  } catch (error) {
    console.warn(`Failed to apply state patch: ...`);
  }
}

State ์ „๋‹ฌ์˜ ์ฃผ์ฒด๋ณ„ ์—ญํ• 

โ€œAgent๋Š” ์–ด๋–ป๊ฒŒ state๋ฅผ ์ „๋‹ฌํ•ด์•ผ ํ• ์ง€ ์•„๋Š”๊ฐ€? LLM์— ์ง€์นจ์ด ์žˆ๋Š”๊ฐ€?โ€๋ผ๋Š” ์งˆ๋ฌธ์€ ์ธต์œ„๋ฅผ ์„ž์—ˆ์„ ๋•Œ ์ƒ๊ธฐ๋Š” ํ˜ผ๋™์ด๋‹ค. ์‹ค์ œ๋กœ๋Š” ๋‹ค์„ฏ ์ฃผ์ฒด๊ฐ€ ๊ฐ์ž ๋‹ค๋ฅธ ์ฑ…์ž„์„ ์ง„๋‹ค.

์ฃผ์ฒด๋‹ด๋‹นํ•œ ์ค„ ์š”์•ฝ
FrontendinitialState ์ •์˜, UI ๋ Œ๋”, setState์•”๋ฌต์  ์Šคํ‚ค๋งˆ์˜ ์ถœ๋ฐœ์ 
AG-UI ํ”„๋กœํ† ์ฝœ์ด๋ฒคํŠธ ํƒ€์ž… ์ •์˜, ์ „์†ก ๊ทœ๊ฒฉโ€HOW to transmitโ€๋งŒ ๋‹ด๋‹น
Agent ํ”„๋ ˆ์ž„์›Œํฌ(+Adapter)LLM ์ถœ๋ ฅ์„ state ์ด๋ฒคํŠธ๋กœ ๋ณ€ํ™˜โ€WHEN to emitโ€ ๊ฒฐ์ •
LLMTool Call ๋˜๋Š” Structured Output ์ƒ์„ฑโ€stateโ€๋ผ๋Š” ๊ฐœ๋…์„ ๋ชจ๋ฆ„
๊ฐœ๋ฐœ์ž์‹œ์Šคํ…œ ํ”„๋กฌํ”„ํŠธ + ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง ์ž‘์„ฑ์„ธ ๋ฒˆ์งธ์™€ ๋„ค ๋ฒˆ์งธ๋ฅผ ์—ฐ๊ฒฐ

์ค‘์š”ํ•œ ์ ์€ LLM์ด โ€œstate๋ฅผ ๋ณด๋‚ด์•ผ๊ฒ ๋‹คโ€๊ณ  ๊ฒฐ์ •ํ•˜๋Š” ๊ฒŒ ์•„๋‹ˆ๋ผ๋Š” ๊ฒƒ์ด๋‹ค. LLM์€ ์–ธ์ œ๋‚˜์ฒ˜๋Ÿผ ๋„๊ตฌ๋ฅผ ํ˜ธ์ถœํ•  ๋ฟ์ด๊ณ , ๊ทธ ๋„๊ตฌ ํ˜ธ์ถœ์„ Agent ํ”„๋ ˆ์ž„์›Œํฌ ๋˜๋Š” Adapter ์ฝ”๋“œ๊ฐ€ state ์ด๋ฒคํŠธ๋กœ ๋ณ€ํ™˜ํ•œ๋‹ค. ์ฆ‰, ํ”„๋กฌํ”„ํŠธ์— ์ ํžŒ ์ง€์นจ์€ LLM์—๊ฒŒ โ€œ์ด๋Ÿฐ ๋„๊ตฌ๋ฅผ ์ด๋Ÿฐ ํ˜•์‹์œผ๋กœ ํ˜ธ์ถœํ•ด ๋‹ฌ๋ผโ€๋Š” ์•ˆ๋‚ด์ด์ง€, โ€œstate๋ฅผ ๋ณด๋‚ด ๋‹ฌ๋ผโ€๋Š” ์ง์ ‘ ์ง€์‹œ๊ฐ€ ์•„๋‹ˆ๋‹ค.

Agent ํ”„๋ ˆ์ž„์›Œํฌ๋ณ„ State emit ํŒจํ„ด

๊ฐ™์€ ๋ชฉํ‘œ(state ๋™๊ธฐํ™”)๋ฅผ ํ”„๋ ˆ์ž„์›Œํฌ๋งˆ๋‹ค ๋‹ค๋ฅธ ์ „๋žต์œผ๋กœ ๊ตฌํ˜„ํ•œ๋‹ค. ์ด ๋‹ค์–‘์„ฑ์ด ๋ฐ”๋กœ integrations/ ๋””๋ ‰ํ† ๋ฆฌ๊ฐ€ ์กด์žฌํ•˜๋Š” ์ด์œ ๋‹ค.

ํŒจํ„ด A โ€” ์ž๋™ Tool ์ฃผ์ž… (Claude Agent SDK). Adapter๊ฐ€ ag_ui_update_state๋ผ๋Š” ํŠน์ˆ˜ ๋„๊ตฌ๋ฅผ MCP ์„œ๋ฒ„์— ์ž๋™ ๋“ฑ๋กํ•˜๊ณ , ์‹œ์Šคํ…œ ํ”„๋กฌํ”„ํŠธ์— ์‚ฌ์šฉ ์ง€์นจ์„ ์ž๋™ ์ถ”๊ฐ€ํ•œ๋‹ค. Claude๋Š” ์ด ๋„๊ตฌ๋ฅผ ํ‰๋ฒ”ํ•œ MCP ๋„๊ตฌ๋กœ ์ธ์‹ํ•˜๊ณ  ํ˜ธ์ถœํ•œ๋‹ค.

ํŒจํ„ด B โ€” ์„ ์–ธ์  ๋ฐ˜ํ™˜ (Pydantic AI). ๊ฐœ๋ฐœ์ž๊ฐ€ ์ •์˜ํ•œ ๋„๊ตฌ์˜ ๋ฐ˜ํ™˜ ํƒ€์ž…์ด StateSnapshotEvent์ด๋ฉด ํ”„๋ ˆ์ž„์›Œํฌ๊ฐ€ ์ž๋™์œผ๋กœ AG-UI ์ŠคํŠธ๋ฆผ์— ํ˜๋ ค๋ณด๋‚ธ๋‹ค.

@agent.tool_plain
async def display_recipe(recipe: Recipe) -> StateSnapshotEvent:
    return StateSnapshotEvent(
        type=EventType.STATE_SNAPSHOT,
        snapshot={'recipe': recipe},
    )

ํŒจํ„ด C โ€” ๋ช…์‹œ์  dispatch (LangGraph). ๊ฐœ๋ฐœ์ž๊ฐ€ ๋…ธ๋“œ ์ฝ”๋“œ ์•ˆ์—์„œ adispatch_custom_event("manually_emit_intermediate_state", state, config=config)๋ฅผ ํ˜ธ์ถœํ•˜๋ฉด, Adapter๊ฐ€ ์ด ์ปค์Šคํ…€ ์ด๋ฒคํŠธ๋ฅผ StateSnapshotEvent๋กœ ๋ณ€ํ™˜ํ•œ๋‹ค.

ํŒจํ„ด D โ€” ์ŠคํŠธ๋ฆฌ๋ฐ ๊ฐ€๋กœ์ฑ„๊ธฐ (LangGraph Predictive State). config["metadata"]["predict_state"]๋กœ โ€œ์–ด๋А ๋„๊ตฌ์˜ ์–ด๋А ์ธ์ž๋ฅผ ์–ด๋А state ํ‚ค์— ๋ณต์‚ฌํ• ์ง€โ€๋ฅผ ์„ ์–ธํ•ด๋‘๋ฉด, LLM์ด ๊ทธ ๋„๊ตฌ ํ˜ธ์ถœ์„ ์ŠคํŠธ๋ฆฌ๋ฐํ•˜๋Š” ๋„์ค‘์—๋„ ์‹ค์‹œ๊ฐ„์œผ๋กœ state๊ฐ€ ์—…๋ฐ์ดํŠธ๋œ๋‹ค. โ€œ๋ฌธ์„œ๊ฐ€ ํƒ€์ดํ•‘๋˜๋Š” ๋ชจ์Šตโ€์ด ์‹ค์‹œ๊ฐ„ ๋ Œ๋”๋ง๋˜๋Š” ๋ฐ๋ชจ์˜ ์›๋ฆฌ๋‹ค.

๊ณตํ†ต์ ์€ ๋ชจ๋‘ LLM ์ถœ๋ ฅ(์ฃผ๋กœ ๋„๊ตฌ ํ˜ธ์ถœ)์„ ์ค‘๊ฐ„ ๋ ˆ์ด์–ด๊ฐ€ AG-UI ์ด๋ฒคํŠธ๋กœ ๋ฒˆ์—ญํ•œ๋‹ค๋Š” ๊ฒƒ์ด๋‹ค.

Agent๊ฐ€ state๋ฅผ emitํ•˜๋Š” ์ฝ”๋“œ ํ๋ฆ„

LangGraph ๊ธฐ์ค€์œผ๋กœ โ€œLLM ์ถœ๋ ฅ โ†’ state ์ด๋ฒคํŠธโ€์˜ ์™„์ „ํ•œ ๊ฒฝ๋กœ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

sequenceDiagram
    autonumber
    participant LLM
    participant Node as LangGraph Node
    participant Adapter as AG-UI Adapter
    participant FE as Frontend

    Note over Node: predict_state ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ์„ค์ •
    Node->>LLM: invoke messages
    LLM-->>Node: tool_call chunks ์ŠคํŠธ๋ฆฌ๋ฐ
    Node->>Node: ๋‚ด๋ถ€ state ์—…๋ฐ์ดํŠธ
    Node->>Adapter: adispatch_custom_event ํ˜ธ์ถœ
    Adapter->>Adapter: get_state_snapshot ํ•„ํ„ฐ๋ง
    Adapter-->>FE: StateSnapshotEvent
    FE->>FE: state ๊ต์ฒด ๋ฐ UI ๋ฆฌ๋ Œ๋”

ClaudeAgentAdapter์˜ ์ž๋™ ์ฃผ์ž… ๋ฉ”์ปค๋‹ˆ์ฆ˜

โ€œ๊ฐœ๋ฐœ์ž๊ฐ€ ํ”„๋กฌํ”„ํŠธ์— ag_ui_update_state๋ฅผ ์ง์ ‘ ์“ฐ๋Š” ๊ฒŒ ์•„๋‹ˆ๋ผ๋ฉด, ๊ทธ๊ฑธ ๊ฐ์‹ธ๋Š” ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์žˆ์„ ๊ฒƒ์ด๋‹คโ€๋ผ๋Š” ์ถ”๋ก ์€ ์ •ํ™•ํ•˜๋‹ค. ์‹ค์ œ๋กœ ClaudeAgentAdapter๊ฐ€ ๋‹ค์„ฏ ๊ฐ€์ง€ ์ผ์„ ์ž๋™์œผ๋กœ ์ˆ˜ํ–‰ํ•œ๋‹ค.

1๋‹จ๊ณ„ โ€” ๋„๊ตฌ ๋™์  ์ƒ์„ฑ. state๊ฐ€ ๋“ค์–ด์˜ค๋ฉด create_state_management_tool()์ด @tool ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋กœ ag_ui_update_state๋ฅผ ์ฆ‰์„์—์„œ ๋งŒ๋“ ๋‹ค. ์‹ค์ œ ๊ตฌํ˜„์€ ์Šคํ…์ด๋ฉฐ, ๋‚˜์ค‘์— โ€œState updated successfullyโ€๋ฅผ ๋ฐ˜ํ™˜ํ•  ๋ฟ์ด๋‹ค. ์›๋ณธ์€ ag_ui_claude_sdk/utils.py.

@tool(
    "ag_ui_update_state",
    "Update the shared application state...",
    {"state_updates": dict}   # ์Šคํ‚ค๋งˆ๋Š” ๊ทธ๋ƒฅ dict (๊ตฌ์กฐ ๊ฐ•์ œ ์—†์Œ)
)
async def update_state_tool(args: dict) -> dict:
    return {"content": [{"type": "text", "text": "State updated successfully"}]}

2๋‹จ๊ณ„ โ€” MCP ์„œ๋ฒ„ ๋“ฑ๋ก. ์ƒ์„ฑ๋œ ๋„๊ตฌ๋ฅผ create_sdk_mcp_server()๋กœ ๋™์  MCP ์„œ๋ฒ„์— ๋“ฑ๋กํ•˜๊ณ , ClaudeAgentOptions.mcp_servers์— ์ฃผ์ž…ํ•œ๋‹ค. Claude์—๊ฒŒ๋Š” mcp__ag_ui__ag_ui_update_state๋ผ๋Š” ์ด๋ฆ„์˜ ํ‰๋ฒ”ํ•œ MCP ๋„๊ตฌ๋กœ ๋ณด์ธ๋‹ค.

3๋‹จ๊ณ„ โ€” ์‹œ์Šคํ…œ ํ”„๋กฌํ”„ํŠธ ์ž๋™ append. build_state_context_addendum()์ด ํ˜„์žฌ state๋ฅผ JSON์œผ๋กœ ์ง๋ ฌํ™”ํ•˜๊ณ  ์‚ฌ์šฉ ์ง€์นจ์„ ํฌํ•จํ•œ markdown ๋ธ”๋ก์„ ์ƒ์„ฑํ•ด, ๊ฐœ๋ฐœ์ž์˜ ์›๋ณธ system_prompt ๋’ค์— ์ด์–ด ๋ถ™์ธ๋‹ค.

[๊ฐœ๋ฐœ์ž๊ฐ€ ์ž‘์„ฑํ•œ ์›๋ณธ system_prompt]
 
## Current Shared State
This state is shared with the frontend UI and can be updated.
```json
{ "recipe": { "title": "", "ingredients": [], "instructions": "" } }

4๋‹จ๊ณ„ โ€” ์ดˆ๊ธฐ SNAPSHOT ๋ฐœ์†ก. run() ์‹œ์ž‘ ์‹œ์ ์— input_data.state๊ฐ€ ์žˆ์œผ๋ฉด ์ฆ‰์‹œ StateSnapshotEvent๋ฅผ emitํ•ด Frontend๊ฐ€ ์ดˆ๊ธฐ state๋ฅผ ๊ฐ€์ง€๊ณ  ์‹œ์ž‘ํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•œ๋‹ค.

5๋‹จ๊ณ„ โ€” tool call ๊ฐ€๋กœ์ฑ„๊ธฐ. Claude๊ฐ€ ag_ui_update_state๋ฅผ ํ˜ธ์ถœํ•˜๋ฉด Adapter๊ฐ€ ๊ทธ ํ˜ธ์ถœ์„ ๊ฐ์ง€ํ•ด ์ผ๋ฐ˜ TOOL_CALL_* ์ด๋ฒคํŠธ ๋Œ€์‹  STATE_SNAPSHOT ์ด๋ฒคํŠธ๋ฅผ emitํ•œ๋‹ค(์ƒ์„ธ๋Š” ์•„๋ž˜ ๋ณ„๋„ ์„น์…˜).

๊ฐœ๋ฐœ์ž ์ž…์žฅ์—์„œ๋Š” ์ด ๋‹ค์„ฏ ๋‹จ๊ณ„๊ฐ€ ๋ชจ๋‘ ๋ธ”๋ž™๋ฐ•์Šค์ด๋ฉฐ, ClaudeAgentAdapter(name=..., options=...)๋งŒ ์ธ์Šคํ„ด์Šคํ™”ํ•˜๋ฉด ์ž๋™์œผ๋กœ ๋™์ž‘ํ•œ๋‹ค.

State์˜ ์Šคํ‚ค๋งˆ๋Š” ๋ˆ„๊ฐ€ ์ •์˜ํ•˜๋Š”๊ฐ€

โ€œstate ํ‚ค๊ฐ€ ์„œ๋น„์Šค๋งˆ๋‹ค ๋‹ค๋ฅผ ์ˆ˜ ์žˆ๋Š”๊ฐ€? ๋ˆ„๊ฐ€ ๊ตฌ์กฐ๋ฅผ ์ •ํ•˜๋Š”๊ฐ€?โ€์— ๋Œ€ํ•œ ๋‹ต์€ ํ”„๋ ˆ์ž„์›Œํฌ๋งˆ๋‹ค ๋‹ค๋ฅด๋‹ค.

claude-agent-sdk ํ†ตํ•ฉ์—์„œ๋Š” Frontend๊ฐ€ ์‚ฌ์‹ค์ƒ์˜ ์Šคํ‚ค๋งˆ ์ •์˜์ž๋‹ค. useCoAgent({ initialState: {...} })๋กœ ๋„˜๊ธฐ๋Š” ์ดˆ๊ธฐ state์˜ JSON ๊ตฌ์กฐ๊ฐ€ ์•”๋ฌต์  ๊ณ„์•ฝ์ด ๋œ๋‹ค. ๋ฐฑ์—”๋“œ Adapter์—๋Š” ์–ด๋–ค ์Šคํ‚ค๋งˆ ๊ฒ€์ฆ๋„ ์—†๊ณ , ๋ฐ›์€ dict๋ฅผ ์–•์€ ๋ณ‘ํ•ฉ({**prev, **updates})์œผ๋กœ ํ•ฉ์น  ๋ฟ์ด๋‹ค. Claude๋Š” ์‹œ์Šคํ…œ ํ”„๋กฌํ”„ํŠธ์— ์ฃผ์ž…๋œ JSON ์˜ˆ์‹œ๋ฅผ ๋ณด๊ณ  ๊ตฌ์กฐ๋ฅผ ์œ ์ถ”ํ•ด ๊ฐ™์€ ํ‚ค ๊ตฌ์กฐ๋กœ ์—…๋ฐ์ดํŠธ๋ฅผ ์ƒ์„ฑํ•œ๋‹ค.

์ฆ‰, ์„œ๋น„์Šค๋งˆ๋‹ค ํ‚ค๊ฐ€ ์™„์ „ํžˆ ๋‹ค๋ฅผ ์ˆ˜ ์žˆ๋‹ค.

์„œ๋น„์Šค ์ข…๋ฅ˜์˜ˆ์‹œ state ๊ตฌ์กฐ
๋ ˆ์‹œํ”ผ ์–ด์‹œ์Šคํ„ดํŠธ{ "recipe": { "title": ..., "ingredients": [...] } }
์ž‘์—… ์Šน์ธ ์›Œํฌํ”Œ๋กœ์šฐ{ "steps": [{"description": ..., "status": "pending"}] }
์ด๋ฉ”์ผ ๋“œ๋ž˜ํ”„ํŠธ ์—์ด์ „ํŠธ{ "email_draft": { "recipient": ..., "body": ... } }
์‡ผํ•‘ ์นดํŠธ{ "user_profile": {...}, "cart": [...] }

๋ฐ˜๋ฉด Pydantic AI๋Š” ๋ช…์‹œ์  Pydantic BaseModel์„ ๊ฐ•์ œํ•˜๊ณ , LangGraph๋Š” TypedDict ๊ธฐ๋ฐ˜ StateGraph๋กœ ์Šคํ‚ค๋งˆ๋ฅผ ์„ ์–ธํ•œ๋‹ค. ์ด๋Ÿฐ ํ†ตํ•ฉ์—์„œ๋Š” ๋ฐฑ์—”๋“œ์—๋„ ์Šคํ‚ค๋งˆ๊ฐ€ ์กด์žฌํ•œ๋‹ค.

claude-agent-sdk ํ†ตํ•ฉ์ด ๋А์Šจํ•œ ์ด์œ ๋Š” ์„ค๊ณ„ ์„ ํƒ์ด๋‹ค. ์Šคํ‚ค๋งˆ๋ฅผ Frontend ํ•œ ๊ณณ์—๋งŒ ๋‘๋ฉด Frontend/Backend ๋™๊ธฐํ™” ๋ฌธ์ œ๊ฐ€ ์‚ฌ๋ผ์ง„๋‹ค๋Š” ์žฅ์ ๊ณผ, Claude์˜ JSON ๊ตฌ์กฐ ์œ ์ถ” ๋Šฅ๋ ฅ์„ ์‹ ๋ขฐํ•˜๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. ํŠธ๋ ˆ์ด๋“œ์˜คํ”„๋Š” ๋ฐฑ์—”๋“œ ํƒ€์ž… ์•ˆ์ „์„ฑ์˜ ํฌ๊ธฐ๋‹ค.

Adapter์˜ ๊ฐ€๋กœ์ฑ„๊ธฐ ๋™์ž‘ ์›๋ฆฌ

โ€œClaude์—๊ฒŒ ์Šคํ…์ด ok๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š”๋ฐ Adapter๋Š” ์–ด๋–ป๊ฒŒ state๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š”๊ฐ€? ๊ฐ€๋กœ์ฑ„๋Š” ์ฃผ์ฒด๋Š” ๋ˆ„๊ตฌ์ธ๊ฐ€?โ€์— ๋Œ€ํ•œ ๋‹ต์€ ๋‘ ๊ฐœ์˜ ํ‰ํ–‰ํ•œ ํ๋ฆ„์ด ๋™์‹œ์— ์ง„ํ–‰๋œ๋‹ค๋Š” ๊ฒƒ์ด๋‹ค.

sequenceDiagram
    autonumber
    participant Claude
    participant SDK as Claude SDK CLI
    participant Stub as Stub Tool
    participant Adapter
    participant FE as Frontend

    Claude->>SDK: tool_use ag_ui_update_state
    SDK->>Stub: execute
    Stub-->>SDK: State updated successfully
    SDK-->>Claude: tool_result success
    Note over Claude: ์ •์ƒ tool ํ˜ธ์ถœ๋กœ ์ธ์‹. ์ถ”๋ก  ๊ณ„์†
    SDK->>Adapter: ๋ฉ”์‹œ์ง€ ์ŠคํŠธ๋ฆผ ๊ด€์ฐฐ
    Adapter->>Adapter: tool_name ๊ฒ€์‚ฌ ํ›„ ํŠน์ˆ˜ ๋ถ„๊ธฐ
    Note over Adapter: TOOL_CALL ์ด๋ฒคํŠธ ์Šคํ‚ตํ•˜๊ณ  state ๋ณ‘ํ•ฉ
    Adapter-->>FE: StateSnapshotEvent ์ „์†ก

ํ๋ฆ„ A (Claude โ†” Stub). Claude Agent SDK์˜ MCP ์ธํ”„๋ผ๊ฐ€ ์Šคํ… ๋„๊ตฌ๋ฅผ ์‹ค์ œ๋กœ ์‹คํ–‰ํ•œ๋‹ค. ์Šคํ…์€ "State updated successfully"๋ฅผ ๋ฐ˜ํ™˜ํ•˜๊ณ , Claude๋Š” ์ด๊ฒƒ์„ ์ •์ƒ tool_result๋กœ ๋ฐ›์•„ ๋‹ค์Œ ๋™์ž‘์„ ๊ฒฐ์ •ํ•œ๋‹ค. ์ด ํ๋ฆ„์€ AG-UI์™€ ๋ฌด๊ด€ํ•˜๊ฒŒ ์™„๊ฒฐ๋œ๋‹ค.

ํ๋ฆ„ B (Adapter์˜ ๋„์ฒญ). ๊ทธ์™€ ๋™์‹œ์— Adapter๋Š” client.receive_response()๋กœ Claude SDK ๋ฉ”์‹œ์ง€ ์ŠคํŠธ๋ฆผ์„ ๊ด€์ฐฐํ•˜๊ณ  ์žˆ๋‹ค. ์ด ์ŠคํŠธ๋ฆผ์„ AG-UI ์ด๋ฒคํŠธ๋กœ ๋ฒˆ์—ญํ•˜๋Š” ๊ณผ์ •์—์„œ tool_name์ด ag_ui_update_state์ธ ๊ฒฝ์šฐ๋งŒ ํŠน์ˆ˜ ๋ถ„๊ธฐํ•œ๋‹ค. ์›๋ณธ์€ ag_ui_claude_sdk/handlers.py.

if _is_state_management_tool(tool_name):
    state_updates = tool_input.get("state_updates", {})
    if isinstance(current_state, dict) and isinstance(state_updates, dict):
        current_state = {**current_state, **state_updates}
    yield StateSnapshotEvent(
        type=EventType.STATE_SNAPSHOT,
        snapshot=current_state
    )
    return   # ์ผ๋ฐ˜ TOOL_CALL_* ์ด๋ฒคํŠธ emit ์Šคํ‚ต

โ€œ๊ฐ€๋กœ์ฑ„๊ธฐโ€์˜ ๋ณธ์งˆ์€ ๋„๊ตฌ ์‹คํ–‰์„ ๋ง‰๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ, Claude SDK ๋ฉ”์‹œ์ง€ โ†’ AG-UI ์ด๋ฒคํŠธ ๋ณ€ํ™˜ ๋‹จ๊ณ„์—์„œ ํŠน์ˆ˜ ๊ฒฝ๋กœ๋กœ ๋ณด๋‚ด๋Š” ๊ฒƒ์ด๋‹ค. ํ†ต์—ญ์‚ฌ๊ฐ€ โ€œ๊ฐ™์€ ๋ง์„ ๋‹ค๋ฅด๊ฒŒ ๋ฒˆ์—ญโ€ํ•˜๋Š” ๊ฒƒ์— ๊ฐ€๊น๋‹ค.

๊ฐ€๋กœ์ฑ„๋Š” ์ฃผ์ฒด๋Š” ๋ช…ํ™•ํžˆ Adapter(ClaudeAgentAdapter), ๊ตฌ์ฒด์ ์œผ๋กœ๋Š” ์ŠคํŠธ๋ฆฌ๋ฐ ๊ฒฝ๋กœ์˜ _stream_claude_sdk() ๋ฉ”์„œ๋“œ์™€ ๋น„์ŠคํŠธ๋ฆฌ๋ฐ ๊ฒฝ๋กœ์˜ handle_tool_use_block() ํ•จ์ˆ˜๋‹ค.

Agent์™€ Claude ๊ฐ„ ํ†ต์‹  ์Šคํƒ

โ€œClaudeAgentAdapter๋Š” API ํ˜ธ์ถœ ๋ฐฉ์‹์ธ๊ฐ€, CLI + NDJSON์ธ๊ฐ€?โ€์— ๋Œ€ํ•œ ๋‹ต์€ ํ›„์ž๋‹ค.

์˜์กด์„ฑ ์ฒด์ธ์€ ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

Python ์„œ๋ฒ„ (FastAPI)
 โ””โ”€ ag-ui-claude-sdk (PyPI, ์ด ๋ ˆํฌ์˜ ํ†ตํ•ฉ ํŒจํ‚ค์ง€)
      โ””โ”€ claude-agent-sdk (PyPI, Anthropic ๊ณต์‹)
           โ””โ”€ subprocess spawn
                โ””โ”€ `claude` CLI ๋ฐ”์ด๋„ˆ๋ฆฌ (npm @anthropic-ai/claude-code)
                     โ””โ”€ HTTPS โ†’ api.anthropic.com

Adapter ์ž์ฒด๊ฐ€ Anthropic API๋ฅผ ์ง์ ‘ ํ˜ธ์ถœํ•˜์ง€๋Š” ์•Š๋Š”๋‹ค. ๋Œ€์‹  claude-agent-sdk ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์— ์œ„์ž„ํ•˜๊ณ , ์ด ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๊ฐ€ Claude Code CLI๋ฅผ subprocess๋กœ ๋„์›Œ stdio ๊ธฐ๋ฐ˜ NDJSON ํ”„๋กœํ† ์ฝœ๋กœ ํ†ต์‹ ํ•œ๋‹ค. CLI๊ฐ€ ์ตœ์ข…์ ์œผ๋กœ Anthropic API์™€ HTTPS๋กœ ํ†ต์‹ ํ•˜๋Š” ๋ถ€๋ถ„์€ Adapter ๊ด€์ ์—์„œ ๋ธ”๋ž™๋ฐ•์Šค๋‹ค.

์ฆ๊ฑฐ๋Š” README์˜ Session Persistence ํ•ญ๋ชฉ์— ๋“œ๋Ÿฌ๋‚œ๋‹ค. Claude SDK๊ฐ€ .claude/ ๋””๋ ‰ํ† ๋ฆฌ์— ์„ธ์…˜ ์ƒํƒœ๋ฅผ ์ €์žฅํ•œ๋‹ค๋Š” ์ ์€ ์ˆœ์ˆ˜ API ํด๋ผ์ด์–ธํŠธ๋กœ๋Š” ๋ถˆ๊ฐ€๋Šฅํ•œ ๋™์ž‘์œผ๋กœ, CLI๊ฐ€ ๋กœ์ปฌ ํŒŒ์ผ์‹œ์Šคํ…œ์„ ๋‹ค๋ฃจ๋Š” ํŠน์„ฑ ๋•Œ๋ฌธ์ด๋‹ค.

๋”ฐ๋ผ์„œ ์ด ํ†ตํ•ฉ์„ ๋ฐฐํฌํ•˜๋ ค๋ฉด ๋‹จ์ˆœํžˆ Python ํŒจํ‚ค์ง€ ํ•˜๋‚˜๋กœ๋Š” ๋ถ€์กฑํ•˜๊ณ , Node.js ํ™˜๊ฒฝ + @anthropic-ai/claude-code CLI ๋ฐ”์ด๋„ˆ๋ฆฌ๊ฐ€ PATH์— ์žˆ์–ด์•ผ ํ•œ๋‹ค.

npm install -g @anthropic-ai/claude-code
pip install ag-ui-claude-sdk
export ANTHROPIC_API_KEY=sk-ant-xxx

ํ†ต์‹  ์Šคํƒ ์ „์ฒด์—๋Š” ์„ธ ๊ฐœ์˜ ๋…๋ฆฝ๋œ ์ŠคํŠธ๋ฆผ์ด ์ค‘์ฒฉ๋˜์–ด ์žˆ๋‹ค.

๊ตฌ๊ฐ„ํ”„๋กœํ† ์ฝœ๋‚ด์šฉ
Claude API โ†” CLIHTTPS + SSEAnthropic Messages API ํ† ํฐ ์ŠคํŠธ๋ฆผ
CLI โ†” Python SDKstdio + NDJSONClaude SDK ๋ฉ”์‹œ์ง€ ๊ฐ์ฒด
FastAPI โ†” FrontendHTTP + SSEAG-UI ์ด๋ฒคํŠธ(RUN_STARTED, STATE_SNAPSHOT ๋“ฑ)

๊ฐ ๋ ˆ์ด์–ด๋Š” ์ž๊ธฐ ๋‹จ์˜ ์ŠคํŠธ๋ฆผ์„ ํŒŒ์‹ฑํ•˜๊ณ  ๋‹ค์Œ ๋ ˆ์ด์–ด ํ˜•์‹์œผ๋กœ ๋ณ€ํ™˜ํ•ด์„œ ๋‚ด๋ณด๋‚ธ๋‹ค. ClaudeAgentAdapter์˜ ๋ณธ์งˆ์€ ์ด ์ค‘๊ฐ„ ๋ฒˆ์—ญ๊ธฐ๋‹ค.

Codex CLI์šฉ ์–ด๋Œ‘ํ„ฐ๋Š” ์กด์žฌํ•˜๋Š”๊ฐ€

ํ˜„์žฌ AG-UI ๊ณต์‹ integrations/ ๋””๋ ‰ํ† ๋ฆฌ์—๋Š” 18๊ฐœ ํ†ตํ•ฉ์ด ์žˆ์ง€๋งŒ, OpenAI Codex CLI ์ „์šฉ ์–ด๋Œ‘ํ„ฐ๋Š” ์—†๋‹ค.

์ „์ฒด ํ†ตํ•ฉ ๋ชฉ๋ก:

a2a                      langroid
adk-middleware           llama-index
ag2                      mastra
agent-spec               microsoft-agent-framework
agno                     pydantic-ai
aws-strands              server-starter
claude-agent-sdk         server-starter-all-features
community/genkit         vercel-ai-sdk
community/spring-ai
crew-ai
langchain
langgraph

CLI subprocess ๋ฐฉ์‹์ด Claude์—๋งŒ ์žˆ๋Š” ๊ฒƒ์€, Claude Code CLI๊ฐ€ ๊ณต์‹ claude-agent-sdk๋กœ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌํ™”๋˜์–ด stdio/NDJSON ํ”„๋กœํ† ์ฝœ์ด ์•ˆ์ •์ ์œผ๋กœ ๊ณต๊ฐœ๋˜์—ˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. Codex CLI๋Š” ์ฃผ๋กœ ํ„ฐ๋ฏธ๋„ UX ์ค‘์‹ฌ์ด๋ผ ํ”„๋กœ๊ทธ๋ž˜๋งคํ‹ฑ ์ œ์–ด๋ฅผ ์œ„ํ•œ ํ‘œ์ค€ SDK ๋ฐฐํฌ๊ฐ€ ์•„์ง Claude๋งŒํผ ์„ฑ์ˆ™ํ•˜์ง€ ์•Š๋‹ค.

OpenAI ๋ชจ๋ธ์„ AG-UI์™€ ๋ถ™์ด๋ ค๋ฉด ํ˜„์‹ค์ ์œผ๋กœ๋Š” API ๊ธฐ๋ฐ˜ ํ†ตํ•ฉ์„ ์‚ฌ์šฉํ•œ๋‹ค: vercel-ai-sdk, langchain, langgraph(LLM provider๋กœ OpenAI ์„ ํƒ), llama-index, pydantic-ai ๋“ฑ์ด ๋ชจ๋‘ HTTPS API ๊ฒฝ๋กœ๋กœ OpenAI๋ฅผ ์ง€์›ํ•œ๋‹ค.

์ง์ ‘ Codex CLI ์–ด๋Œ‘ํ„ฐ๋ฅผ ๋งŒ๋“ค๊ณ  ์‹ถ๋‹ค๋ฉด claude-agent-sdk ํ†ตํ•ฉ์ด ์ข‹์€ ํ…œํ”Œ๋ฆฟ์ด ๋œ๋‹ค. adapter.py(AbstractAgent ๊ตฌํ˜„), session.py(subprocess ๊ด€๋ฆฌ), handlers.py(tool call ๋ฒˆ์—ญ), utils.py(ํ”„๋กฌํ”„ํŠธ augmentation)์˜ ๋„ค ๊ฐ€์ง€ ํŒŒ์ผ ๊ตฌ์กฐ๋ฅผ ๊ทธ๋Œ€๋กœ ๋”ฐ๋ฅด๋˜, Codex CLI์˜ stdio ๋ฉ”์‹œ์ง€ ํฌ๋งท์„ AG-UI ์ด๋ฒคํŠธ๋กœ ๋งคํ•‘ํ•˜๋Š” ๋กœ์ง๋งŒ ์ƒˆ๋กœ ์ž‘์„ฑํ•˜๋ฉด ๋œ๋‹ค. ๋‚œ์ด๋„๋Š” Codex CLI์˜ ํ”„๋กœํ† ์ฝœ ๋ฌธ์„œํ™” ์ˆ˜์ค€์— ํฌ๊ฒŒ ์ขŒ์šฐ๋œ๋‹ค.

ํ†ตํ•ฉ๋ณ„ ๋น„๊ต ์š”์•ฝ

๊ฐ™์€ AG-UI ํ”„๋กœํ† ์ฝœ์— ๋ถ™๋Š” ๋‹ค์–‘ํ•œ ๋ฐฉ์‹:

ํ†ตํ•ฉAgent โ†” LLM ํ†ต์‹  ๋ฐฉ์‹state ์Šคํ‚ค๋งˆ ์ •์˜ ์œ„์น˜
claude-agent-sdkCLI subprocess + stdio/NDJSONFrontend (์•”๋ฌต์ )
langchainanthropic / openai SDK โ†’ HTTPS API๊ฐœ๋ฐœ์ž ์ฝ”๋“œ
langgraph๋‚ด๋ถ€ LLM ์ถ”์ƒํ™” โ†’ HTTPS APITypedDict / Pydantic
vercel-ai-sdk@ai-sdk/* โ†’ HTTPS APITypeScript ํƒ€์ž…
pydantic-ai๋‹ค์ค‘ provider โ†’ HTTPS APIPydantic BaseModel
crew-ai๋‚ด๋ถ€ LLM ์ถ”์ƒํ™”Flow state (Pydantic)
mastra๋‚ด๋ถ€ ๋ฉ”๋ชจ๋ฆฌ ์‹œ์Šคํ…œWorking memory

AG-UI ํ”„๋กœํ† ์ฝœ์€ ์–ด๋–ค ๋ฐฉ์‹์ด๋“  ์ƒ๊ด€ํ•˜์ง€ ์•Š๋Š”๋‹ค. ํ”„๋กœํ† ์ฝœ์€ Frontend โ†” Agent ์‚ฌ์ด์˜ ๊ทœ๊ฒฉ๋งŒ ์ •์˜ํ•˜๊ณ , ๊ทธ ๋’ค๋Š” ๊ฐ ํ†ตํ•ฉ์ด ์ž์œ ๋กญ๊ฒŒ ์„ ํƒํ•œ๋‹ค. ์ด๊ฒƒ์ด AG-UI๊ฐ€ โ€œtransport agnosticโ€์ด๋ผ๊ณ  ๋ถˆ๋ฆฌ๋Š” ์‹ค์ œ ์ด์œ ๋‹ค.

Best Practices

  • SNAPSHOT์€ ์•„๊ปด ์“ฐ๊ธฐ: ์ดˆ๊ธฐํ™”, ์žฌ๋™๊ธฐํ™”, ๋Œ€๊ทœ๋ชจ ๋ฆฌ์…‹ ๋“ฑ baseline์ด ํ•„์š”ํ•œ ์ˆœ๊ฐ„์—๋งŒ ์‚ฌ์šฉํ•œ๋‹ค
  • DELTA๋ฅผ ๊ธฐ๋ณธ์œผ๋กœ: ์ฆ๋ถ„ ๋ณ€๊ฒฝ์€ ํ•ญ์ƒ delta๋กœ ๋ณด๋‚ด ๋„คํŠธ์›Œํฌ ๋น„์šฉ์„ ์ค„์ธ๋‹ค
  • State ๊ตฌ์กฐ ์„ค๊ณ„: ๋ถ€๋ถ„ ์—…๋ฐ์ดํŠธ๊ฐ€ ์‰ฝ๋„๋ก ์ค‘์ฒฉ์„ ์–•๊ฒŒ, ํ‚ค๋ฅผ ์•ˆ์ •์ ์œผ๋กœ ์„ค๊ณ„ํ•œ๋‹ค
  • ์ถฉ๋Œ ์ฒ˜๋ฆฌ ์ •์ฑ… ๋งˆ๋ จ: Agent์™€ Frontend๊ฐ€ ๋™์‹œ์— ๊ฐ™์€ ํ•„๋“œ๋ฅผ ์ˆ˜์ •ํ•  ๋•Œ ์–ด๋А ์ชฝ์ด ์ด๊ธฐ๋Š”์ง€ ๋ฏธ๋ฆฌ ์ •ํ•œ๋‹ค
  • ์—๋Ÿฌ ๋ณต๊ตฌ ๊ฒฝ๋กœ ํ™•๋ณด: delta ์ ์šฉ ์‹คํŒจ ์‹œ SNAPSHOT ์žฌ์š”์ฒญ ๋ฉ”์ปค๋‹ˆ์ฆ˜์„ ๋‘์–ด ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์˜๊ตฌ์ ์œผ๋กœ ์–ด๊ธ‹๋‚˜์ง€ ์•Š๊ฒŒ ํ•œ๋‹ค
  • ๋ฏผ๊ฐ ์ •๋ณด ๋ฐฐ์ œ: ๊ณต์œ  state์—๋Š” ์ธ์ฆ ํ† ํฐ, ๊ฐœ์ธ์ •๋ณด ๋“ฑ์„ ๋‹ด์ง€ ์•Š๋Š”๋‹ค
  • ํ”„๋ ˆ์ž„์›Œํฌ๋ณ„ ๊ทœ์•ฝ ์ค€์ˆ˜: ๊ฐ ํ†ตํ•ฉ์ด ๊ฐ€์ง„ state emit ํŒจํ„ด(Claude SDK์˜ ๋„๊ตฌ ์ฃผ์ž…, Pydantic AI์˜ ๋ฐ˜ํ™˜ ํƒ€์ž…, LangGraph์˜ dispatch ๋“ฑ)์„ ๋”ฐ๋ฅธ๋‹ค

์ฐธ๊ณ  ๋ฌธ์„œ