CopilotKit 只读 Agent Context 集成验证指南:Google ADK 环境下的 useAgentContext 端到端 QA 实践
【免费下载链接】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 与 Google ADK(Agent Development Kit)集成仓库中的一份端到端质量验证文档展开,系统讲解只读 Agent Context(useAgentContext)功能在showcase/integrations/google-adk集成示例中的完整测试路径与底层实现原理。读者将掌握如何验证"前端只读上下文 → CopilotKit 会话状态 → ADK 后端按轮注入系统提示词 → Agent 实时感知最新值"这条数据链路的每个环节,并理解其源码级实现依据,可直接用于自行搭建与复测同类只读上下文功能。
文档定位与验证目标
本验证文档位于 showcase/integrations/google-adk/qa/readonly-state-agent-context.md,属于google-adk集成示例的 QA 测试规范。它覆盖的演示功能在 manifest.yaml 中被登记为readonly-state-agent-context("Frontend provides read-only context to the agent via useAgentContext"),归类为agent-state能力。
该功能解决的核心问题是:前端应用通常掌握用户身份、时区、近期行为等私有信息,但 Agent 默认无从知晓。useAgentContext允许前端把这些 JSON 可序列化数据以"只读"方式发布给 Agent——Agent 每一轮都能读取最新值,但没有能力修改这些值,从而保证"前端仍是这些数据的唯一事实来源(single source of truth)"。
前置条件
依据原文档,执行该 QA 用例前需满足三项条件:
- Demo 已部署且可访问:即
/demos/readonly-state-agent-context页面能够正常加载; - Agent 后端健康:通过
/api/health检查后端存活。仓库中 route.ts 的GET处理函数会向后端AGENT_URL(默认http://localhost:8000)发起带 3 秒超时的健康探测,并返回agent_status: "reachable" | "unreachable (原因)",可作为检查手段; - Agent 已挂载:
readonly_state_agent_context_agent必须在 ADK 服务端注册。它定义在 readonly_state_agent_context_agent.py 中,并在 registry.py 中通过"readonly-state-agent-context": AgentSpec(readonly_state_agent_context_agent)挂载进AGENT_REGISTRY。注册表注释说明:agent_server.py会遍历该注册表,把每个条目以<agent_name>路径挂载为 ADKAgent 中间件,Next.js 的/api/copilotkit路由再把同名请求代理到对应后端路径。
测试目标页面结构
被测试的页面是 page.tsx,其结构包含:
- Agent Context 卡片(
data-testid="context-card"):由 demo-layout.tsx 渲染,分为 Identity(身份)、Recent Activity(近期活动)、Published Context(已发布上下文 JSON 预览)三个区域; - 角落的弹出式聊天窗口:
<CopilotPopup>组件,占位文本为 "Ask about your context..."; - 描述文本:页面标题下方明确写道 "Edit fields below and watch the data flow into the agent. The agent can read this context, butcannot modifyit.",即该卡片与聊天窗口构成一个可视化的"上下文监视器(Agent Context Inspector)"。
页面同时通过useConfigureSuggestions注册了三条建议快捷语(见 suggestions.ts),并配置为available: "always"。
测试步骤详解
以下步骤完整继承自原 QA 文档,并结合仓库源码标注了对应的可验证点。
步骤 1:基础功能
- 导航到
readonly-state-agent-context演示页面; - 验证 "Agent Context" 卡片可见(
data-testid="context-card")——该卡片在 demo-layout.tsx 中以data-testid="context-card"标注; - 验证描述文本引用了
useAgentContext:"Edit fields below and watch the data flow into the agent. The agent can read this context, but cannot modify it."; - 验证弹出式聊天在角落渲染,占位文本为 "Ask about your context..."——对应 page.tsx 中
CopilotPopup的labels={{ chatInputPlaceholder: "Ask about your context..." }}配置; - 通过聊天发送一条基础消息(如 "Hello");
- 验证 Agent 以文本消息回复。
步骤 2:特性专项检查
初始上下文状态
- 验证 Name 输入框(
data-testid="ctx-name")默认为 "Atai"——对应 page.tsx 中useState("Atai")的初始值; - 验证 Timezone 下拉框(
data-testid="ctx-timezone")默认为 "America/Los_Angeles"——对应同文件useState("America/Los_Angeles"); - 验证 Timezone 下拉框提供六个时区选项:
America/Los_Angeles、America/New_York、Europe/London、Europe/Berlin、Asia/Tokyo、Australia/Sydney。这些常量定义在 demo-layout.tsx 的TIMEZONES数组中,全部为 IANA 时区标识; - 验证 Recent Activity 复选框列表包含五项:"Viewed the pricing page"、"Added 'Pro Plan' to cart"、"Watched the product demo video"、"Started the 14-day free trial"、"Invited a teammate"——对应
ACTIVITIES常量(demo-layout.tsx); - 验证默认勾选两项活动:"Viewed the pricing page"(
ACTIVITIES[0])与 "Watched the product demo video"(ACTIVITIES[2])——对应 page.tsx 的初始 state; - 验证 Published Context JSON 预览(
data-testid="ctx-state-json")显示{ "name": "Atai", "timezone": "America/Los_Angeles", "recentActivity": [...] }——该 JSON 由 demo-layout.tsx 中的publishedContext对象经JSON.stringify(publishedContext, null, 2)渲染,任何 state 变化都会立即反映其中。
建议快捷语(Suggestions)
- 验证 "Who am I?" 建议可见;
- 验证 "Suggest next steps" 建议可见;
- 验证 "Plan my morning" 建议可见。
三条建议分别映射到不同的实际消息体(见 suggestions.ts):
| 建议标题 | 实际发送给 Agent 的消息 |
|---|---|
| Who am I? | "What do you know about me from my context?" |
| Suggest next steps | "Based on my recent activity, what should I try next?" |
| Plan my morning | "What time is it in my timezone and what should I do for the next hour?" |
Agent 读取用户名(useAgentContext)
- 点击 "Who am I?" 建议(或直接询问 "What is my name?");
- 验证 Agent 回复中以 "Atai" 称呼用户;
- 将 Name 输入框(
data-testid="ctx-name")修改为 "Jamie"; - 验证 Published Context JSON 更新为
"name": "Jamie"; - 再次询问 "What is my name?";
- 验证 Agent 此时回复 "Jamie"(而非 "Atai")。
这一步验证了上下文是按轮实时生效的:修改立即反映在 JSON 预览中,且 Agent 下一轮回复即使用新值,不存在"陈旧上下文(stale context)"。
Agent 读取时区
- 将 Timezone 下拉框(
data-testid="ctx-timezone")切换为 "Asia/Tokyo"; - 验证 Published Context JSON 更新为
"timezone": "Asia/Tokyo"; - 点击 "Plan my morning" 建议;
- 验证 Agent 在讨论时间时引用 Tokyo / JST / Asia/Tokyo。
注意这里的描述语义:useAgentContext在 page.tsx 中注册时附带的description为 "The user's IANA timezone (used when mentioning times)",这正是引导 LLM 在涉及时间的话题中使用该字段的提示语义。
Agent 读取近期活动
- 取消所有默认活动,仅勾选 "Started the 14-day free trial" 和 "Invited a teammate";
- 验证 Published Context JSON 显示新的
recentActivity数组; - 点击 "Suggest next steps" 建议;
- 验证 Agent 的回复引用 trial 和/或 invited-teammate 相关活动(而不再引用 pricing page 或 demo video)。
这一步验证了数组型上下文值的动态替换:Agent 每一轮拿到的都是最新的活动列表,且只依据当前可见的活动生成建议。
步骤 3:错误处理
- 将 Name 输入框清空(空字符串)后询问 "What is my name?"——Agent 应优雅处理(不崩溃)。前端层面,demo-layout.tsx 对空用户名显示 "Anonymous" 兜底,头像取
userName.charAt(0).toUpperCase() || "?";后端层面,_inject_context对空值条目会跳过(见下文原理); - 发送空聊天消息——输入应被拒绝且不报错;
- 验证正常使用过程中无控制台错误;
- 验证 Agent无法修改上下文值(Name / Timezone / Activity 复选框始终由用户控制)。
预期结果
原文档明确列出以下验收标准:
- 上下文卡片与聊天在 3 秒内加载完成;
- Agent 在 10 秒内响应;
- 每次对 Name / Timezone / Recent Activity 的修改都立即反映在 Published Context JSON 中;
- Agent 每一轮的回复都反映当前上下文值(无陈旧上下文);
- 无 UI 错误或布局破坏。
底层实现原理:从 useAgentContext 到 ADK before-model 回调
理解测试步骤背后的机制,需要追踪这条完整链路。QA 文档断言的所有"实时性"与"只读性"行为,都能在源码中找到对应实现。
前端:useAgentContext 钩子
演示页面通过三次调用发布上下文(page.tsx):
useAgentContext({ description: "The currently logged-in user's display name", value: userName }); useAgentContext({ description: "The user's IANA timezone (used when mentioning times)", value: userTimezone }); useAgentContext({ description: "The user's recent activity in the app, newest first", value: recentActivity });钩子本身实现在 packages/react-core/src/v2/hooks/use-agent-context.tsx。其关键行为包括:
- 入参类型:
AgentContextInput由description(人类可读的描述)与value(任意JsonSerializable值)组成; - 值序列化:非字符串的
value通过JSON.stringify转为字符串后发布——这就是recentActivity数组能够进入 Agent 视线的原因; - 生命周期管理:钩子在
useLayoutEffect中调用copilotkit.addContext({ description, value })注册上下文,并在 effect 清理时调用removeContext(id)反注册。当description或序列化后的stringValue变化时 effect 重跑,实现"改即发布、即时生效"。
中间层:上下文如何进入会话状态
依据 agent_config_agent.py 的模块注释(同一机制的姊妹实现),useAgentContext发布的条目经 ag-ui-adk 中间件落地为state["copilotkit"]["context"],是一个{description, value}字典的列表——同一页面上多个组件可以各自发布,条目共存于该列表。
后端:before_model_callback 按轮注入
ADK 侧的LlmAgent配置在 readonly_state_agent_context_agent.py:
readonly_state_agent_context_agent = LlmAgent( name="ReadonlyStateAgentContextAgent", model=get_model(), instruction=_INSTRUCTION, tools=[AGUIToolset()], before_model_callback=_inject_context, after_model_callback=stop_on_terminal_text, )_inject_context是整条链路的核心,它实现了三个关键设计:
- 状态容错:从
callback_context.state中读取state["copilotkit"]["context"]时,对state缺失、非 dict、context非 list 等情况逐级兜底为空列表并记录 warning 日志,避免形变状态导致请求失败; - 格式化注入块:
_format_context把条目列表渲染为以固定签名[agent-context] frontend-supplied context:开头、以结束标记Treat this context as read-only background information.结尾的文本块,每个条目一行- {description}: {value};无有效条目时返回None不注入; - 剥离旧块(strip-prior-block):注入前先在
system_instruction中查找上一轮的签名与结束标记,找到则只把旧块从原文中切除并保留头部与尾部,再拼接新块。若签名存在但找不到结束标记,则保持原文不动并告警,避免误删用户内容。这一步是"无陈旧上下文"验收标准的实现保障——每一轮都重建上下文块,而不是累加。
同时注意注入块末尾那句结束标记本身就是一条指令:"Treat this context as read-only background information."——它与静态_INSTRUCTION("The frontend passes read-only context entries via useAgentContext; they are added to your system prompt every turn. Use them when relevant.")共同构成了"Agent 可读但不可改"的行为约束,这也是 QA 文档"验证 Agent 无法修改上下文值"一项的提示词级保障。
路由层:前后端名称映射
route.ts 中的agentNames数组包含"readonly-state-agent-context",每个名称在buildAgents中映射为一个HttpAgent({ url: \${AGENT_URL}/${name}` }),即前端请求经 CopilotRuntime 代理到http://localhost:8000/readonly-state-agent-context`,与注册表挂载路径一一对应。
自动化验证支撑
仓库为同一套用例提供了 Playwright 自动化测试:tests/e2e/readonly-state-agent-context.spec.ts。测试文件的头部注释明确指出它以本文对应的 QA 文档为参考("QA reference: qa/readonly-state-agent-context.md"),可以视为该 QA 规范的机器可执行版本。值得关注的点包括:
- 确定性断言:例如 "Who am I?" 建议点击后断言助手回复以 "I see you're Atai" 开头、"Suggest next steps" 后断言 "Since you recently viewed the pricing page and watched the product demo video"——这些回复由 aimock 夹具(
showcase/aimock/d5-all.json)固定,保证 CI 中稳定;而在 Railway 部署环境下相同提示词会得到真实 LLM 回复,从而端到端证明useAgentContext接线正确; - 覆盖与 QA 文档对应:测试覆盖了页面加载、编辑 name/timezone 后 JSON 预览更新、建议点击后身份识别、默认勾选活动、活动驱动回复等与 QA 步骤一一对应的场景;
- 严格的 testid 契约:测试断言了
context-card、ctx-name、ctx-timezone、ctx-state-json、identity-name、identity-timezone、identity-avatar、activity-*等 testid,QA 手工用例与自动化用例共享同一套 DOM 契约,二者互为校验。
延伸:与可写共享状态的对比
仓库中还提供了shared-state-read、shared-state-read-write、shared-state-streaming等共享状态演示(见 manifest.yaml),它们走的是agent.setState/PredictStateMapping的可写或双向通道。本 QA 文档对应的readonly-state-agent-context恰好与之形成对照:useAgentContext是"前端到 Agent"的单向、只读数据通道,前端保留数据所有权,Agent 只被授权读取。选择哪种模式取决于数据所有权需求——当 UI 状态必须由用户界面独占控制、Agent 仅需知情时,只读上下文是更安全、更符合直觉的方案。
小结
本文以 readonly-state-agent-context.md 为骨架,完整继承了其前置条件、三大类测试步骤与预期结果,并结合仓库源码解释了每条断言背后的实现依据:前端useAgentContext的注册与清理、中间层对state["copilotkit"]["context"]的落地、ADKbefore_model_callback的按轮注入与旧块剥离、以及 Playwright 自动化用例对同一契约的机器化验证。对于需要在 CopilotKit 生态中实现"前端只读上下文供给 Agent"的开发者,该 QA 文档与其配套源码是一份可直接复用的参考实现与验收清单。
【免费下载链接】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),仅供参考