- 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_MESSAGE | STATE_* |
|---|---|---|
| ๋ณธ์ง | ๋ํ ํ ์คํธ | ๊ตฌ์กฐํ๋ ์ ํ๋ฆฌ์ผ์ด์ ๋ฐ์ดํฐ |
| ๋ณ๊ฒฝ ๊ฐ๋ฅ์ฑ | 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[]
}| ์ถ | SNAPSHOT | DELTA |
|---|---|---|
| ๋ฐ์ดํฐ | ์ ์ฒด | ๋ณ๊ฒฝ๋ถ๋ง |
| ์ ์ฉ ๋ฐฉ์ | ๊ต์ฒด | ํจ์น ์ ์ฉ |
| ๋คํธ์ํฌ ๋น์ฉ | ๋์ | ๋ฎ์ |
| ์ฃผ ์ฉ๋ | ์ด๊ธฐํ, ์ฌ๋๊ธฐํ, ๋๊ท๋ชจ ๋ณ๊ฒฝ | ๊ณ ๋น๋ ์ฆ๋ถ ์ ๋ฐ์ดํธ |
SNAPSHOT์ ์ต์ด์๋ง ์ฌ์ฉ๋๋๊ฐ
์ผ๋ฐ์ ํ๋ฆ์ โ์ต์ด SNAPSHOT โ ์ดํ DELTAโ๊ฐ ๋ง์ง๋ง, SNAPSHOT์ ์ค๊ฐ์๋ ํ์์ ๋ฐ๋ผ ๋ค์ ๋์ฌ ์ ์๋ค. ๊ณต์ ๋ฌธ์๋ ๋ค ๊ฐ์ง ๊ฒฝ์ฐ๋ฅผ ๋ช ์ํ๋ค.
- ์ธํฐ๋์ ์์ ์ baseline ์๋ฆฝ
- ์ฐ๊ฒฐ ๋๊น ํ ์ฌ๋๊ธฐํ
- ํจ์น ์ ์ฉ์ ์คํจํด ๋ถ์ผ์น๊ฐ ๊ฐ์ง๋ ๊ฒฝ์ฐ
- ๋๊ท๋ชจ ๋ณ๊ฒฝ์ผ๋ก 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์ ์ง์นจ์ด ์๋๊ฐ?โ๋ผ๋ ์ง๋ฌธ์ ์ธต์๋ฅผ ์์์ ๋ ์๊ธฐ๋ ํผ๋์ด๋ค. ์ค์ ๋ก๋ ๋ค์ฏ ์ฃผ์ฒด๊ฐ ๊ฐ์ ๋ค๋ฅธ ์ฑ ์์ ์ง๋ค.
| ์ฃผ์ฒด | ๋ด๋น | ํ ์ค ์์ฝ |
|---|---|---|
| Frontend | initialState ์ ์, UI ๋ ๋, setState | ์๋ฌต์ ์คํค๋ง์ ์ถ๋ฐ์ |
| AG-UI ํ๋กํ ์ฝ | ์ด๋ฒคํธ ํ์ ์ ์, ์ ์ก ๊ท๊ฒฉ | โHOW to transmitโ๋ง ๋ด๋น |
| Agent ํ๋ ์์ํฌ(+Adapter) | LLM ์ถ๋ ฅ์ state ์ด๋ฒคํธ๋ก ๋ณํ | โWHEN to emitโ ๊ฒฐ์ |
| LLM | Tool 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 โ CLI | HTTPS + SSE | Anthropic Messages API ํ ํฐ ์คํธ๋ฆผ |
| CLI โ Python SDK | stdio + NDJSON | Claude SDK ๋ฉ์์ง ๊ฐ์ฒด |
| FastAPI โ Frontend | HTTP + SSE | AG-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-sdk | CLI subprocess + stdio/NDJSON | Frontend (์๋ฌต์ ) |
| langchain | anthropic / openai SDK โ HTTPS API | ๊ฐ๋ฐ์ ์ฝ๋ |
| langgraph | ๋ด๋ถ LLM ์ถ์ํ โ HTTPS API | TypedDict / Pydantic |
| vercel-ai-sdk | @ai-sdk/* โ HTTPS API | TypeScript ํ์ |
| pydantic-ai | ๋ค์ค provider โ HTTPS API | Pydantic 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 ๋ฑ)์ ๋ฐ๋ฅธ๋ค
์ฐธ๊ณ ๋ฌธ์
- AG-UI State Management (๊ณต์)
- JSON Patch (RFC 6902)
- JSON Pointer (RFC 6901)
- Claude Agent SDK (Python)
- CopilotKit useCoAgent
- ๋ ํฌ ๋ด๋ถ ๊ตฌํ
sdks/typescript/packages/core/src/events.tsโStateSnapshotEvent,StateDeltaEvent์คํค๋ง ์ ์sdks/typescript/packages/client/src/apply/default.tsโ ํด๋ผ์ด์ธํธ ์ธกfast-json-patch์ ์ฉ ๋ก์ง
- ํตํฉ ๊ตฌํ ์ (
claude-agent-sdk)adapter.pyโ ๋ฉ์ธ ์ค์ผ์คํธ๋ ์ด์ ,build_options,_stream_claude_sdkhandlers.pyโ ๋น์คํธ๋ฆฌ๋ฐ ๊ฒฝ๋ก์ tool ๊ฐ๋ก์ฑ๊ธฐutils.pyโcreate_state_management_tool,build_state_context_addendum
- ํตํฉ ๊ตฌํ ์ (
langgraphPython)ag_ui_langgraph/agent.pyโ LangGraph ๋ ธ๋ ์ด๋ฒคํธ๋ฅผ AG-UI ์ด๋ฒคํธ๋ก ๋ณํexamples/agents/shared_state/agent.pyโadispatch_custom_event์ฌ์ฉ ์์examples/agents/predictive_state_updates/agent.pyโ predict_state ๋ฉํ๋ฐ์ดํฐ ์ค์ ์์
- ํตํฉ ๊ตฌํ ์ (๊ธฐํ ํ๋ ์์ํฌ)
integrations/pydantic-ai/python/examples/server/api/shared_state.pyโ Pydantic AI ์ ์ธ์ ๋ฐํ ํจํดintegrations/mastra/typescript/src/mastra.tsโ Mastra working memory snapshot ๋ฐฉ์integrations/crew-ai/python/ag_ui_crewai/endpoint.pyโ CrewAI ์ด๋ฒคํธ ๋ฒ์ค ๋ฆฌ์ค๋ ๋ฐฉ์
- ์ ์ฒด ํตํฉ ๋ชฉ๋ก:
integrations/