AG-UI Protocol 2026: Khi AI Agent Vẽ Giao Diện Trực Tiếp Cho Người Dùng
Posted on: 5/15/2026 9:15:16 AM
Table of contents
- 1. Tại sao cần thêm một giao thức nữa?
- 2. Anatomy của một AG-UI event stream
- 3. State synchronization: phần khó nhất của AG-UI
- 4. UI Invocation: agent "vẽ" component động
- 5. Generative UI: hai trường phái
- 6. So sánh nhanh với các option khác
- 7. Code mẫu: backend FastAPI emit AG-UI stream
- 8. Code mẫu: frontend React với CopilotKit AG-UI
- 9. Timeline tóm tắt: từ chat text đến AG-UI
- 10. Bảo mật và pitfalls khi đưa vào production
- 11. Khi nào không nên dùng AG-UI?
- 12. Kết luận
- Nguồn tham khảo
Suốt năm 2025 và đầu 2026, hai giao thức agent đã thống trị mọi cuộc thảo luận kiến trúc: MCP (Model Context Protocol) chuẩn hóa cách agent gọi tool và truy cập dữ liệu, còn A2A (Agent-to-Agent) chuẩn hóa cách các agent từ nhiều vendor hợp tác với nhau. Nhưng có một lỗ hổng cuối cùng trong tam giác: làm sao agent giao tiếp với chính người dùng theo cách phong phú hơn vài dòng markdown? Đây là khoảng trống mà AG-UI (Agent-User Interaction Protocol) sinh ra để lấp đầy — và chỉ trong vài tháng, nó đã trở thành lớp transport mặc định cho mọi frontend agentic từ CopilotKit, Microsoft Agent Framework cho tới AWS Bedrock AgentCore.
Bài này phân tích AG-UI ở mức kiến trúc — từ 16 loại event chuẩn, kiểu transport SSE/WebSocket, mô hình state synchronization, cho đến cách kết hợp với Vercel AI SDK và A2UI để dựng một stack frontend agent production-ready. Đi kèm là code mẫu, sơ đồ luồng, so sánh thẳng tay giữa các thư viện, và checklist bảo mật cho năm 2026.
1. Tại sao cần thêm một giao thức nữa?
Nhìn lại bức tranh tổng thể của tầng agent:
- MCP giải quyết "agent ↔ tool/data". Một agent gọi
search_jira, agent đó không cần biết Jira được host ở đâu. - A2A giải quyết "agent ↔ agent". Một agent PM giao việc cho agent QA do hãng khác viết, mà không cần share code base.
- AG-UI giải quyết "agent ↔ người dùng". Agent cần stream từng token, đẩy reasoning trace, hiện trạng tool call đang chạy, đồng bộ state, và đôi khi vẽ một component React/Vue ngay trong khung chat để người dùng phê duyệt một hành động — tất cả qua một protocol duy nhất, framework-agnostic.
Trước AG-UI, mỗi frontend phải tự cuộn một schema event riêng. Vercel AI SDK có format của Vercel, LangServe có format khác, OpenAI Assistants stream theo OpenAI, mỗi MCP client tự định nghĩa partial message... Hậu quả là chuyển backend agent là phải viết lại frontend. AG-UI là một "OpenTelemetry cho agent UI": một event spec chung, mọi agent runtime emit theo, mọi frontend lib hiểu được.
flowchart LR
User([Người dùng])
UI[Frontend React/Vue
CopilotKit · AI SDK UI]
AGUI{AG-UI
Event Stream}
Agent[Agent Runtime
LangGraph · CrewAI · Mastra]
A2A[A2A Protocol]
PeerAgent[Agent khác vendor]
MCP[MCP Servers
Tools · Data]
User <-->|"input · feedback"| UI
UI <-->|"SSE/WS events"| AGUI
AGUI <--> Agent
Agent <-->|"agent collab"| A2A
A2A <--> PeerAgent
Agent <-->|"tools"| MCP
style AGUI fill:#e94560,stroke:#fff,color:#fff
style Agent fill:#16213e,stroke:#fff,color:#fff
style A2A fill:#2c3e50,stroke:#fff,color:#fff
style MCP fill:#2c3e50,stroke:#fff,color:#fff
style UI fill:#f8f9fa,stroke:#e94560,color:#2c3e50
2. Anatomy của một AG-UI event stream
AG-UI định nghĩa 16 event types, được chia thành sáu nhóm. Tất cả đều là JSON object, gắn type, messageId, timestamp, và đi qua transport SSE hoặc WebSocket — chọn cái nào tùy yêu cầu bidirectional. Một phiên tương tác điển hình diễn ra như sau:
sequenceDiagram
participant U as User
participant FE as Frontend
participant AG as AG-UI Layer
participant BE as Agent Runtime
U->>FE: gõ "Tạo sprint cho team Q3"
FE->>AG: POST /run (input + thread state)
AG->>BE: invoke agent
BE-->>AG: RUN_STARTED
AG-->>FE: RUN_STARTED
BE-->>AG: TEXT_MESSAGE_CHUNK ("Đang đọc backlog...")
AG-->>FE: stream chunk
BE-->>AG: TOOL_CALL_START (jira.search)
AG-->>FE: hiện spinner "đang gọi Jira"
BE-->>AG: TOOL_CALL_END (kết quả 24 ticket)
BE-->>AG: STATE_DELTA (sprint draft)
AG-->>FE: cập nhật panel bên phải
BE-->>AG: UI_INVOCATION (SprintApprovalCard)
AG-->>FE: render component động
U->>FE: bấm "Approve"
FE-->>AG: USER_RESPONSE (approved=true)
AG-->>BE: tiếp tục flow
BE-->>AG: RUN_FINISHED
Sáu nhóm event cốt lõi:
| Nhóm event | Ví dụ | Mục đích |
|---|---|---|
| Lifecycle | RUN_STARTED, RUN_FINISHED, RUN_ERROR | Đánh dấu vòng đời của một invocation |
| Text | TEXT_MESSAGE_START, TEXT_MESSAGE_CHUNK, TEXT_MESSAGE_END | Stream token-level cho phần lời thoại |
| Tool | TOOL_CALL_START, TOOL_CALL_ARGS, TOOL_CALL_END | Hiển thị tool đang chạy, params, kết quả |
| Reasoning | THINKING_CHUNK | Đẩy reasoning trace (Claude extended thinking, o-series) |
| State | STATE_SNAPSHOT, STATE_DELTA | Đồng bộ shared state giữa agent ↔ UI bằng JSON Patch |
| UI | UI_INVOCATION, UI_INPUT_REQUEST | Yêu cầu frontend render một component và chờ user response |
Mẹo thiết kế
Khi build agent runtime tự custom, bạn không bắt buộc emit cả 16 event. Chỉ cần phát đủ Lifecycle + Text là đã tương thích cơ bản với mọi client AG-UI. Tool/Reasoning/State/UI là "progressive enhancement" — bật khi backend hỗ trợ.3. State synchronization: phần khó nhất của AG-UI
Khác với chat API truyền thống chỉ stream text, AG-UI cho phép agent và UI chia sẻ một shared state JSON. Khi agent cập nhật state (ví dụ thêm một row vào bảng sprint draft), nó emit STATE_DELTA theo định dạng JSON Patch (RFC 6902). Frontend apply patch vào local state, React re-render — không cần round-trip thêm.
// Backend emit (Python pseudocode)
state_delta_event = {
"type": "STATE_DELTA",
"messageId": msg_id,
"timestamp": now(),
"delta": [
{"op": "add", "path": "/sprint/tickets/-", "value": {"id": "AT-128", "title": "Refactor auth"}},
{"op": "replace", "path": "/sprint/totalPoints", "value": 42}
]
}
yield sse_format(state_delta_event)
// Frontend hook (React + CopilotKit AG-UI)
const { state, applyDelta } = useAGUIState<SprintState>({
initial: { sprint: { tickets: [], totalPoints: 0 } }
});
useAGUIEvent("STATE_DELTA", (evt) => {
applyDelta(evt.delta); // apply JSON Patch
});
// state.sprint.tickets tự cập nhật, component re-render
return <SprintBoard tickets={state.sprint.tickets} />;
Lý do AG-UI chọn JSON Patch thay vì replace-toàn-bộ:
- Bandwidth — sprint 100 ticket mà chỉ đổi 1 trường, bạn chỉ gửi 1 patch thay vì 100 record.
- Tính idempotent — nếu client reconnect và replay từ event #42, state vẫn hội tụ đúng.
- UI nuột — React reconciler chỉ patch DOM tại điểm thay đổi, không full re-render board.
Bẫy hay gặp
Đừng gửiSTATE_DELTA ở tần suất quá cao (ví dụ mỗi token). Batching mỗi 50–100ms hoặc mỗi "logical change" (1 ticket được thêm) là sweet spot. Tần suất quá cao sẽ làm React queue render bị nghẹt và animation lag — đặc biệt khi state cây sâu.
4. UI Invocation: agent "vẽ" component động
Đây là tính năng đắt giá nhất của AG-UI — cũng là điểm tách nó khỏi mọi chat protocol truyền thống. Khi agent muốn xin user quyết định, nó không trả về markdown text "bạn có muốn approve không (y/n)?". Thay vào đó nó emit:
{
"type": "UI_INVOCATION",
"componentName": "SprintApprovalCard",
"props": {
"sprintId": "Q3-2026-S1",
"ticketCount": 24,
"estimatedPoints": 42,
"risks": ["3 ticket chưa có acceptance criteria"]
},
"awaitResponse": true,
"responseSchema": {
"type": "object",
"properties": { "decision": { "enum": ["approve", "reject", "edit"] } }
}
}
Frontend đã đăng ký sẵn một map {componentName: ReactComponent}. Nó render SprintApprovalCard ngay trong khung chat, chờ user thao tác, rồi gửi USER_RESPONSE ngược lại agent. Toàn bộ vòng này là human-in-the-loop native — không cần thêm REST endpoint, không cần WebSocket riêng cho approval flow.
flowchart TB
A[Agent ra quyết định:
cần user approve] --> B[Emit UI_INVOCATION
componentName + props]
B --> C{Frontend lookup
componentRegistry}
C -->|found| D[Render Component
truyền props]
C -->|not found| E[Fallback: render JSON]
D --> F[User tương tác]
F --> G[Frontend emit
USER_RESPONSE]
G --> H[Agent resume flow
với input mới]
style A fill:#16213e,stroke:#fff,color:#fff
style B fill:#e94560,stroke:#fff,color:#fff
style C fill:#f8f9fa,stroke:#e94560,color:#2c3e50
style D fill:#2c3e50,stroke:#fff,color:#fff
style E fill:#f8f9fa,stroke:#ff9800,color:#2c3e50
style F fill:#f8f9fa,stroke:#e94560,color:#2c3e50
style G fill:#e94560,stroke:#fff,color:#fff
style H fill:#16213e,stroke:#fff,color:#fff
5. Generative UI: hai trường phái
Có hai cách hiện thực generative UI trên AG-UI, mỗi cách phù hợp một use case:
| Trường phái | Cơ chế | Khi dùng | Rủi ro |
|---|---|---|---|
| Component Registry (closed-set) | FE pre-register một bộ component an toàn, agent chỉ chọn theo tên | Sản phẩm enterprise, brand-consistent, audit chặt | Agent bị giới hạn — không "sáng tạo" UI mới được |
| Declarative JSON UI (open-set, kiểu A2UI) | Agent emit cây JSON mô tả layout, FE có renderer chung dịch ra React/Vue/Web Component | Sản phẩm cần "vẽ" UI lạ on-the-fly: dashboard generator, form builder | Cần kỹ — agent có thể emit cây UI lạ, gây XSS/UX dở |
Trong stack Q2/2026, A2UI (do Google công bố tháng 3/2026) trở thành "payload format" chuẩn cho trường phái thứ hai, còn AG-UI vẫn là "transport". Hai chuẩn này được thiết kế để bổ sung nhau: AG-UI là cái ống, A2UI là cái chui qua ống. CopilotKit, Microsoft Agent Framework, AWS Bedrock AgentCore đều đã ship integration với cả hai.
6. So sánh nhanh với các option khác
| Tiêu chí | AG-UI | Vercel AI SDK UI | OpenAI Assistants Stream | LangServe stream |
|---|---|---|---|---|
| Spec mở, đa runtime | ✅ open spec, agnostic | ⚠️ liên quan Next.js/RSC | ❌ vendor-locked OpenAI | ⚠️ chỉ LangChain |
| State sync (JSON Patch) | ✅ native | ⚠️ phải tự build | ❌ | ❌ |
| UI Invocation native | ✅ | ✅ (Generative UI 3.x) | ❌ (chỉ text + tool) | ❌ |
| Reasoning trace | ✅ THINKING_CHUNK | ✅ (reasoning UI) | ✅ (o-series only) | ⚠️ |
| Bidirectional (WebSocket) | ✅ optional | ❌ SSE only | ❌ | ❌ |
| Integration với MCP/A2A | ✅ design-friendly | ⚠️ partial | ⚠️ | ⚠️ |
7. Code mẫu: backend FastAPI emit AG-UI stream
from fastapi import FastAPI
from fastapi.responses import StreamingResponse
import json, time, uuid
app = FastAPI()
def sse(event: dict) -> str:
return f"data: {json.dumps(event)}\n\n"
async def run_agent(user_input: str):
msg_id = str(uuid.uuid4())
yield sse({"type": "RUN_STARTED", "messageId": msg_id})
# text stream
for token in ("Đang ", "đọc ", "backlog ", "Jira..."):
yield sse({"type": "TEXT_MESSAGE_CHUNK",
"messageId": msg_id, "delta": token})
# tool call
yield sse({"type": "TOOL_CALL_START",
"messageId": msg_id, "toolName": "jira.search"})
tickets = await jira_client.search("sprint=Q3")
yield sse({"type": "TOOL_CALL_END",
"messageId": msg_id, "result": {"count": len(tickets)}})
# state delta — đẩy ticket vào shared state
yield sse({"type": "STATE_DELTA", "messageId": msg_id,
"delta": [{"op": "add", "path": "/sprint/tickets",
"value": [t.to_dict() for t in tickets]}]})
# UI invocation — chờ user approve
yield sse({"type": "UI_INVOCATION", "messageId": msg_id,
"componentName": "SprintApprovalCard",
"props": {"ticketCount": len(tickets)},
"awaitResponse": True})
yield sse({"type": "RUN_FINISHED", "messageId": msg_id})
@app.post("/agui/run")
async def run(req: dict):
return StreamingResponse(run_agent(req["input"]),
media_type="text/event-stream")
8. Code mẫu: frontend React với CopilotKit AG-UI
import { useAGUIRun, useAGUIComponentRegistry } from "@copilotkit/agui-react";
// 1. Đăng ký các component agent có thể gọi
useAGUIComponentRegistry({
SprintApprovalCard: ({ ticketCount, onResponse }) => (
<div className="card">
<p>Agent đã chọn {ticketCount} ticket cho sprint mới.</p>
<button onClick={() => onResponse({ decision: "approve" })}>Approve</button>
<button onClick={() => onResponse({ decision: "reject" })}>Reject</button>
</div>
),
});
// 2. Chạy agent và bind stream vào UI
function SprintPlanner() {
const { messages, state, runAgent, isRunning } = useAGUIRun({
endpoint: "/agui/run",
});
return (
<>
<ChatThread messages={messages} />
<SprintBoard tickets={state.sprint?.tickets ?? []} />
<button onClick={() => runAgent("Plan sprint Q3")}
disabled={isRunning}>
Lên sprint
</button>
</>
);
}
9. Timeline tóm tắt: từ chat text đến AG-UI
10. Bảo mật và pitfalls khi đưa vào production
5 điểm phải nắm trước khi ship
- Component registry phải allowlist tuyệt đối. Đừng cho agent gọi tên component tùy ý — nó có thể bị prompt inject để render component admin nhạy cảm. Khai báo
Map<string, Component>tĩnh ở FE. - Sanitize prop trước render. Mọi string trong props phải coi là user-controlled.
dangerouslySetInnerHTMLlà cấm địa. - Rate-limit STATE_DELTA. Một agent buggy có thể spam patch và làm DOM-thrash. Batch tối thiểu 50ms, drop nếu hàng đợi vượt ngưỡng.
- JSON Patch path phải có schema guard. Đừng tin patch path đến từ agent. Validate qua AJV/Zod trước khi apply để tránh patch ghi đè key admin.
- Resumable stream. Mạng rớt là chuyện thường. Mỗi event nên có sequence number; client resume từ
lastEventIdqua SSE.
Checklist production-ready AG-UI
- ✅ Backend emit đầy đủ Lifecycle + Text + Tool events
- ✅ STATE_DELTA dùng JSON Patch chuẩn RFC 6902, có schema guard
- ✅ Component registry allowlist, props sanitized
- ✅ UI_INVOCATION có
responseSchema, FE validate response - ✅ SSE resumable bằng
Last-Event-Id - ✅ Observability: log mỗi event → Langfuse/OTel để debug
- ✅ Fallback graceful khi component không tồn tại trong registry
- ✅ Rate-limit và budget cho stream (tránh runaway agent loop)
11. Khi nào không nên dùng AG-UI?
Không phải mọi sản phẩm cần AG-UI. Một vài trường hợp nên skip:
- Agent chỉ trả lời câu hỏi đơn lẻ — chat completion API + markdown là đủ, AG-UI overkill.
- Backend đã khóa vào OpenAI Assistants/Vercel SDK — chuyển sang AG-UI lúc này là refactor lớn, hãy cân nhắc kỹ.
- App chỉ chạy server-side, không có UI (cron, batch job) — bạn cần A2A hoặc message queue, không phải AG-UI.
- Domain compliance khắt khe (medical, banking) cấm render UI động — fall back về form pre-built là an toàn hơn.
12. Kết luận
Năm 2026 là năm tam giác giao thức agent hoàn chỉnh. MCP đã trưởng thành, A2A đang lan rộng, và AG-UI là mảnh ghép sau cùng — chuẩn hóa cách agent nói chuyện với người. Lợi thế của AG-UI không nằm ở event nào "mới", mà ở chỗ nó là spec mở duy nhất hợp nhất stream text, tool trace, reasoning, state sync và component invocation vào một protocol — để bất kỳ agent runtime nào cũng có thể plug vào bất kỳ frontend lib nào mà không khóa vendor.
Nếu bạn đang dựng một sản phẩm agent có UI thật — không phải chỉ một console chat — thì lựa chọn rõ ràng cho 2026: backend emit AG-UI, frontend dùng CopilotKit hoặc tự cuộn renderer với event spec chuẩn. Pair thêm A2UI cho generative UI, MCP cho tool, A2A cho liên-agent — bạn đã có một stack production-ready, framework-agnostic, đủ chuẩn để chạy vài năm tới.
Nguồn tham khảo
- AG-UI Overview — Agent User Interaction Protocol
- ag-ui-protocol/ag-ui (GitHub)
- CopilotKit — AG-UI Protocol
- Microsoft Agent Framework — AG-UI Integration
- Amazon Bedrock AgentCore Runtime — AG-UI support
- Google Developers Blog — Introducing A2UI
- A2UI v0.9 — Framework-Agnostic Generative UI
- Vercel AI SDK 3.0 — Generative UI
Disclaimer: The opinions expressed in this blog are solely my own and do not reflect the views or opinions of my employer or any affiliated organizations. The content provided is for informational and educational purposes only and should not be taken as professional advice. While I strive to provide accurate and up-to-date information, I make no warranties or guarantees about the completeness, reliability, or accuracy of the content. Readers are encouraged to verify the information and seek independent advice as needed. I disclaim any liability for decisions or actions taken based on the content of this blog.