CopilotKit 逐 Token 状态流式(State Streaming)实战:让 Agent 工具参数实时写入共享状态并驱动 UI 渲染
2026/9/12 14:20:25 网站建设 项目流程

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,共有三个取值:OnMessagesChangedOnStateChangedOnRunStatusChanged,本演示用到了后两者。

文档面板:LIVE 徽标、字符计数器与闪烁光标

DemoLayout 将文档内容与流式状态传入DocumentView,同时挂载CopilotSidebardefaultOpen={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 导出链

StateStreamingMiddlewareStateItem由 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、工具到中间件装配的整个过程:

  1. 状态 SchemaAgentState继承BaseAgentState并声明document: str
  2. 工具定义write_document(document: str, runtime: ToolRuntime)返回Command(update={...}),把完整文档写入document键,同时追加一条ToolMessage("Document written to shared state.")
  3. 系统提示约束:提示词明确要求"任何写作/起草/修改请求都必须调用write_document,把全文作为document参数传入,不要把文档贴进聊天消息"——这是保证状态流式路径被稳定触发的关键;
  4. 中间件装配
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/v2CopilotRuntime+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.pypage.tsxroute.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),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询