CopilotKit 与 AG-UI:三层拆解、90 行最小示例与真实学费

CopilotKit 与 AG-UI:三层拆解、90 行最小示例与真实学费

我在两个项目(联盟营销控制平面、AI 漫剧产线)里都接了 AG-UI 和 CopilotKit,重构跑了一多半,突然被人问"CopilotKit 到底是什么",发现自己讲不清楚。讲不清楚的根因:三个东西挤在一个名词里。本文把它拆干净——一个三层模型、一份能跑的 90 行示例、一份真金白银换来的学费清单。协议层面的深度研判见 [[07-知识库/技术调研/AG-UI协议深度研判|AG-UI 协议深度研判]],本文只管一件事:让你读完就能动手。


一、结论先行:三层模型

一套 agent 化的前后端,只有三个位置,每个位置有自己的工具:

┌─────────────── 浏览器(前端)───────────────┐
│  按钮、表单、仪表盘(原有 UI,REST/SSE)    │
│  + 聊天窗、审批卡      ←【CopilotKit 装这】│
└───────────────────┬─────────────────────────┘
                    │ 一个 HTTP 接口,跑【AG-UI 事件流】
┌───────────────────┴─────────────────────────┐
│  后端:LangGraph 业务图 + 领域服务          │
└─────────────────────────────────────────────┘
层 是什么 谁家工具 传统项目对位
脸 人看的界面:聊天窗、卡片、流式气泡 CopilotKit(React SDK) Vue + Element Plus
神经 前后端之间的事件格式约定 AG-UI(开放协议) HTTP + REST/JSON 约定
脑子 agent 本体:流程、工具、记忆 LangGraph / Claude Agent SDK / CrewAI…… Java/Python 后端

三句话记住分工:

  1. CopilotKit 是前端组件库——地位类似 Element Plus,装进 React 项目就长出一个 AI 聊天面板。但它比 EP 多一层:组件自带"懂 agent"的运转逻辑(收流式事件逐字渲染、把后端的"停下等人"渲染成审批卡)。
  2. AG-UI 不是软件,是说话格式的约定——规定后端怎么发事件(开跑、逐字说话、报状态、停下来等拍板)。它跑在 HTTP/SSE 上,地位等同于你从前端同学手里接过的那份"REST 接口返回什么 JSON"的约定文档。
  3. 脑子用谁家的 SDK 都行——LangGraph、Claude Agent SDK、CrewAI、Mastra 都提供 AG-UI 适配器(官方集成表里 Microsoft、Google、AWS、Pydantic AI 都在列)。三方互相独立,可以单独换。

一个高频误解先拆掉:"用 CopilotKit 在业务链的任意节点赋能"——坐标错了。CopilotKit 不在业务链上,它整个住在你的网页里;它和业务链的唯一接触点是一个 HTTP 接口。所谓赋能不是往节点里插东西,而是:流水线肯往那条线上广播什么(观测)、人在聊天窗能问到什么(问答)、流水线肯在哪里停下来等人(闸门)。边界是你自己定的,不是 CopilotKit 给你的。

二、AG-UI 长什么样:一组文本帧

协议的全部内容,就是规定线上传输这些帧,按这个顺序:

RUN_STARTED                                      ← 回合开始(threadId/runId 是身份键)
  TEXT_MESSAGE_START                             ← 一条消息开始
  TEXT_MESSAGE_CONTENT(delta) × N                ← 逐字增量(流式的本体)
  TEXT_MESSAGE_END                               ← 消息结束
RUN_FINISHED(outcome=success | interrupt)        ← 回合收尾
任何异常 → RUN_ERROR(之后同一 run 不再发事件)  ← 协议铁律

这是 demo 的真实线上输出(curl 实录,不是文档抄的):

event: RUN_STARTED
data: {"type":"RUN_STARTED","threadId":"t-demo","runId":"r-1"}

event: TEXT_MESSAGE_CONTENT
data: {"type":"TEXT_MESSAGE_CONTENT","messageId":"msg_r-1","delta":"你"}

event: RUN_FINISHED
data: {"type":"RUN_FINISHED","threadId":"t-demo","runId":"r-1","outcome":{"type":"success"}}

和传统 REST 有两处质变:

  1. 服务器主动连续说话——不是一问一答,而是一条持续推送的事件流;
  2. 协议里长了"停下来等人"的语义——RUN_FINISHED 可以带 outcome={type:"interrupt", interrupts:[...]} 收场,客户端用 resume 数组答复后接着跑。这是传统前后端协议里不存在的东西,也是资金闸门能做成标准件的原因。

注意细节:线上字段一律 camelCase(threadId/messageId/runId),Python 侧 pydantic model_dump(by_alias=True) 才能对上官方 zod 校验——这是后面学费清单的第一条。

三、90 行跑通:最小示例

完整代码两个文件(tmp/agui-demo/,随仓可跑:uvicorn server:app 后开 http://localhost:8000/):server.py(FastAPI + 官方 ag-ui-protocol,约 90 行)和 client.html(零依赖手写客户端,故意不用任何 SDK)。三个场景:普通对话、点火闸门(interrupt)、批准续跑(resume)。

后端核心

AG-UI 端点就是一个 async 生成器:

from ag_ui.core import (RunAgentInput, Interrupt,
    RunStartedEvent, RunFinishedEvent, TextMessageContentEvent,
    RunFinishedInterruptOutcome, EventType)
from ag_ui.encoder import EventEncoder

@app.post("/agent")
async def agent(input: RunAgentInput):
    async def gen():
        yield frame(RunStartedEvent(..., thread_id=input.thread_id, run_id=input.run_id))
        yield frame(TextMessageStartEvent(..., message_id=mid, role="assistant"))
        for piece in ["收到点火指令。", "方案:offer GG-32033,预算 $60/天……"]:
            yield frame(TextMessageContentEvent(..., message_id=mid, delta=piece))
        # 资金闸门:run 以 interrupt 结局终结,载荷随行
        yield frame(RunFinishedEvent(...,
            outcome=RunFinishedInterruptOutcome(type="interrupt",
                interrupts=[Interrupt(id="gate-ignition-001", reason="money_gate",
                                      payload={"offer": "GG-32033", "budget": "$60/day"})])))
    return StreamingResponse(gen(), media_type="text/event-stream")

前端核心(零依赖版)

故意用原生 fetch 手工解析,让你看到协议毫无魔法——每个 data: {...} 就是一个 JSON 对象:

const resp = await fetch("/agent", { method: "POST", body: JSON.stringify({
    threadId, runId: "r-" + Date.now(),
    messages: [{ id, role: "user", content: text }],
    state: {}, tools: [], context: [], forwardedProps: {},  // 必填字段,探针实证
    ...(resume ? { resume } : {})                            // 批准后带 resume 重发
})});
// 逐行读 SSE 流,按事件类型各炒各的菜:
case "TEXT_MESSAGE_CONTENT": 气泡追加 ev.delta; break;       // 流式逐字
case "RUN_FINISHED":
    if (ev.outcome?.type === "interrupt") 弹审批卡();        // 停下等人
// 点「批准」→ resume: [{ interruptId, status: "resolved", payload: {approved: true} }]

实测效果(浏览器亲跑,非示意图):

![[90-附件/agui-demo-final.png]]

左侧聊天区:用户说"点火" → 助手流式说明方案 → 弹出审批卡 → 点批准 → 续跑消息流式上屏。右侧事件面板:两轮完整事件序列实时滚动——那一栏滚动的文本,就是 AG-UI 协议的本体。

这个 demo 证明了一件事:协议是薄的一层。90 行后端 + 150 行无依赖前端,就能说全套话。那 CopilotKit 的价值在哪?在把手写的 150 行缩成 10 行,还白得会话管理、中断钩子、组件生态——和"你不会为了一个按钮自己造 antd"是同一个道理。

四、真实项目怎么接(两仓实录)

demo 是骨架,两个生产项目的接线长这样:

AFF(联盟营销控制平面,Vue→React 重构):@copilotkit/react-core 1.70.1 + @ag-ui/client 0.0.59,HttpAgent 直连自研 FastAPI AG-UI 路由(零 Node 进程、零云依赖)。编排图的执行统一走这条通道——useAgent 观测图状态、节点进度变成时间线、点火/KILL/SCALE 闸门变成聊天里的审批卡(ag_ui_langgraph.LangGraphAgent(emit_interrupt_outcome=True)),批准经 resume 直回图续跑。

漫剧(AI 视频产线):主服务保持 stdlib 零框架纪律,AG-UI 走边车——ag-ui-langgraph 挂 FastAPI 可选依赖组,同一张 LangGraph 图、同一个检查点库:批量生产走队列通道,交互式单集走聊天通道。节点代码零改动,progress.report 被包装成 AG-UI 事件流出去。

两仓共用同一条治理总则:agent 可看、可提议、不可花钱——SAFE 工具直调、GUARDED 只产提案卡、MONEY_GATED(真实资金/烧机器)服务端 403 永不进 agent 工具面,只走闸门。这条不在任何协议里,是自己的立法。

五、学费清单(每一行都付过代价)

坑 根因 解法
线上帧被官方客户端拒收 pydantic 默认 model_dump 吐 snake_case;协议是 camelCase model_dump(mode="json", by_alias=True);SSE 帧名用 payload.type 纯值(str(enum) 会带 EventType. 前缀)
聊天气泡集体消失 空 MESSAGES_SNAPSHOT {"messages":[]} 按协议整体替换消息列表 协议正确行为,根因在发送侧:手动 emit 的消息没进图 state;路由层拦空快照
run() 调完没反应 @ag-ui/client 的 run() 返回 Observable,await 它等于静默空跑 用 runAgent(parameters);v2 订阅回调收 ctx 包装,字段在 ctx.event.*
审批后死循环假象 LangGraph falsy resume 坑:Command(resume={}) 空字典按"无 resume"处理,节点重执行又中断 一切 decision 载荷非空,"直接过"用 True 哨兵
官方 pendingInterrupts 恒空 copilotkit Python 薄壳不可传 emit_interrupt_outcome 直用 ag_ui_langgraph.LangGraphAgent;注意开启后 legacy on_interrupt 默认仍同时发,前端别双处消费
demo 当天三个新发现 文档不显眼 RunAgentInput 必填 context(数组!)和 forwardedProps;Interrupt 必填 reason

最后一列的通名就叫探针先行:接任何新接口先写最小探针实证行为,再接线。协议还在 1.0 Draft(未冻结、无兼容承诺),这条纪律不是风格,是生存条件。

六、红线与选型清醒剂

三条结构性事实,写代码之前要知道:

  1. 商业边界在收紧:CopilotKit 代码 MIT,但官方文档已把 selfManagedAgents(HttpAgent 直连的生产路径)标为 "Enterprise Intelligence tier — licensing for production use"。单用户本地场景实际风险低,但别把 <CopilotKit> Provider 越埋越深——保留撤退路径(自研面板、或换 @ag-ui/client 直连 + assistant-ui 组件)。
  2. 治理不对称:MCP、A2A、A2UI 都已进基金会/中立组织,AG-UI 仍是 CopilotKit 一家公司主导。生态是真的(@ag-ui/core 月下载 674 万、三大云厂商一手文档),但节奏和破坏性变更系于一家 25 人公司。
  3. 不是所有东西都该进这条通道:读模型刷新、部署进度这类单向遥测走业务 SSE 更干净;BI 密度区(漏斗图、分镜墙)留在 GUI;sync 轮询包成 agent 会话是过度设计。判据一句话:需要人回应的走 AG-UI,不需要的走 SSE,纯机器执行留在图里。

七、结语

回到最初的问题:CopilotKit 是什么?一个会听 agent 说话的 Element Plus。AG-UI 是什么?前后端之间那份事件格式的接口约定。LangGraph/Claude SDK 是什么?后端造脑子的工具。

三层各造一格,谁也不含着谁。你的业务链不用为任何人重写——它只是多了一扇会说话的窗。


参考

关联

  • [[06-博文/2026/09/当应用长出Agent:未来应用设计范式推演|当应用长出 Agent:未来应用设计范式推演]] —— 范式层(本文是它的落地操作面)
  • [[07-知识库/可复用框架/AG-UI框架设计思想-v1|AG-UI 框架设计思想 v1]] —— 设计语言层(三层模型的体系化定稿:公理/模式语言/案例/自检)
  • [[07-知识库/技术调研/AG-UI协议深度研判|AG-UI 协议深度研判]] —— 协议现状/生态/风险的深度核查(本文第六节的数据源)
  • [[07-知识库/可复用框架/Agent快起Init提示词-v2|Agent 快起 Init 提示词 v2]] —— AG-UI 全栈项目的 Day 0 快起模板(本文三层模型与学费清单已吸收进 v2)
  • [[06-博文/2026/12/AgentHermes:我的个人Agent知识库框架——三项目全息复盘与全量对话考古的快速启动体系|AgentHermes:我的个人 Agent 知识库框架]] —— 两项目的体系背景
  • [[00-工作台/Agent 工程化导航|Agent 工程化导航]]
posted @ 2026-10-08 16:38  AJun816  阅读(30)  评论(0)    收藏  举报