CopilotKit × MS Agent Framework (.NET):State Streaming 演示的 QA 验证清单与逐 token 状态流式实现
【免费下载链接】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
本篇以 CopilotKit 仓库中 MS Agent Framework(.NET)showcase 的shared-state-streaming演示 QA 清单为骨架,讲解如何验证"State Streaming"演示的可用性,并深入拆解其底层"逐 token 状态流式"(per-token state streaming)机制:从 .NET 侧的write_document工具、运行时的TOOL_CALL_ARGS → STATE_SNAPSHOT中间件,到前端useAgent驱动的DocumentView。读完后,你既能按清单完成该演示的验收,也能理解它"字符级实时生长"的完整调用链与可执行断言。
一、State Streaming 演示的定位与核心概念
该演示位于 MS Agent Framework(.NET)集成 showcase 内,对应前端路由为/demos/shared-state-streaming。其演示目标用一句可验证的话概括就是:把一次工具调用(tool call)的字符串参数,逐 token 地流式写入到 agent 的共享状态里——文档在 UI 中"逐字符生长",而此刻这次工具调用其实仍在进行中。
- 该演示的自述(见 演示 README)是"Per-token streaming of a tool argument directly into shared agent state",即工具参数被逐 token 镜像进状态键
document。 - QA 清单(qa/shared-state-streaming.md)把这一演示列为
State Streaming,作为整篇验证的主对象。
说明:README 里给出的是 Python 参考实现
StateStreamingMiddleware(StateItem(state_key="document", tool="write_document", tool_argument="content"))。但当前 showcase 的后端是 .NET Agent Framework,运行时在源码注释中明确写道:"the .NET host has nopredict_state_config, so per-token emission is done here on the route"(见 route.ts 第 892–897 行)。也就是说,.NET 这条链路是靠运行时中间件在路由层补出逐 token 快照的,后文会专门拆解。
二、前置条件与运行环境
QA 清单给出的前置条件只有两条,但对照源码后可以落到具体可检查的端点上:
- 演示已部署并可访问:页面挂在
/demos/shared-state-streaming(见 演示 page)。 - Agent 后端健康(检查
/api/health):前端存在 health 路由;同时 CopilotKit 运行时在 GET 探针里会fetch(${AGENT_URL}/health),其中AGENT_URL默认http://localhost:8000(见 route.ts 第 12–13、989–999 行)。
由此可以推断出该演示的运行拓扑:Next.js 前端 + 独立的 .NET Agent 后端(默认 :8000),前端通过/api/copilotkit这条 single-route 运行时以 AG-UI 协议代理到后端/shared-state-streaming路径(见 route.tscreateCopilotRuntimeHandler第 952–969 行 与agents["shared-state-streaming"] = createSharedStateStreamingAgent()第 898 行)。
三、QA 验证清单(继承 QA 文档骨架)
下面完整继承 QA 清单的原始检查项,并逐条对照当前源码给出的"实际可验证锚点"。
1. 基础功能(Basic Functionality)
清单原始步骤为:导航到演示页 → 校验聊天界面以标题State Streaming加载 → 校验输入框 placeholderType a message...可见 → 发送一条基础消息(如Hello! What can you do?)→ 校验 agent 响应。
对照当前实现,可验证的 DOM 锚点来自 Playwright spec:
- 文档面板以
[data-testid="document-view"]挂载,内部标题文案为Document; - 字符计数初始为
0 chars([data-testid="document-char-count"]); - 侧边栏输入框 placeholder 实为
Ask me to write something...(来自 demo-layout.tsx 第 19–25 行)。
⚠️ 清单中写的标题
State Streaming与 placeholderType a message...与当前实现存在偏差(详见第八节差异说明)。验收时建议以 E2E 断言的Document/Ask me to write something...为准。
2. 功能点检查(Suggestions 与"Stub"状态说明)
清单原始条目包含:校验Get started建议按钮可见;并标注"Status: Stub — This demo is currently a stub (TODO: implement)",期望"仅基础 CopilotChat 加载并接受消息、agent 能响应、除聊天外无自定义 UI"。
当前实现里,建议 chips 由 suggestions.ts 通过useConfigureSuggestions({ available: "always" })注入,共三颗(不是单个Get started):
Write a short poem→ "Write a short poem about autumn leaves."Draft an email→ "Draft a polite email declining a meeting next Tuesday afternoon."Explain quantum computing→ "Write a 2-paragraph explanation of quantum computing for a curious teenager."
E2E 中starter suggestions render in the sidebar用例正是逐一断言这三颗按钮可见。这说明当前仓库中的演示已经不是清单所记录的 Stub 状态,它带有完整的自定义DocumentView面板。
3. 异常处理(Error Handling)
清单要求:发送空消息应被妥善处理;正常使用期间不应有控制台错误。对照实现,可验证的行为来自 document-view.tsx 第 54–59 行:当content.length === 0 && !isStreaming时展示占位提示Ask the agent to write something — its output will stream here token by token.,且此时[data-testid="document-content"]不应可见(E2E 的empty state shows placeholder text用例正是这一断言)。空消息不会破坏布局,文档面板会稳定停留在占位态。
4. 预期结果与时间阈值(Expected Results)
清单给出的验收阈值是:聊天 3 秒内加载;agent 10 秒内响应;无 UI 错误或布局错乱。这些是 QA 文档既定的验收标准(非性能基准),可直接作为回归门槛沿用。
四、逐 token 状态流式:.NET 侧write_document工具
演示的"魔法"起点是一个 .NET 工具。在 agent/D5ParityAgents.cs 第 120 行 的CreateSharedStateStreamingAgent()中,注册了一个名为write_document的工具(第 132 行),agent 名称为SharedStateStreamingAgent(第 143 行),其 system instructions(第 144 行)明确要求:
"Whenever the user asks you to write, draft, or revise text, ALWAYS call
write_documentwith the full content as a single string in thedocumentargument. Never paste the document into a chat message directly — the document belongs in shared state and the UI renders it live as you type."
这条指令保证了:模型的产出会被塞进工具的document字符串参数,而不是直接吐进聊天消息气泡。该 agent 在 agent/Program.cs 第 65 行 通过app.MapAGUI("/shared-state-streaming", d5ParityFactory.CreateSharedStateStreamingAgent())挂载到 AG-UI 路径。
五、运行时中间件:把TOOL_CALL_ARGS增量变成STATE_SNAPSHOT
真正让 UI"逐 token 生长"的关键在 CopilotKit 运行时的路由层,核心是 route.ts 第 602–687 行 的createSharedStateStreamingAgent()。其工作机制如下:
- 捕获工具调用开始:当事件
TOOL_CALL_START且toolCallName === "write_document"时,把toolCallId记入集合,并吞掉该事件(文档应活在状态里,而不是聊天 tool-call 气泡里)。 - 缓冲参数增量:对
TOOL_CALL_ARGS事件,把delta追加到argsByToolCallId的 JSON 缓冲中;每追加一次就尝试解码出当前document值。 - 按需发射快照:只有当解码出的文档字符串真的增长/变化时才发射
{ type: STATE_SNAPSHOT, snapshot: { document } },避免结构性 JSON 字符造成冗余快照。 - 清理:
TOOL_CALL_RESULT时移除该toolCallId相关的缓冲与去重记录。
解码的难点是JSON.parse无法解析"流到一半"的 JSON(比如{"document":"Autumn lea)。为此 route.ts 第 524–588 行 的extractStreamingDocument手写了一个"字符串值增量解码器":先用正则定位"document":",再逐字符扫描并处理转义序列(\n、\t、\"、\\、\/等),遇到尚未流完的\uXXXX就等待下一个 delta,遇到未转义收尾引号即判定字符串值完整。这保证了在工具调用结束前,state.document就能逐 token 更新。
对照 README:Python 参考版靠
StateStreamingMiddleware完成同样语义,而 .NET 这条链因为宿主没有对应中间件,改由上述路由层 shim 复现(README 的tool_argument写作content,而 .NET 实际参数名是document,见 D5ParityAgents.cs 第 144 行 与 route.ts 第 533 行 对"document"键的匹配)。
六、前端渲染:useAgent与DocumentView
前端只订阅状态与运行状态两路更新,见 page.tsx 第 30–42 行:
const { agent } = useAgent({ agentId: "shared-state-streaming", updates: [UseAgentUpdate.OnStateChanged, UseAgentUpdate.OnRunStatusChanged], }); const document = (agent.state as StreamingAgentState | undefined)?.document ?? ""; const isRunning = agent.isRunning; return <DemoLayout document={document} isStreaming={isRunning} />;OnStateChanged驱动逐 token 的文档重渲染;OnRunStatusChanged则在 agent 起止时切换 "LIVE" 徽标;agent.isRunning进一步控制光标闪烁。- document-view.tsx 把
state.document渲染成"活文档"面板:LIVE徽标(data-testid="document-live-badge")、字符计数(document-char-count)、以及一个随isStreaming出现的闪烁光标;内容区data-testid="document-content"用whitespace-pre-wrap保留换行。 - 整体布局是"文档面板 + 右侧
CopilotSidebar",侧边栏defaultOpen且 placeholder 为Ask me to write something...(见 demo-layout.tsx 第 19–25 行)。
三者合起来,使得"每收到一个流式 token,父组件就以更长的content字符串重新渲染 DocumentView"——这正是 QA 清单"逐 token"要验证的现象。
七、自动化 E2E:把 QA 清单变成可执行断言
QA 清单的手工检查项,在仓库里有一一映射的可执行 Playwright 用例:tests/e2e/shared-state-streaming.spec.ts。用例覆盖:
page loads with document panel and chat sidebar:面板挂载、Document标题、初始0 chars、侧边栏输入框存在;empty state shows placeholder text:空态占位文案可见、document-content不可见;starter suggestions render in the sidebar:三颗建议按钮可见;sending a message triggers document streaming:发送消息后document-content出现且长度 > 10;character count updates as document streams:字符数从 0 增长;live badge appears while agent is streaming:document-live-badge在 agent 运行时出现;assistant responds in the sidebar chat:侧边栏出现copilot-assistant-message。
这一 spec 事实上就是 QA 清单的"机器可读版本",可作为 CI 回归直接运行,替代人工勾选。
八、QA 清单与当前实现的差异(测试维护提示)
为保证验收口径与现状一致,建议对照源码更新 QA 清单,主要差异点(均可在源码中核对):
- 状态:清单标注 "Status: Stub",但当前实现已具备完整的
DocumentView、LIVE徽标与字符计数,"除聊天外无自定义 UI"的期望已不成立。 - 标题 / placeholder:清单写
State Streaming与Type a message...,实际面板标题为Document、侧边栏 placeholder 为Ask me to write something...。 - 建议按钮:清单写单个
Get started,实际是三颗固定主题 chips(诗 / 邮件 / 量子计算)。
这些差异不影响"逐 token 状态流式"这一核心主题的验证,但直接照抄旧清单会产生误判。建议把第八节的实际锚点回填进 qa/shared-state-streaming.md,并以 Playwright spec 作为权威验收断言。
(注:本文为只读介绍,仅说明查看、运行与配置方式,不改动仓库内容。)
【免费下载链接】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),仅供参考