CopilotKit 逐 Token 状态流式(State Streaming)实战:让 Agent 工具参数实时写入共享状态并驱动 UI 渲染
【免费下载链接】CopilotKitThe Frontend Stack for Agents & Generative UI. React, Angular, Mobile, Slack, and more. Makers of the AG-UI Protocol项目地址: https://gitcode.com/GitHub_Trending/co/CopilotKit
导读
State Streaming 是 CopilotKit 共享状态体系中最直观、也最"魔法"的能力:当 Agent 还在调用write_document工具时,LLM 为工具参数生成的每一个 token 就已经被镜像进共享状态state.document,前端文档面板会像打字机一样逐字符生长,而不是等整个工具调用结束才一次性刷新。本文以 agno 集成中的 State Streaming 演示 为主体,结合仓库内 SDK 导出、LangGraph 规范后端实现与 E2E 测试,完整拆解这一机制的配置方式、前端订阅原理与底层数据流。读完你将掌握:StateStreamingMiddleware+StateItem的一行式接入方法、useAgent状态订阅的正确姿势,以及如何用"字符计数器 + LIVE 徽标"让逐 Token 增量在 UI 上肉眼可见。
这个 Demo 展示了什么
演示的核心效果一句话概括:工具参数逐 Token 直接流入共享 Agent 状态——文档在 UI 中一个字符一个字符地增长,而此刻工具调用仍在进行中。
具体拆解为三个可见特性:
- 实时文档面板(Live document panel):
state.document被渲染在一个文档视图中,带有一个闪烁光标和一个 "LIVE" 徽标; - Token 级增量(Token-level deltas):Agent 的
write_document工具参数中流式生成的每一个 token,都会被直接转发到document状态键; - 字符计数器(Char counter):一个实时刷新的字符数统计,让逐 Token 流式过程变得一目了然。
如何交互
页面默认展示一个文档面板 + 聊天侧边栏(CopilotSidebar),点击建议芯片(suggestion chip),或直接在输入框发送以下任一提示词即可体验:
- "Write a short poem about autumn leaves."
- "Draft a polite email declining a meeting next Tuesday afternoon."
- "Write a 2-paragraph explanation of quantum computing for a curious teenager."
随后观察文档面板在 Agent 写作过程中被实时填充。这三个提示词并非随手示例,而是通过 suggestions.ts 中useConfigureSuggestions以配置形式注册的,且available: "always"保证随时可用:
useConfigureSuggestions({ suggestions: [ { title: "Write a short poem", message: "Write a short poem about autumn leaves." }, { title: "Draft an email", message: "Draft a polite email declining a meeting next Tuesday afternoon." }, { title: "Explain quantum computing", message: "Write a 2-paragraph explanation of quantum computing for a curious teenager." }, ], available: "always", });前端实现解析:状态订阅驱动逐 Token 重渲染
页面入口与 useAgent 订阅
页面根组件(page.tsx)通过CopilotKit上下文绑定运行时与 Agent:
<CopilotKit runtimeUrl="/api/copilotkit" agent="shared-state-streaming"> <DemoContent /> </CopilotKit>核心逻辑在DemoContent中——它同时订阅状态变化与运行状态变化两类更新事件。前者驱动文档的逐 Token 重渲染,后者在 Agent 开始/结束时切换 "LIVE" 徽标:
const { agent } = useAgent({ agentId: "shared-state-streaming", updates: [UseAgentUpdate.OnStateChanged, UseAgentUpdate.OnRunStatusChanged], }); const agentState = agent.state as StreamingAgentState | undefined; const document = agentState?.document ?? ""; const isRunning = agent.isRunning; return <DemoLayout document={document} isStreaming={isRunning} />;其中StreamingAgentState仅声明了一个可选的document?: string字段——这也是逐 Token 状态流式在类型层面的全部约定:后端往document键写入什么,前端就渲染什么。UseAgentUpdate枚举定义在 packages/react-core/src/v2/hooks/use-agent.tsx,共有三个取值:OnMessagesChanged、OnStateChanged、OnRunStatusChanged,本演示用到了后两者。
文档面板:LIVE 徽标、字符计数器与闪烁光标
DemoLayout 将文档内容与流式状态传入DocumentView,同时挂载CopilotSidebar(defaultOpen={true},输入占位符 "Ask me to write something...")。真正的视觉反馈在 document-view.tsx 中实现:
- LIVE 徽标:仅当
isStreaming为真时渲染,红色胶囊底色 + 白色脉冲圆点,配 "Live" 文案(data-testid="document-live-badge"); - 字符计数器:
const charCount = content.length,以等宽字体显示 "{charCount}chars"(data-testid="document-char-count"); - 闪烁光标:流式期间在文档末尾追加一个
animate-pulse的黑色竖条 span; - 空态占位:文档为空且未流式时,显示斜体提示 "Ask the agent to write something — its output will stream here token by token.";
- 正文渲染:
whitespace-pre-wrap+ 衬线字体,保证换行与缩进忠实呈现。
这些组件注释里写得很直白:"On every streamed token, the parent re-renders this component with a longercontentstring"——即每次 token 到达,父组件就会用更长的字符串重渲染本组件,从而形成逐字符生长的视觉效果。
核心机制:一行 StateStreamingMiddleware 接入逐 Token 状态镜像
配置入口
README 给出的"魔法"只有一段中间件配置:
StateStreamingMiddleware( StateItem( state_key="document", tool="write_document", tool_argument="content", ) )StateItem的三个字段语义如下:
| 字段 | 含义 | 在本 Demo 中的取值 |
|---|---|---|
state_key | 共享状态中要实时更新的键名 | document |
tool | 需要监听其参数流的工具名 | write_document |
tool_argument | 该工具参数中要被逐 Token 镜像进状态的字段 | content |
有与没有中间件的差别
- 没有它:
state.document只会在工具调用结束后更新一次——用户看到的是"文档瞬间出现"; - 有它:LLM 为
content参数生成的每一个 token 都被立即镜像进状态——用户看到的是"文档逐字符被写出"。
前端侧的配合无需任何额外代码:useAgent({ updates: [OnStateChanged, OnRunStatusChanged] })驱动文本与 "LIVE" 徽标的重渲染,agent.isRunning控制光标闪烁。
源码级佐证:SDK 导出与规范后端实现
SDK 导出链
StateStreamingMiddleware与StateItem由 Python SDK 统一导出。在 sdk-python/copilotkit/init.py 中可以看到它们源自ag_ui_langgraph.middlewares.state_streaming模块,并被列入__all__公开 API:
from ag_ui_langgraph.middlewares.state_streaming import ( StateStreamingMiddleware, StateItem, )LangGraph 规范实现:完整接线
仓库中逐 Token 状态流式的规范后端实现位于 showcase/integrations/langgraph-python/src/agents/shared_state_streaming.py,它完整展示了从状态 Schema、工具到中间件装配的整个过程:
- 状态 Schema:
AgentState继承BaseAgentState并声明document: str; - 工具定义:
write_document(document: str, runtime: ToolRuntime)返回Command(update={...}),把完整文档写入document键,同时追加一条ToolMessage("Document written to shared state."); - 系统提示约束:提示词明确要求"任何写作/起草/修改请求都必须调用
write_document,把全文作为document参数传入,不要把文档贴进聊天消息"——这是保证状态流式路径被稳定触发的关键; - 中间件装配:
graph = create_agent( model=ChatOpenAI(model="gpt-5.4"), tools=[write_document], middleware=[ CopilotKitMiddleware(), StateStreamingMiddleware( StateItem( state_key="document", tool="write_document", tool_argument="document", ) ), ], state_schema=AgentState, )该文件注释里还有一条极易踩坑的约束值得单独强调:前端usePredictStateSubscription钩子是按state_key对(部分 JSON 解析后的)工具参数做索引的,因此工具的参数字段名必须与state_key保持一致,逐 Token 增量才能真正落到state.document。也就是说,如果state_key="document",那么工具签名必须是write_document(document=...),参数名对不上则流式增量无法命中状态键(这也是为何本文主文档 agno 示例中tool_argument="content"与 LangGraph 规范实现的tool_argument="document"存在差异——它取决于具体后端工具的参数命名)。
平行实现:CrewAI Flows
同类能力在 CrewAI 集成中以copilotkit_predict_state呈现(见 showcase/integrations/crewai-conversational-flows/src/agents/shared_state_streaming.py),用法语义一致——把StateItem(state_key="document", tool="write_document", tool_argument="document")传给copilotkit_predict_state,Agent 边流式生成边将参数增量预测进共享状态,配套的单元测试 tests/python/test_specialized_flows.py 还验证了预测的StateItem列表、flow.state.document的写入结果与工具结果的 emit 行为。
运行时接线:CopilotRuntime 如何路由到该 Agent
在 agno 集成中,该 Demo 的前端通过/api/copilotkit路由接入后端,接线逻辑在 route.ts 中:
- 使用
@copilotkit/runtime/v2的CopilotRuntime+createCopilotRuntimeHandler,以mode: "single-route"单路由方式服务所有 Agent; - 后端 Agent 以独立进程运行在
AGENT_URL(默认http://localhost:8000),前端通过HttpAgent按 AG-UI 协议代理转发; shared-state-streaming被列入mainAgentNames数组(route.ts 第 67 行),即该名称被别名到主 Agent上,前端按每个 demo 名称注册的工具/组件作用域因此可以正确隔离。
该 Demo 同时在 agno 的 manifest.yaml 中注册:id: shared-state-streaming、路由/demos/shared-state-streaming、标签agent-state,并高亮src/agents/main.py、page.tsx与route.ts三个文件。需要说明的是,Agno 的协议级 parity 清单将该特性列在not_supported_features中,即逐 Token 状态流式的规范后端实现以仓库内 LangGraph 集成(上文shared_state_streaming.py)为准,Agno 集成的此页面主要承担完整前端演示与路由别名的作用。
质量保障:E2E 测试如何验证逐 Token 流式
仓库为这个 Demo 提供了完整的 Playwright 端到端测试(tests/e2e/shared-state-streaming.spec.ts),断言与上述组件一一对应,可作为复现/回归的验收清单:
- 页面装载:
document-view面板可见,"Document" 标题存在,字符计数初始为 "0 chars",侧边栏输入框占位符正确; - 空态:无内容时显示 "Ask the agent to write something" 占位文本,且
document-content不出现; - 建议芯片:三条建议("Write a short poem" / "Draft an email" / "Explain quantum computing")均渲染为按钮;
- 流式触发:发送 "Write a short poem about autumn leaves." 后
document-content出现,且文本长度在 60 秒内增长到超过 10 个字符(toPass轮询断言); - 字符计数增长:发送消息后计数从 0 变为大于 0;
- LIVE 徽标:发送前徽标不可见,流式期间出现;
- 侧边栏回复:
copilot-assistant-message出现,证明 Agent 在流式文档的同时仍在正常对话。
这套断言本质上是把"逐 Token 状态流式"翻译成了可机器验证的行为:document-content从无到有、char-count单调增长、live-badge随运行状态切换。
延伸阅读路径
- 本文主文档:showcase/integrations/agno/src/app/demos/shared-state-streaming/README.md
- 前端四件套:page.tsx、demo-layout.tsx、document-view.tsx、suggestions.ts
- 规范后端实现:showcase/integrations/langgraph-python/src/agents/shared_state_streaming.py
- SDK 导出:sdk-python/copilotkit/init.py
- 订阅 API 定义:packages/react-core/src/v2/hooks/use-agent.tsx
- E2E 测试:showcase/integrations/agno/tests/e2e/shared-state-streaming.spec.ts
【免费下载链接】CopilotKitThe Frontend Stack for Agents & Generative UI. React, Angular, Mobile, Slack, and more. Makers of the AG-UI Protocol项目地址: https://gitcode.com/GitHub_Trending/co/CopilotKit
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考