- AG-UI μ΄λ²€νΈ μμ€ν
μ μμ΄μ νΈ-UI κ° μ€μκ° ν΅μ μ μν typed μ΄λ²€νΈ μ€νΈλ¦Ό νλ‘ν μ½
- 28κ° νμ± μ΄λ²€νΈ νμ
κ³Ό 3κ°μ§ μ€κ³ ν¨ν΄μΌλ‘ ꡬμ±λ νμ€νλ μμ΄μ νΈ μΈν°νμ΄μ€
- Thread(λν μΈμ
) > Run(μ¬μ©μ 1ν΄) > Step(μ νμ λ¨κ³) > Streaming Item μ 4κ³μΈ΅ ꡬ쑰
ν΄λΉ κ°λ
μ΄ νμν μ΄μ
- LangGraph, CrewAI λ± λ€μν νλ μμν¬λ₯Ό νλμ UIμμ μ°κ²°ν νμ€ μΈν°νμ΄μ€ λΆμ¬ ν΄κ²°
- LLM μλ΅μ μ€μκ° μ€νΈλ¦¬λ°μ μν μ΄λ²€νΈ κΈ°λ° λΉλκΈ° ν΅μ νμ
- MCP(μμ΄μ νΈ λꡬ) Β· A2A(μμ΄μ νΈ κ° ν΅μ )μ ν¨κ» μμ΄μ νΈ νλ‘ν μ½ μ€νμ UI κ³μΈ΅ λ΄λΉ
AS-IS β REST μμ²/μλ΅ λ°©μ
sequenceDiagram
autonumber
participant User
participant UI
participant Agent
User->>UI: λ©μμ§ μ
λ ₯
UI->>Agent: HTTP POST /chat
Note over Agent: μ 체 μλ΅ μμ± (waiting...)
Agent-->>UI: μμ±λ μλ΅ λ°ν
UI->>User: μλ΅ νμ (ν λ²μ)
TO-BE β AG-UI μ΄λ²€νΈ μ€νΈλ¦¬λ°
sequenceDiagram
autonumber
participant User
participant UI
participant Agent
User->>UI: λ©μμ§ μ
λ ₯
UI->>Agent: RunAgentInput (threadId + runId)
Agent-->>UI: RUN_STARTED
Agent-->>UI: TEXT_MESSAGE_START
Agent-->>UI: TEXT_MESSAGE_CONTENT (delta)
Agent-->>UI: TEXT_MESSAGE_CONTENT (delta)
Agent-->>UI: TEXT_MESSAGE_END
Agent-->>UI: RUN_FINISHED
UI->>User: μ€μκ° μ€νΈλ¦¬λ° νμ
4κ³μΈ΅ ꡬ쑰: Thread > Run > Step > Item
flowchart TD
subgraph THREAD["Thread (threadId β λν μΈμ
)"]
subgraph RUN["Run (runId β μ¬μ©μ 1ν΄)"]
RUN_STARTED([RUN_STARTED])
RUN_FINISHED([RUN_FINISHED])
RUN_ERROR([RUN_ERROR])
subgraph STEP["Step (μ νμ , Nκ° μμ°¨)"]
STEP_STARTED[STEP_STARTED]
STEP_FINISHED[STEP_FINISHED]
subgraph MSG["Text Message"]
TMS[TEXT_MESSAGE_START]
TMC[TEXT_MESSAGE_CONTENT]
TME[TEXT_MESSAGE_END]
end
subgraph TOOL["Tool Call"]
TCS[TOOL_CALL_START]
TCA[TOOL_CALL_ARGS]
TCE[TOOL_CALL_END]
TCR[TOOL_CALL_RESULT]
end
subgraph REASON["Reasoning"]
RST[REASONING_START]
RMC[REASONING_MESSAGE_CONTENT]
REN[REASONING_END]
end
end
end
end
RUN_STARTED --> STEP_STARTED
STEP_STARTED --> TMS
STEP_STARTED --> TCS
STEP_STARTED --> RST
TMS --> TMC
TMC -->|λ°λ³΅| TMC
TMC --> TME
TCS --> TCA
TCA -->|λ°λ³΅| TCA
TCA --> TCE
TCE --> TCR
RST --> RMC
RMC -->|λ°λ³΅| RMC
RMC --> REN
TME --> STEP_FINISHED
TCR --> STEP_FINISHED
REN --> STEP_FINISHED
STEP_FINISHED -->|λ€μ Step| STEP_STARTED
STEP_FINISHED --> RUN_FINISHED
RUN_STARTED -->|μΉλͺ
μ μλ¬| RUN_ERROR
RUN_FINISHED -->|λ€μ μ¬μ©μ ν΄| RUN_STARTED
State Management β μν κ³Ό λΌμ΄νμ¬μ΄ν΄
λͺ©μ : μμ΄μ νΈμ νλ‘ νΈμλ κ° μλ°©ν₯ 곡μ μν λκΈ°ν. λ¨μ μ μ₯μ΄ μλλΌ μμͺ½ λͺ¨λ μ½κ³ μΈ μ μλ collaborative stateλ‘, Human-in-the-Loop μν¬νλ‘μ°μ κΈ°λ°μ΄ λλ€.
ν΅μ¬ νΉμ±
- λ°©ν₯: μλ°©ν₯ (agent β frontend λͺ¨λ μμ κ°λ₯)
- λͺ©μ : μμ΄μ νΈμ μ μ(proposal)μ UIκ° νμ β μ¬μ©μκ° μΉμΈ/κ±°λΆ/μμ β λ€μ μμ΄μ νΈμ λ°μ
- UI λ°μ: λ©μΈ UI (νΌ, μλν°, λ¬Έμ λ·° λ±)
- μ§μμ±: Run κ²½κ³λ₯Ό λμ΄ λμ
μ€μ μμ: Shared State β λ μνΌ μλν°
AIμ μ¬μ©μκ° κ°μ λ μνΌλ₯Ό ν¨κ» νΈμ§νλ μλ리μ€.
state = {
"recipe": {
"skill_level": "Advanced",
"ingredients": [{"name": "chicken breast", "amount": "1"}, ...],
"instructions": ["Season chicken...", "Sear until...", ...]
}
}
yield StateSnapshotEvent(type=EventType.STATE_SNAPSHOT, snapshot=state)
CopilotKitμ useCoAgent hookμ΄ μ΄ stateλ₯Ό μλ°©ν₯μΌλ‘ λ°μΈλ©νλ€:
const { state: agentState, setState: setAgentState } = useCoAgent({
name: "agent",
initialState: { someProperty: "initialValue" },
})
- μμ΄μ νΈκ° λ μνΌ μ΄μμ
STATE_SNAPSHOTμΌλ‘ 곡μ
- UIκ° νΌμΌλ‘ λ λλ§ β μ¬μ©μκ° μ¬λ£ μμ
setAgentStateλ‘ λ³κ²½μ΄ μμ΄μ νΈμ μ λ¬λμ΄ λ€μ μμ±μ λ°μ
μ νμνκ°: λ©μμ§λ‘λ βμ¬λ£ λͺ©λ‘, 쑰리 λ¨κ³, λμ΄λβ κ°μ ꡬ쑰νλ 곡λ νΈμ§ λ¬Έμλ₯Ό ννν μ μλ€.
λΌμ΄νμ¬μ΄ν΄ λ€μ΄μ΄κ·Έλ¨
sequenceDiagram
autonumber
participant Agent
participant UI
participant User
Note over Agent: Run μμ
Agent->>UI: STATE_SNAPSHOT (μ΄κΈ° μ 체 μν)
UI->>User: νΌ λ λλ§
Note over Agent: μΌλΆ νλ μ
λ°μ΄νΈ
Agent->>UI: STATE_DELTA (JSON Patch)
UI->>User: ν΄λΉ νλλ§ κ°±μ
User->>UI: νλ μμ
UI->>Agent: setState (μλ°©ν₯ λ°μ)
Note over Agent: λκ·λͺ¨ λ³κ²½/μ¬λκΈ°ν νμ
Agent->>UI: STATE_SNAPSHOT (μ¬λκΈ°ν)
UI->>User: μ 체 μ¬λ λλ§
Activity β μν κ³Ό λΌμ΄νμ¬μ΄ν΄
λͺ©μ : μ±ν
λ©μμ§ μ¬μ΄μ ꡬ쑰νλ μ§ν μν©μ νμ. μμ΄μ νΈκ° νμ¬ νκ³ μλ μμ
μ νλ‘κ·Έλ μ€ λ°, μΉ΄λ, νλ νΈλ¦¬ κ°μ λ³λ UI μ»΄ν¬λνΈλ‘ μκ°ννκΈ° μν λ¨λ°©ν₯ μ€νΈλ¦Ό.
ν΅μ¬ νΉμ±
- λ°©ν₯: λ¨λ°©ν₯ (agent β frontend, μ¬μ©μ μμ λΆκ°)
- λͺ©μ : μμ
μ§ν μν©μ ꡬ쑰νλ μ½κΈ° μ μ© λ·°
- UI λ°μ: λ©μμ§ μ€νΈλ¦Ό μ€κ° (Plan λ°μ€, κ²μ κ²°κ³Ό ν¨λ λ±)
- μ§μμ±:
role: "activity" λ©μμ§λ‘ νμ€ν 리 λμ , Runμ λμ΄ λ³΄μ‘΄
μ€μ μμ: Plan λͺ¨λ
μμ΄μ νΈκ° μμ
κ³νμ μ립νλ©΄μ UIμ λ³λμ Plan λ°μ€λ‘ μ€μκ° νμ.
{
type: EventType.ACTIVITY_SNAPSHOT,
messageId: "activity-1",
activityType: "PLAN",
content: { tasks: ["task 1"] }
}
activityTypeμ΄ "PLAN"μ΄λ©΄ UIλ Plan μ μ© λ λλ¬λ₯Ό μ ν
- κ°μ
messageIdμ ACTIVITY_DELTAλ‘ tasks λ°°μ΄μ μΆκ°/μμ
- Runμ΄ λλλ
role: "activity" λ©μμ§λ‘ νμ€ν 리μ λ¨μ
μ νμνκ°:
- λ©μμ§λ μμ°μ΄ ν
μ€νΈ λ²λΈ β Plan/Search κ°μ ꡬ쑰νλ μ§ν λ·°μ λΆμ ν©
- State Managementλ μλ°©ν₯ νΈμ§μ©μ΄λΌ μ½κΈ° μ μ© μ§ν μν© νμμλ κ³Όν μ€κ³
- Activity = βμμ΄μ νΈλ§ μ°κ³ μ¬μ©μλ λ³΄κΈ°λ§ νλ ꡬ쑰νλ λ·°βμ μ μ© ν리미ν°λΈ
λ©μμ§ vs State vs Activity λΉκ΅
| νλͺ© | TEXT_MESSAGE | STATE | ACTIVITY |
|---|
| μ©λ | λν ν
μ€νΈ | 곡μ νΈμ§ μν | μ§ν μν© λ·° |
| λ΄μ© | μμ°μ΄ λ¬Έμμ΄ | ꡬ쑰νλ JSON | ꡬ쑰νλ JSON |
| νΈμ§ λ°©ν₯ | β | μλ°©ν₯ | λ¨λ°©ν₯ (agentβUI) |
| UI μμΉ | μ±ν
λ²λΈ | λ©μΈ UI (νΌ/μλν°) | λ©μμ§ μ¬μ΄ (μΉ΄λ/ν¨λ) |
| μ¦λΆ μ
λ°μ΄νΈ | delta (λ¬Έμ append) | JSON Patch | JSON Patch |
| νμ€ν 리 | role: "assistant" | μνλ‘ λμ | role: "activity" λ©μμ§ |
λΌμ΄νμ¬μ΄ν΄ λ€μ΄μ΄κ·Έλ¨
sequenceDiagram
autonumber
participant Agent
participant UI
participant User
Note over Agent: μμ
μμ
Agent->>UI: ACTIVITY_SNAPSHOT (activityType PLAN)
UI->>User: Plan λ°μ€ λ λλ§
loop μμ
μ§ν
Agent->>UI: ACTIVITY_DELTA (tasks μΆκ°)
UI->>User: Plan λ°μ€ μ€μκ° κ°±μ
end
Note over Agent: μ PlanμΌλ‘ κ΅μ²΄
Agent->>UI: ACTIVITY_SNAPSHOT (replace true)
UI->>User: Plan λ°μ€ μ 체 κ΅μ²΄
Note over Agent: Run μ’
λ£ νμλ role activity λ©μμ§λ‘ νμ€ν 리 보쑴
28κ° νμ± μ΄λ²€νΈ (μΉ΄ν
κ³ λ¦¬λ³)
Lifecycle β 5κ°
| μ΄λ²€νΈ | ν΅μ¬ νλ | μλ―Έ |
|---|
RUN_STARTED | threadId, runId | μμ΄μ νΈ μ€ν μμ |
RUN_FINISHED | threadId, runId | μ€ν μ μ μλ£ |
RUN_ERROR | message, code | μ€ν μ€λ₯ μ’
λ£ |
STEP_STARTED | stepName | νμ λ¨κ³ μμ |
STEP_FINISHED | stepName | νμ λ¨κ³ μλ£ |
Text Message β 4κ°
| μ΄λ²€νΈ | ν΅μ¬ νλ | μλ―Έ |
|---|
TEXT_MESSAGE_START | messageId, role | ν
μ€νΈ μ€νΈλ¦Ό μμ |
TEXT_MESSAGE_CONTENT | messageId, delta | ν
μ€νΈ μ²ν¬ λμ |
TEXT_MESSAGE_END | messageId | ν
μ€νΈ μ€νΈλ¦Ό μλ£ |
TEXT_MESSAGE_CHUNK | messageId, delta | Start+Content+End μλ νμ₯ νΈμ μ΄λ²€νΈ |
| μ΄λ²€νΈ | ν΅μ¬ νλ | μλ―Έ |
|---|
TOOL_CALL_START | toolCallId, toolCallName | ν΄ νΈμΆ μμ |
TOOL_CALL_ARGS | toolCallId, delta | ν΄ μΈμ JSON μ²ν¬ |
TOOL_CALL_END | toolCallId | ν΄ μΈμ μ μ‘ μλ£ |
TOOL_CALL_RESULT | toolCallId, content | ν΄ μ€ν κ²°κ³Ό (μ€νΈλ¦¬λ° μλ) |
TOOL_CALL_CHUNK | toolCallId, delta | Start+Args+End μλ νμ₯ νΈμ μ΄λ²€νΈ |
State Management β 3κ°
| μ΄λ²€νΈ | ν΅μ¬ νλ | μλ―Έ |
|---|
STATE_SNAPSHOT | snapshot | μ 체 μν (νλ‘ νΈμλ κ΅μ²΄) |
STATE_DELTA | delta (RFC 6902) | JSON Patch μ¦λΆ μ
λ°μ΄νΈ |
MESSAGES_SNAPSHOT | messages | λν νμ€ν 리 μ 체 μ€λ
μ· |
Activity β 2κ°
| μ΄λ²€νΈ | ν΅μ¬ νλ | μλ―Έ |
|---|
ACTIVITY_SNAPSHOT | messageId, activityType, content | μ§ν νλ μ 체 μ€λ
μ· (PLAN, SEARCH λ±) |
ACTIVITY_DELTA | messageId, patch | νλ JSON Patch μ¦λΆ μ
λ°μ΄νΈ |
Special β 2κ°
| μ΄λ²€νΈ | ν΅μ¬ νλ | μλ―Έ |
|---|
RAW | event, source | μΈλΆ μμ€ν
μ΄λ²€νΈ pass-through |
CUSTOM | name, value | μ ν리μΌμ΄μ
νμ₯ μ΄λ²€νΈ |
Reasoning β 7κ°
| μ΄λ²€νΈ | ν΅μ¬ νλ | μλ―Έ |
|---|
REASONING_START | messageId | μΆλ‘ νλ‘μΈμ€ μμ |
REASONING_MESSAGE_START | messageId, role | μΆλ‘ λ©μμ§ μ€νΈλ¦Ό μμ |
REASONING_MESSAGE_CONTENT | messageId, delta | μΆλ‘ μ²ν¬ |
REASONING_MESSAGE_END | messageId | μΆλ‘ λ©μμ§ μλ£ |
REASONING_MESSAGE_CHUNK | messageId, delta | νΈμ μ΄λ²€νΈ (μλ νμ₯) |
REASONING_END | messageId | μΆλ‘ νλ‘μΈμ€ μλ£ |
REASONING_ENCRYPTED_VALUE | subtype, entityId, encryptedValue | μνΈνλ chain-of-thought |
Deprecated 5κ°: THINKING_* β λͺ¨λ REASONING_*μΌλ‘ λ체 (v1.0.0 μ κ±° μμ )
3κ°μ§ μ€κ³ ν¨ν΄
1. Start-Content-End ν¨ν΄ (μ€νΈλ¦¬λ°)
TEXT_MESSAGE_START β TEXT_MESSAGE_CONTENT (λ°λ³΅) β TEXT_MESSAGE_END
TOOL_CALL_START β TOOL_CALL_ARGS (λ°λ³΅) β TOOL_CALL_END
REASONING_START β REASONING_MESSAGE_CONTENT β REASONING_END
2. Snapshot-Delta ν¨ν΄ (μν λκΈ°ν)
STATE_SNAPSHOT β STATE_DELTA (λ°λ³΅) β STATE_SNAPSHOT (μ¬λκΈ°ν μ)
3. Lifecycle ν¨ν΄ (μ€ν κ²½κ³)
RUN_STARTED β (Steps/Items) β RUN_FINISHED | RUN_ERROR
AG-UI vs Codex vs Claude CLI μ΄λ²€νΈ λ§€ν
| μλ―Έ | AG-UI | Codex | Claude CLI (NDJSON) |
|---|
| μΈμ
(λν) | threadId (RUN_STARTED ν¬ν¨) | thread.started | νλ‘μΈμ€ spawn |
| ν΄ μμ | RUN_STARTED | turn.started | system { subtype:"init" } |
| ν
μ€νΈ μμ | TEXT_MESSAGE_START | item.started | content_block_start { type:"text" } |
| ν
μ€νΈ μ²ν¬ | TEXT_MESSAGE_CONTENT | item.updated | content_block_delta { type:"text_delta" } |
| ν
μ€νΈ μ’
λ£ | TEXT_MESSAGE_END | item.completed | content_block_stop |
| ν΄ μμ | TOOL_CALL_START | item.started | content_block_start { type:"tool_use" } |
| ν΄ μΈμ | TOOL_CALL_ARGS | item.updated | content_block_delta { type:"input_json_delta" } |
| ν΄ μ’
λ£ | TOOL_CALL_END | item.completed | content_block_stop |
| ν΄ κ²°κ³Ό | TOOL_CALL_RESULT | β | μμ (Claude λ΄λΆ μ€ν) |
| μΆλ‘ μμ | REASONING_START + REASONING_MESSAGE_START | β | content_block_start { type:"thinking" } |
| μΆλ‘ μ²ν¬ | REASONING_MESSAGE_CONTENT | β | content_block_delta { type:"thinking_delta" } |
| μΆλ‘ μ’
λ£ | REASONING_MESSAGE_END + REASONING_END | β | content_block_stop |
| ν΄ μ±κ³΅ | RUN_FINISHED | turn.completed | result { subtype:"success" } |
| ν΄ μ€ν¨ | RUN_ERROR | turn.failed | result { subtype:"error_*" } |
| μΈν°λ½νΈ | (컀μ€ν
μ΄λ²€νΈ) | β | result { subtype:"error_during_execution" } |
| μΉλͺ
μ μλ¬ | RUN_ERROR | error | νλ‘μΈμ€ crash (stdout μ’
λ£) |
| μν λκΈ°ν | STATE_SNAPSHOT / STATE_DELTA | β | β |
| λ¨κ³ κ΅¬λΆ | STEP_STARTED / STEP_FINISHED | β | β |
μ€κ³ μ² ν λΉκ΅
| νλͺ© | AG-UI | Codex | Claude CLI |
|---|
| 컨ν
μΈ νμ
κ΅¬λΆ | νμ
λ³ λ³λ μ΄λ²€νΈ | λ¨μΌ item.* ν΅ν© | content_block.type νλ κ΅¬λΆ |
| ν΄ κ²°κ³Ό | TOOL_CALL_RESULT μ‘΄μ¬ | β | μμ (μμ΄μ νΈ λ΄λΆ μ€ν) |
| μΆλ‘ (Thinking) | 7κ° μ μ© μ΄λ²€νΈ | β | thinking λΈλ‘ νμ
|
| μν λκΈ°ν | μ μ© μ΄λ²€νΈ 3μ’
| β | β |
| Step κ°λ
| λͺ
μμ μ΄λ²€νΈ μ | β | β |
| μΈμ
vs ν΄ λΆλ¦¬ | threadId + runId λͺ
μ λΆλ¦¬ | thread + turn λΆλ¦¬ | νλ‘μΈμ€=μΈμ
, result=ν΄ μ’
λ£ |
μ°Έκ³ λ¬Έμ