Agent-User Interaction Protocol: Architecture, Implementation & Ecosystem
---
ç®åœ
1. åè®®æŠè¿°äžè®Ÿè®¡å²åŠ 2. æ žå¿æ¶æïŒäºä»¶é©±åšçç¥ç»ç³»ç» 3. 17ç§æ åäºä»¶ç±»åå®å šè§£æ 4. ç¶æåæ¥æºå¶ïŒJSON Patchäžäºä»¶æº¯æº 5. äŒ èŸå±å®ç°ïŒSSEãWebSocketäžHTTP 6. äžMCPãA2Açåè®®æ å¯¹æ¯ 7. 代ç å®ç°ïŒä»å ¥éšå°ç产 8. æ§èœäŒåäžæäœ³å®è·µ 9. çæç³»ç»äžæ¡æ¶éæ 10. æªæ¥å±æäžåŒæŸé®é¢
---
äžãåè®®æŠè¿°äžè®Ÿè®¡å²åŠ
1.1 ä»ä¹æ¯AG-UIïŒ
AG-UIïŒAgent-User Interaction ProtocolïŒæ¯äžäžªåŒæŸçã蜻é级çãåºäºäºä»¶çåè®®ïŒçšäºæ ååAI Agentäžçšæ·çé¢ä¹éŽç宿¶éä¿¡ãå®ç±CopilotKitå¢éäº2025幎5æåèµ·å¹¶åŒæºã
1.2 讟计å²åŠ
AG-UIç讟计éµåŸªå äžªæ žå¿ååïŒ
| åå | 诎æ |
|---|---|
| äºä»¶é©±åš | äžåéä¿¡éœä»¥äºä»¶äžºåºæ¬åäœïŒèéäŒ ç»ç请æ±-ååºæš¡åŒ |
| æµåŒäŒå | åçæ¯æå®æ¶æµåŒäŒ èŸïŒå»¶è¿å¯æ§å¶åš100ms以å |
| ç¶æå³æµ | ç¶æåæŽéè¿äºä»¶æµäŒ æïŒèé蜮询æå®æŽå¿«ç § |
| æ¡æ¶æ å ³ | äžç»å®ä»»äœç¹å®æ¡æ¶æææ¯æ |
| äŒ èŸæ å ³ | æ¯æSSEãWebSocketãHTTP/2çå€ç§äŒ èŸæ¹åŒ |
1.3 åè®®å®äœ
åšç°ä»£Agentåè®®æ äžïŒAG-UIå æ®ç¬ç¹çäœçœ®ïŒ
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
â çšæ·çé¢å± â
â ââââââââââââââââââââââââââââââââââââââââââââââââââââââââ â
â â AG-UI (Agent â çšæ·) â â
â â 宿¶äº€äºãç¶æåæ¥ã人æºåäœ â â
â ââââââââââââââââââââââââââââââââââââââââââââââââââââââââ â
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ€
â å·¥å
·äžæ°æ®å± â
â ââââââââââââââââââââ ââââââââââââââââââââââââââââââââ â
â â MCP â â A2A â â
â â Agent â å·¥å
·/æ°æ® â â Agent â Agent â â
â ââââââââââââââââââââ ââââââââââââââââââââââââââââââââ â
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
- MCPïŒAnthropicïŒïŒè§£å³AgentåŠäœè°çšå·¥å ·åè®¿é®æ°æ®
- A2AïŒGoogleïŒïŒè§£å³Agentä¹éŽåŠäœåäœ
- AG-UIïŒCopilotKitïŒïŒè§£å³AgentåŠäœäžçšæ·çé¢äº€äº
äºãæ žå¿æ¶æïŒäºä»¶é©±åšçç¥ç»ç³»ç»
2.1 äºä»¶ä¿¡å°ç»æ
æ¯äžªAG-UIäºä»¶éœéµåŸªæ åä¿¡å°æ ŒåŒïŒ
{
"protocol": "AG-UI/1.0",
"type": "TEXT_MESSAGE_CONTENT",
"timestamp": 1709827200000,
"runId": "run_abc123",
"threadId": "thread_xyz789",
"payload": {
// äºä»¶ç¹å®æ°æ®
},
"extensions": {
// å¯éæ©å±å段
}
}
2.2 æ žå¿ç»ä»¶
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
â AG-UIæ¶æåŸ â
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ€
â â
â ââââââââââââââââ ââââââââââââââââ â
â â Agentå端 ââââââââââºâ AG-UIäºä»¶ â â
â â (ä»»ææ¡æ¶) â â çŒç åš â â
â ââââââââââââââââ ââââââââ¬ââââââââ â
â â â
â ⌠â
â ââââââââââââââââ â
â â äŒ èŸå±æœè±¡ â â
â â (SSE/WS/HTTP)â â
â ââââââââ¬ââââââââ â
â â â
â ⌠â
â ââââââââââââââââ â
â â AG-UIäºä»¶ â â
â â è§£ç åš â â
â ââââââââ¬ââââââââ â
â â â
â ⌠â
â ââââââââââââââââ ââââââââââââââââ â
â â å端UI ââââââââââºâ ç¶æç®¡ç â â
â â (React/Vue) â â (Store) â â
â ââââââââââââââââ ââââââââââââââââ â
â â
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
2.3 äºä»¶çŒç åš
AG-UIæäŸäºæ ååççŒç åšïŒ
# Python瀺äŸ
from ag_ui.core import BaseEvent
from ag_ui.encoder import EventEncoder
encoder = EventEncoder()
# çŒç äºä»¶
encoded = encoder.encode(event)
# çææµåŒååº
yield encoder.encode(TextMessageContentEvent(
type=EventType.TEXT_MESSAGE_CONTENT,
content="Hello"
))
// TypeScript瀺äŸ
import { EventEncoder } from '@ag-ui/core';
const encoder = new EventEncoder();
// çŒç äºä»¶æµ
const stream = encoder.encodeStream(events);
---
äžã17ç§æ åäºä»¶ç±»åå®å šè§£æ
AG-UIå®ä¹äº17ç§ïŒå«ç¹æ®åäœïŒæ åäºä»¶ç±»åïŒå䞺äºå€§ç±»å«ïŒ
3.1 çåœåšæäºä»¶ïŒLifecycle EventsïŒ
| äºä»¶ç±»å | æè¿° | 䜿çšåºæ¯ |
|---|---|---|
RUN_STARTED | Agentæ§è¡åŒå§ | æŸç€ºå èœœç¶æ |
STEP_STARTED | 忥æ§è¡åŒå§ | 倿¥ä»»å¡è¿åºŠè·èžª |
STEP_FINISHED | 忥æ§è¡å®æ | æŽæ°è¿åºŠæ¡ |
RUN_FINISHED | Agentæ§è¡å®æ | æž çå èœœç¶æ |
RUN_ERROR | æ§è¡é误 | é误å€çåæ¢å€ |
RUN_STARTED
âââ STEP_STARTED (æ¥éª€1)
â âââ ...å·¥å
·è°çšäºä»¶...
â âââ STEP_FINISHED
âââ STEP_STARTED (æ¥éª€2)
â âââ ...ç¶æåæŽäºä»¶...
â âââ STEP_FINISHED
âââ RUN_FINISHED
3.2 ææ¬æ¶æ¯äºä»¶ïŒText Message EventsïŒ
| äºä»¶ç±»å | æè¿° | åæ®µ |
|---|---|---|
TEXT_MESSAGE_START | æ¶æ¯åŒå§ | messageId, role |
TEXT_MESSAGE_CONTENT | ææ¬å å®¹çæ®µ | content (token) |
TEXT_MESSAGE_END | æ¶æ¯ç»æ | messageId |
yield encoder.encode(TextMessageStartEvent(
type=EventType.TEXT_MESSAGE_START,
message_id="msg_001",
role="assistant"
))
for token in llm_stream():
yield encoder.encode(TextMessageContentEvent(
type=EventType.TEXT_MESSAGE_CONTENT,
message_id="msg_001",
content=token
))
yield encoder.encode(TextMessageEndEvent(
type=EventType.TEXT_MESSAGE_END,
message_id="msg_001"
))
3.3 å·¥å ·è°çšäºä»¶ïŒTool Call EventsïŒ
| äºä»¶ç±»å | æè¿° | åæ®µ |
|---|---|---|
TOOL_CALL_START | å·¥å ·è°çšåŒå§ | toolCallId, toolName |
TOOL_CALL_ARGS | å·¥å ·åæ°ïŒæµåŒïŒ | toolCallId, delta |
TOOL_CALL_END | å·¥å ·è°çšç»æ | toolCallId |
TOOL_CALL_RESULT | å·¥å ·è¿åç»æ | toolCallId, content |
# 1. åŒå§è°çš
yield encoder.encode(ToolCallStartEvent(
type=EventType.TOOL_CALL_START,
tool_call_id="tool_123",
tool_call_name="fetch_weather"
))
# 2. æµåŒåæ°ïŒå€§åæ°æ¶åçäŒ èŸïŒ
yield encoder.encode(ToolCallArgsEvent(
type=EventType.TOOL_CALL_ARGS,
tool_call_id="tool_123",
delta=json.dumps({"city": "San Francisco"})
))
# 3. è°çšç»æ
yield encoder.encode(ToolCallEndEvent(
type=EventType.TOOL_CALL_END,
tool_call_id="tool_123"
))
# 4. è¿åç»æ
yield encoder.encode(ToolCallResultEvent(
type=EventType.TOOL_CALL_RESULT,
tool_call_id="tool_123",
content="72°F, Sunny"
))
å端å€çïŒ
async function handleToolEvents(event: AGUIEvent) {
switch(event.type) {
case 'TOOL_CALL_START':
showLoadingSpinner(`Calling ${event.tool_call_name}...`);
break;
case 'TOOL_CALL_ARGS':
displayToolParams(event.tool_call_id, event.delta);
break;
case 'TOOL_CALL_RESULT':
displayToolResult(event.content);
hideLoadingSpinner();
break;
}
}
3.4 ç¶æç®¡çäºä»¶ïŒState Management EventsïŒ
è¿æ¯AG-UIæç²ŸåŠç讟计ä¹äžïŒ
| äºä»¶ç±»å | æè¿° | 䜿çšåºæ¯ |
|---|---|---|
STATE_SNAPSHOT | 宿Žç¶æå¿«ç § | åå§åæ¥ãå šéå·æ° |
STATE_DELTA | ç¶æå¢éïŒJSON PatchïŒ | é«é¢æŽæ°ãååçŒèŸ |
MESSAGES_SNAPSHOT | æ¶æ¯åå²å¿«ç § | äŒè¯æ¢å€ |
STATE_SNAPSHOT (åå§ç¶æ)
â
âââ STATE_DELTA (å¢é1)
âââ STATE_DELTA (å¢é2)
âââ STATE_DELTA (å¢é3)
â
âââ STATE_SNAPSHOT (åšææ§å
šéïŒé²æŒç§»)
Python瀺äŸïŒ
# åé宿Žå¿«ç
§
yield encoder.encode(StateSnapshotEvent(
type=EventType.STATE_SNAPSHOT,
snapshot={
"score": 0,
"tasks_completed": 0,
"current_step": "fetching_data",
"document": {
"title": "Draft",
"content": ""
}
}
))
# åéå¢éåæŽïŒJSON Patchæ ŒåŒïŒ
yield encoder.encode(StateDeltaEvent(
type=EventType.STATE_DELTA,
delta=[
{"op": "replace", "path": "/score", "value": 42},
{"op": "replace", "path": "/current_step", "value": "analyzing_data"},
{"op": "add", "path": "/document/content", "value": "New text"}
]
))
3.5 ç¹æ®äºä»¶ïŒSpecial EventsïŒ
| äºä»¶ç±»å | æè¿° | çšé |
|---|---|---|
HUMAN_IN_THE_LOOP / INTERRUPT | æåçåŸ çšæ·èŸå ¥ | 人æºåäœãå®¡æ¹æµçš |
HUMAN_RESPONSE | çšæ·ååº | æ¢å€Agentæ§è¡ |
CUSTOM | èªå®ä¹äºä»¶ | åè®®æ©å± |
RAW | éäŒ å€éšäºä»¶ | ç¬¬äžæ¹ç³»ç»éæ |
# AgentæåçåŸ
çšæ·ç¡®è®€
yield encoder.encode(HumanInTheLoopEvent(
type=EventType.HUMAN_IN_THE_LOOP,
reason="USER_CONFIRMATION_NEEDED",
message="确讀æ§è¡æ°æ®åºå 逿äœïŒ"
))
# ...çåŸ
çšæ·ååº...
# æ¶å°çšæ·ååºåç»§ç»
yield encoder.encode(HumanResponseEvent(
type=EventType.HUMAN_RESPONSE,
response="confirmed"
))
èªå®ä¹äºä»¶ïŒ
# å€Agent亀æ¥
custom_event = CustomEvent(
type=EventType.CUSTOM,
name="AGENT_HANDOFF",
value={
"from_agent": "Planner",
"to_agent": "Executor",
"context": {...}
}
)
---
åãç¶æåæ¥æºå¶ïŒJSON Patchäžäºä»¶æº¯æº
4.1 䞺ä»ä¹éæ©JSON PatchïŒ
AG-UIäœ¿çš JSON PatchïŒRFC 6902ïŒ äœäžºç¶æå¢éæ ŒåŒïŒçžæ¯å®æŽå¿«ç §çäŒå¿ïŒ
| 对æ¯é¡¹ | 宿Žå¿«ç § | JSON Patch |
|---|---|---|
| 垊宜æ¶è | O(宿Žç¶æå€§å°) | O(åæŽå€§å°) |
| å»¶è¿ | é«ïŒå€§ç¶æïŒ | äœïŒæå®ïŒ |
| å²çªæ£æµ | å°éŸ | çžå¯¹å®¹æ |
| çŠ»çº¿æ¯æ | éé¢å€å®ç° | å€©ç¶æ¯æïŒpatchéåïŒ |
| 审计远溯 | éååšå€çæ¬ | 倩ç¶å®æŽåå² |
4.2 JSON Patchæäœç±»å
// æ¿æ¢åŒ
{"op": "replace", "path": "/score", "value": 100}
// æ·»å åæ®µ
{"op": "add", "path": "/tags/-", "value": "new_tag"}
// å é€å段
{"op": "remove", "path": "/temp_data"}
// ç§»åšå段
{"op": "move", "from": "/old_path", "path": "/new_path"}
// æµè¯ïŒæ¡ä»¶æŽæ°ïŒ
{"op": "test", "path": "/version", "value": 1}
4.3 äºä»¶æº¯æºæš¡åŒ
AG-UIçç¶æç®¡çåéŽäº äºä»¶æº¯æºïŒEvent SourcingïŒ æ¶æïŒ
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
â äºä»¶æº¯æºæ¶æ â
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ€
â â
â äŒ ç»æ¹åŒïŒ â
â State ââ⺠State ââ⺠State ââ⺠State â
â â
â äºä»¶æº¯æºïŒ â
â Event1 ââ⺠Event2 ââ⺠Event3 ââ⺠Event4 â
â â â â â â
â ⌠⌠⌠⌠â
â State1 State2 State3 State4 â
â (掟ç) (掟ç) (掟ç) (掟ç) â
â â
â åœåç¶æ = fold(ææäºä»¶) â
â â
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
奜å€ïŒ 1. æ¶éŽæ è¡ïŒå¯ä»¥åå°ä»»æåå²ç¶æ 2. è°è¯å奜ïŒå®æŽçäºä»¶æ¥å¿å¯è¿œæº¯ 3. ååçŒèŸïŒç±»äŒŒGoogle Docsç宿¶åäœ 4. 犻线äŒå ïŒæ¬å°åºçšpatchïŒèçœå忥
4.4 åç«¯ç¶æåå¹¶å®ç°
import { applyPatch } from 'fast-json-patch';
class AGUIStateManager {
private state: any = {};
private eventLog: AGUIEvent[] = [];
handleEvent(event: AGUIEvent) {
switch(event.type) {
case 'STATE_SNAPSHOT':
this.state = event.snapshot;
this.eventLog = [event];
break;
case 'STATE_DELTA':
// åºçšJSON Patch
applyPatch(this.state, event.delta);
this.eventLog.push(event);
break;
}
// éç¥UIæŽæ°
this.notifySubscribers();
}
// æ¶éŽæ
è¡ïŒåå°ç¬¬n䞪äºä»¶
timeTravel(eventIndex: number) {
this.state = {};
for(let i = 0; i <= eventIndex; i++) {
this.handleEvent(this.eventLog[i]);
}
}
}
---
äºãäŒ èŸå±å®ç°ïŒSSEãWebSocketäžHTTP
5.1 Server-Sent Events (SSE) - æšè
AG-UIçåèå®ç°äœ¿çšSSEïŒåå ïŒ
| ç¹æ§ | SSE | WebSocket |
|---|---|---|
| åºäºHTTP | â æ¯ | â åŠ |
| èªåšéè¿ | â å 眮 | â ïž éå®ç° |
| é²ç«å¢å奜 | â æ¯ | â ïž å¯èœåé» |
| ååéä¿¡ | â åŠ | â æ¯ |
| äºè¿å¶æ¯æ | â ïž Base64 | â åç |
| å€æåºŠ | äœ | èŸé« |
# FastAPI + SSE
from fastapi import FastAPI
from fastapi.responses import StreamingResponse
from ag_ui.encoder import EventEncoder
app = FastAPI()
encoder = EventEncoder()
@app.post("/agent")
async def run_agent(request: AgentRequest):
async def event_stream():
# åéäºä»¶æµ
async for event in agent.execute(request):
yield encoder.encode(event) + "\n\n"
return StreamingResponse(
event_stream(),
media_type="text/event-stream",
headers={
"Cache-Control": "no-cache",
"Connection": "keep-alive",
}
)
5.2 WebSocketæ¯æ
对äºéèŠååé«é¢éä¿¡çåºæ¯ïŒ
// å端WebSocket客æ·ç«¯
import { AGUIWebSocketClient } from '@ag-ui/ws';
const client = new AGUIWebSocketClient({
url: 'wss://api.example.com/agent',
onEvent: (event) => {
console.log('Received:', event);
}
});
// åéçšæ·èŸå
¥
client.send({
type: 'USER_MESSAGE',
content: 'Hello, Agent!'
});
5.3 HTTPé¿èœ®è¯¢ïŒFallbackïŒ
@app.post("/agent/poll")
async def poll_agent(request: AgentRequest, last_event_id: str = None):
events = await get_events_since(last_event_id)
return {
"events": events,
"last_event_id": events[-1].id if events else None
}
---
å ãäžMCPãA2Açåè®®æ 对æ¯
6.1 äžå€§åè®®å®æŽå¯¹æ¯
| 绎床 | AG-UI | MCP | A2A |
|---|---|---|---|
| åèµ·æ¹ | CopilotKit (2025) | Anthropic (2024) | Google (2025) |
| æ žå¿é®é¢ | AgentåŠäœäžçšæ·äº€äº | AgentåŠäœè°çšå·¥å · | Agentä¹éŽåŠäœåäœ |
| éä¿¡æš¡åŒ | ååäºä»¶æµ | 请æ±-ååº | 请æ±-ååº + æšé |
| åè®®æ ŒåŒ | JSONäºä»¶ | JSON-RPC 2.0 | JSON-RPC 2.0 |
| äŒ èŸæ¹åŒ | SSE/WebSocket/HTTP | stdio/SSE/HTTP | HTTP/SSE |
| ç¶æç®¡ç | äºä»¶æº¯æº | æ ç¶æ | æç¶æä»»å¡ |
| æµåŒæ¯æ | åç | éè¿SSE | åç |
| äž»èŠçšäŸ | 宿¶UIãååçŒèŸ | å·¥å ·è°çšãæ°æ®è®¿é® | å€AgentçŒæ |
6.2 åè®®åäœæ¶æ
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
â çšæ·å± â
â ââââââââââââ â
â â çšæ· â â
â ââââââ¬ââââââ â
â â â
â ⌠â
â ââââââââââââââââââââââââââââââââââââââââââââââââââââââââ â
â â AG-UI: Agent â UI â â
â â 宿¶äº€äºãç¶æåæ¥ã人æºåäœ â â
â ââââââââââââââââââââââââââââââââââââââââââââââââââââââââ â
â â â
âââââââââââââââââââââââââââŒââââââââââââââââââââââââââââââââââââ€
â Agentå± â
â âââââââââââââââââââ â âââââââââââââââââââââââââââ â
â â Agent A ââââââŒââââºâ Agent B â â
â â (è§åè
) â â â (æ§è¡è
) â â
â ââââââââââ¬âââââââââ â ââââââââââââ¬âââââââââââââââ â
â â â â â
â â ââââââââââââŽâââââââââââ â â
â â â A2A: Agent â Agent â â â
â â â ä»»å¡å§æãåäœ â â â
â â âââââââââââââââââââââââââ â â
â â â â
â ⌠⌠â
â ââââââââââââââââââââââââââââââââââââââââââââââââââââââââ â
â â MCP: Agent â å·¥å
·/æ°æ® â â
â â æ°æ®åºãAPIãæä»¶ç³»ç»ãæçŽ¢ç â â
â ââââââââââââââââââââââââââââââââââââââââââââââââââââââââ â
â â â
âââââââââââââââââââââââââââŒââââââââââââââââââââââââââââââââââââ€
â åºç¡è®Ÿæœå± â
â âââââââââââââââââŒââââââââââââââââ â
â ⌠⌠⌠â
â ââââââââââââ ââââââââââââ ââââââââââââ â
â â PostgreSQLâ â REST API â â File Systemâ â
â ââââââââââââ ââââââââââââ ââââââââââââ â
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
6.3 䜿çšåºæ¯å³çæ
äœ éèŠä»ä¹ïŒ
â
âââ çšæ·çé¢äº€äºïŒ
â âââ äœ¿çš AG-UI
â
âââ è°çšå€éšå·¥å
·/APIïŒ
â âââ äœ¿çš MCP
â
âââ å€äžªAgentåäœïŒ
â âââ äœ¿çš A2A
â
âââ å
šéšéœéèŠïŒ
âââ AG-UI + MCP + A2A ç»å䜿çš
6.4 AG-UI vs A2UI
è¿æäžäžªå®¹ææ··æ·çåè®®ïŒA2UIïŒAgent-to-User InterfaceïŒïŒç±Googleæåºã
| 绎床 | AG-UI | A2UI |
|---|---|---|
| æ§èŽš | äŒ èŸåè®® | UIæè¿°è§è |
| å ³æ³šç¹ | "åŠäœäŒ èŸ" | "æŸç€ºä»ä¹" |
| ç±»æ¯ | HTTP | HTML |
| å ³ç³» | AG-UIå¯ä»¥æ¿èœœA2UIæ°æ® | A2UIå®ä¹UIç»ä»¶æœè±¡ |
AG-UI (äŒ èŸå±)
â
âââ äºä»¶: TEXT_MESSAGE_CONTENT
âââ äºä»¶: STATE_DELTA
âââ äºä»¶: CUSTOM
â
âââ payload: A2UIæè¿°
{
"type": "form",
"fields": [...]
}
---
äžã代ç å®ç°ïŒä»å ¥éšå°ç产
7.1 æå°å¯è¡ç€ºäŸ
å端ïŒPython + FastAPIïŒïŒ
from fastapi import FastAPI
from fastapi.responses import StreamingResponse
from ag_ui import EventEncoder, EventType
import asyncio
app = FastAPI()
encoder = EventEncoder()
@app.post("/chat")
async def chat(message: str):
async def generate():
# åŒå§äºä»¶
yield encoder.encode({
"type": EventType.RUN_STARTED,
"runId": "run_001"
})
# ææ¬æ¶æ¯åŒå§
yield encoder.encode({
"type": EventType.TEXT_MESSAGE_START,
"messageId": "msg_001",
"role": "assistant"
})
# æš¡ææµåŒèŸåº
response = "Hello! I'm an AI assistant."
for token in response.split():
yield encoder.encode({
"type": EventType.TEXT_MESSAGE_CONTENT,
"messageId": "msg_001",
"content": token + " "
})
await asyncio.sleep(0.1)
# ææ¬æ¶æ¯ç»æ
yield encoder.encode({
"type": EventType.TEXT_MESSAGE_END,
"messageId": "msg_001"
})
# è¿è¡ç»æ
yield encoder.encode({
"type": EventType.RUN_FINISHED,
"runId": "run_001"
})
return StreamingResponse(
generate(),
media_type="text/event-stream"
)
å端ïŒReactïŒïŒ
import { useAGUI } from '@ag-ui/react';
function Chat() {
const { messages, sendMessage, isLoading } = useAGUI({
endpoint: '/api/chat'
});
return (
<div className="chat">
{messages.map(msg => (
<div key={msg.id} className={`message ${msg.role}`}>
{msg.content}
</div>
))}
{isLoading && <div className="loading">...</div>}
<input
onKeyPress={(e) => {
if (e.key === 'Enter') {
sendMessage(e.target.value);
e.target.value = '';
}
}}
/>
</div>
);
}
7.2 LangGraphéæ
from langgraph.graph import StateGraph
from ag_ui import EventEncoder, EventType
encoder = EventEncoder()
class AgentState:
messages: list
current_step: str
graph = StateGraph(AgentState)
@graph.node()
def planner(state: AgentState):
# åéæ¥éª€åŒå§äºä»¶
yield encoder.encode({
"type": EventType.STEP_STARTED,
"step": "planning"
})
# æ§è¡è§åé»èŸ...
plan = generate_plan(state.messages)
# åéç¶ææŽæ°
yield encoder.encode({
"type": EventType.STATE_DELTA,
"delta": [
{"op": "add", "path": "/plan", "value": plan}
]
})
# åéæ¥éª€ç»æ
yield encoder.encode({
"type": EventType.STEP_FINISHED,
"step": "planning"
})
return {"current_step": "executing"}
# æŽå€èç¹...
7.3 ç产级é误å€ç
from contextlib import asynccontextmanager
@asynccontextmanager
async def safe_agent_execution():
try:
yield
except AgentError as e:
yield encoder.encode({
"type": EventType.RUN_ERROR,
"error": {
"code": e.code,
"message": str(e),
"recoverable": e.recoverable
}
})
except Exception as e:
# æªç¥é误
yield encoder.encode({
"type": EventType.RUN_ERROR,
"error": {
"code": "INTERNAL_ERROR",
"message": "An unexpected error occurred",
"recoverable": False
}
})
logger.exception("Unexpected error in agent execution")
7.4 人æºåäœå®ç°
import asyncio
class HumanInTheLoopManager:
def __init__(self):
self.pending_confirmations = {}
async def request_confirmation(self, context: dict) -> str:
confirmation_id = generate_id()
# åéäžæäºä»¶
yield encoder.encode({
"type": EventType.HUMAN_IN_THE_LOOP,
"confirmationId": confirmation_id,
"context": context
})
# çåŸ
çšæ·ååº
future = asyncio.Future()
self.pending_confirmations[confirmation_id] = future
try:
response = await asyncio.wait_for(future, timeout=300)
return response
except asyncio.TimeoutError:
raise HumanTimeoutError("User did not respond in time")
def handle_human_response(self, confirmation_id: str, response: str):
if confirmation_id in self.pending_confirmations:
future = self.pending_confirmations.pop(confirmation_id)
future.set_result(response)
---
å «ãæ§èœäŒåäžæäœ³å®è·µ
8.1 æ§èœææ
| ææ | ç®æ åŒ | 诎æ |
|---|---|---|
| éŠåå»¶è¿ | <100ms | ä»åéå°éŠtokenè¿å |
| äºä»¶å€çå»¶è¿ | <10ms | å端å€çå䞪äºä»¶ |
| ç¶æåæ¥åžŠå®œ | åå°90%+ | çžæ¯å®æŽå¿«ç § |
| å¹¶åè¿æ¥ | 10K+ | åæå¡åš |
8.2 äŒåçç¥
1. äºä»¶æ¹å€ç
# å°tokenåå¹¶åéïŒåå°çœç»åŒé
buffer = []
for token in llm_stream():
buffer.append(token)
if len(buffer) >= 5 or time_since_last_send() > 50:
yield encoder.encode({
"type": EventType.TEXT_MESSAGE_CONTENT,
"content": "".join(buffer)
})
buffer = []
2. ç¶æå猩
import gzip
import base64
# å€§ç¶æå¿«ç
§å猩
def compress_large_snapshot(snapshot: dict) -> str:
json_str = json.dumps(snapshot)
compressed = gzip.compress(json_str.encode())
return base64.b64encode(compressed).decode()
3. å¢éæŽæ°çç¥
# é«é¢æŽæ°åå¹¶
class StateBatcher:
def __init__(self, flush_interval=100):
self.pending_deltas = []
self.flush_interval = flush_interval
def add_delta(self, delta):
self.pending_deltas.extend(delta)
if len(self.pending_deltas) >= self.flush_interval:
return self.flush()
return None
def flush(self):
if not self.pending_deltas:
return None
deltas = self.pending_deltas
self.pending_deltas = []
return deltas
4. è¿æ¥æ± 管ç
// åç«¯è¿æ¥å€çš
class AGUIConnectionPool {
private connections: Map<string, Connection> = new Map();
getConnection(endpoint: string): Connection {
if (!this.connections.has(endpoint)) {
this.connections.set(endpoint, new Connection(endpoint));
}
return this.connections.get(endpoint)!;
}
}
8.3 å®å šæäœ³å®è·µ
1. äºä»¶éªè¯
from pydantic import BaseModel, validator
class AGUIEvent(BaseModel):
type: str
timestamp: int
@validator('type')
def validate_type(cls, v):
allowed_types = [t.value for t in EventType]
if v not in allowed_types:
raise ValueError(f"Invalid event type: {v}")
return v
2. éçéå¶
from ratelimit import limits, sleep_and_retry
@sleep_and_retry
@limits(calls=100, period=60)
def send_event(event):
# åéäºä»¶
pass
3. æææ°æ®è¿æ»€
def sanitize_event(event: dict) -> dict:
"""è¿æ»€ææä¿¡æ¯"""
sensitive_keys = ['password', 'token', 'secret', 'api_key']
def redact(obj):
if isinstance(obj, dict):
return {
k: '[REDACTED]' if k in sensitive_keys else redact(v)
for k, v in obj.items()
}
elif isinstance(obj, list):
return [redact(item) for item in obj]
return obj
return redact(event)
---
ä¹ãçæç³»ç»äžæ¡æ¶éæ
9.1 宿¹æ¯æçæ¡æ¶
| æ¡æ¶ | ç¶æ | SDK | ææ¡£ |
|---|---|---|---|
| LangGraph | â 宿¹æ¯æ | Python/TS | docs.ag-ui.com |
| CrewAI | â 宿¹æ¯æ | Python | docs.ag-ui.com |
| Mastra | â 宿¹æ¯æ | TypeScript | docs.ag-ui.com |
| Pydantic AI | â 宿¹æ¯æ | Python | docs.ag-ui.com |
| Google ADK | â 宿¹æ¯æ | Python | docs.ag-ui.com |
| Microsoft Agent Framework | â 宿¹æ¯æ | .NET | docs.ag-ui.com |
| AWS Strands Agents | â 宿¹æ¯æ | Python | docs.ag-ui.com |
| Oracle Agent Spec | â 宿¹æ¯æ | Java | docs.ag-ui.com |
| AG2 | â 宿¹æ¯æ | Python | docs.ag-ui.com |
9.2 SDKæ¯æ
| è¯èš/å¹³å° | SDK | ç¶æ |
|---|---|---|
| TypeScript | @ag-ui/core | â çš³å® |
| Python | ag-ui | â çš³å® |
| Kotlin Multiplatform | ag-ui-4k | â çš³å® |
| Go | ag-ui-go | ð Beta |
| Rust | ag-ui-rs | ð Beta |
| Dart (Flutter) | ag_ui | ð Beta |
| Java | ag-ui-java | ð Beta |
| Ruby | ag-ui-ruby | ð Alpha |
| .NET | AGUI.NET | ð Alpha |
9.3 äŒäžéçšæ¡äŸ
| å ¬åž/ç»ç» | çšäŸ | è§æš¡ |
|---|---|---|
| JPMorgan Chase | 亀æå°å®æ¶é£é©çæ§ | ç产ç¯å¢ |
| Clifford Chance | æ³åŸæä»¶å®¡æ¥å©æ | ç产ç¯å¢ |
| Stanford BMI Lab | ç¥ç»åè¢æ§å¶ç³»ç» | ç ç©¶é¶æ®µ |
| Shopify | å家婿 | è¯ç¹é¶æ®µ |
| Walmart | äŸåºéŸAgent | è¯ç¹é¶æ®µ |
åãæªæ¥å±æäžåŒæŸé®é¢
10.1 路线åŸ
| çæ¬ | ç¹æ§ | é¢è®¡æ¶éŽ |
|---|---|---|
| v1.1 | è¯é³/è§é¢æµæ¯æ | 2025 Q3 |
| v1.2 | 端å°ç«¯å å¯ | 2025 Q4 |
| v2.0 | 蟹çŒè®¡ç®äŒå | 2026 Q1 |
| v2.1 | 倿š¡æäºä»¶ç±»å | 2026 Q2 |
10.2 åŒæŸé®é¢
1. å议治çïŒå€äžªç«äºåè®®ïŒAG-UIãA2UIãMCP-UIïŒåŠäœç»äžæå ±åïŒ 2. å®å šèŸ¹çïŒåŠäœé²æ¢æ¶æAgentéè¿UIäºä»¶è¿è¡æ»å»ïŒ 3. è·šåå Œå®¹ïŒäžåååçå®ç°æ¯åŠååšç¢çåé£é©ïŒ 4. æ§èœèŸ¹çïŒåšè¶ äœå»¶è¿åºæ¯ïŒåŠAR/VRïŒäžèœåŠæ»¡è¶³èŠæ±ïŒ
10.3 åäžç€Ÿåº
| èµæº | éŸæ¥ |
|---|---|
| GitHub | github.com/ag-ui-protocol/ag-ui |
| 宿¹ææ¡£ | docs.ag-ui.com |
| Discordç€Ÿåº | discord.gg/ag-ui |
| AG-UI DojoïŒç€ºäŸïŒ | dojo.ag-ui.com |
| èŽ¡ç®æå | github.com/ag-ui-protocol/ag-ui/blob/main/CONTRIBUTING.md |
éåœïŒå¿«éåèå¡
äºä»¶ç±»åéæ¥è¡š
çåœåšæïŒRUN_STARTED, STEP_STARTED, STEP_FINISHED, RUN_FINISHED, RUN_ERROR
ææ¬æ¶æ¯ïŒTEXT_MESSAGE_START, TEXT_MESSAGE_CONTENT, TEXT_MESSAGE_END
å·¥å
·è°çšïŒTOOL_CALL_START, TOOL_CALL_ARGS, TOOL_CALL_END, TOOL_CALL_RESULT
ç¶æç®¡çïŒSTATE_SNAPSHOT, STATE_DELTA, MESSAGES_SNAPSHOT
ç¹æ®äºä»¶ïŒHUMAN_IN_THE_LOOP, HUMAN_RESPONSE, CUSTOM, RAW
JSON Patchæäœéæ¥è¡š
{"op": "add", "path": "/field", "value": "data"} // æ·»å
{"op": "remove", "path": "/field"} // å é€
{"op": "replace", "path": "/field", "value": "new"} // æ¿æ¢
{"op": "move", "from": "/old", "path": "/new"} // ç§»åš
{"op": "copy", "from": "/src", "path": "/dst"} // å€å¶
{"op": "test", "path": "/field", "value": "expected"} // æµè¯
æå°å¯è¿è¡ä»£ç
# å®è£
npm install @ag-ui/core
pip install ag-ui
# å¿«éåŒå§
npx create-ag-ui-app my-app
---
*æ¥åçæ¬ïŒv1.0* *æåæŽæ°ïŒ2026幎3æ29æ¥* *äœè ïŒAI Research Assistant* *åè®®çæ¬ïŒAG-UI/1.0*
---
#AG-UI #Agentåè®® #MCP #A2A #AIæ¶æ #ææ¯æ·±åºŠè§£æ #å°å¯