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

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.

16Loại event chuẩn của AG-UI
3Lớp giao thức agent: MCP · A2A · AG-UI
SSE+WSTransport mặc định, không bắt buộc
2026Bedrock AgentCore native support

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
Hình 1. Tam giác giao thức agent 2026: AG-UI nằm ở cạnh "agent ↔ người".

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
Hình 2. Một vòng tương tác AG-UI điển hình: stream chunk, tool trace, state delta và UI invocation đồng thời.

Sáu nhóm event cốt lõi:

Nhóm eventVí dụMục đích
LifecycleRUN_STARTED, RUN_FINISHED, RUN_ERRORĐánh dấu vòng đời của một invocation
TextTEXT_MESSAGE_START, TEXT_MESSAGE_CHUNK, TEXT_MESSAGE_ENDStream token-level cho phần lời thoại
ToolTOOL_CALL_START, TOOL_CALL_ARGS, TOOL_CALL_ENDHiển thị tool đang chạy, params, kết quả
ReasoningTHINKING_CHUNKĐẩy reasoning trace (Claude extended thinking, o-series)
StateSTATE_SNAPSHOT, STATE_DELTAĐồng bộ shared state giữa agent ↔ UI bằng JSON Patch
UIUI_INVOCATION, UI_INPUT_REQUESTYê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ửi STATE_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
Hình 3. UI Invocation: agent yêu cầu, frontend render, user response quay ngược về agent.

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áiCơ chếKhi dùngRủi ro
Component Registry (closed-set)FE pre-register một bộ component an toàn, agent chỉ chọn theo tênSản phẩm enterprise, brand-consistent, audit chặtAgent 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 ComponentSản phẩm cần "vẽ" UI lạ on-the-fly: dashboard generator, form builderCầ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-UIVercel AI SDK UIOpenAI Assistants StreamLangServe 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 traceTHINKING_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

2023 — Sơ khai
Chat completion chỉ stream text. UI = markdown render. Tool call mới hé lộ trong GPT-3.5.
12/2023 — Vercel AI SDK 3.0
Vercel open-source phần Generative UI từ v0, lần đầu thấy agent "trả về" React Server Component thay vì text.
Q3/2024 — CopilotKit định hình AG-UI
Cộng đồng CopilotKit gom các event SSE phổ biến thành spec ag-ui-protocol đầu tiên, framework-agnostic.
Q1/2026 — Native trên cloud agent runtime
AWS Bedrock AgentCore Runtime (3/2026) thêm hỗ trợ AG-UI native. Microsoft Agent Framework ship integration cùng tháng.
3/2026 — A2UI
Google công bố A2UI — định dạng payload UI declarative, framework-agnostic. AG-UI làm transport, A2UI làm payload. Hai chuẩn bổ sung nhau.
Q2/2026 — Hợp nhất stack
CopilotKit ship A2UI renderer trên AG-UI transport. LangGraph, CrewAI, Mastra đều emit chuẩn AG-UI mặc định. Tam giác MCP/A2A/AG-UI hoàn chỉnh.

10. Bảo mật và pitfalls khi đưa vào production

5 điểm phải nắm trước khi ship

  1. 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.
  2. Sanitize prop trước render. Mọi string trong props phải coi là user-controlled. dangerouslySetInnerHTML là cấm địa.
  3. 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.
  4. 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.
  5. Resumable stream. Mạng rớt là chuyện thường. Mỗi event nên có sequence number; client resume từ lastEventId qua 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