尧图精选

CopilotKit × Google ADK 对话内 Human-in-the-Loop 实战:从 useHumanInTheLoop 到时间选择卡片的全链路解析

🕒 发布时间:2026/9/13 12:52:12 📁 来源:尧图网络
CopilotKit × Google ADK 对话内 Human-in-the-Loop 实战从 useHumanInTheLoop 到时间选择卡片的全链路解析【免费下载链接】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/CopilotKitCopilotKit 与 Google ADKAgent Development Kit的集成中「对话内 Human-in-the-LoopHITL」是让 Agent 在聊天流中向用户提出结构化问题、并由前端渲染交互卡片收集答案的核心模式。本文以仓库中google-adk集成示例的hitl-in-chat演示为骨架完整拆解其前端工具注册、卡片状态机、ADK 后端无工具 stub 的实现原理并结合仓库内的 QA 清单与 Playwright 端到端测试给出一份可复现、可验收的实战指南。读完本文你将掌握useHumanInTheLoop的完整配置方法、时间选择卡片的实现细节以及如何在单会话内连续触发多次 HITL 流程而不产生状态串扰。一、功能全景前端工具式 HITL 是什么hitl-in-chat演示展示的是「前端工具frontend tool式 HITL」Agent 在对话中需要用户做一次选择例如挑选会议时间但它不依赖后端预定义的工具而是调用一个只存在于前端的工具book_call。前端收到工具调用后在聊天流内渲染一张TimePickerCard时间选择卡片用户点选后选择结果作为**工具执行结果tool result**回传给 AgentAgent 再以自然语言确认。该演示对应的入口页面为 src/app/demos/hitl-in-chat/page.tsx页面挂在/demos/hitl-in-chat路由下前端通过CopilotKit runtimeUrl/api/copilotkit agenthitl-in-chat绑定运行时与 Agent。与「对话内 HITL」相对的是hitl-in-app页面内 HITL而本模式被仓库文档称为 ADK 场景下的「canonical agent asks user a structured question」范式ADK 没有像 LangGraph 那样的原生interrupt()原语聊天侧的工具有声明周期inProgress → executing → complete本身就编码了「暂停—恢复」的语义因此无需中断机制即可实现人与 Agent 的交互。二、全链路架构一次 booking 请求如何走通从一次用户消息到最终确认回复book_callHITL 流程的完整调用链如下用户在聊天输入框发送「Schedule a 1:1 with Alice next week to review Q2 goals.」。Next.js 前端把消息通过/api/copilotkit路由转发到 CopilotKit Runtime。路由实现在 src/app/api/copilotkit/route.ts 中它按 Agent 名称映射到 Python 后端进程默认http://localhost:8000/agent_namehitl-in-chat被映射到后端挂载的hitl-in-chat路径。CopilotKit 运行时通过ag-ui-adk的AGUIToolset()把前端注册的book_call工具定义在请求时注入ADK 后端。ADK 后端hitl_in_chat_book_call_agent收到注入的工具列表后LLM 决定调用book_call并携带topicattendee参数。工具调用流回前端useHumanInTheLoop的render回调渲染TimePickerCard。用户点选一个时间槽或点击「None of these work」取消respond({...})把结构化结果作为 tool result 返回。Agent 读取结构化结果在聊天中给出简短文字确认。值得注意的是ADK 后端没有任何book_call的实现 stub。这一点在 src/agents/hitl_in_chat_book_call_agent.py 的模块 docstring 中明确说明ag-ui-adk中间件会在请求时注入前端的工具列表因此后端只需要tools[AGUIToolset()]这一行即可。三、前端核心useHumanInTheLoop 完整配置book_call工具完全由前端通过useHumanInTheLoop钩子定义位于 src/app/demos/hitl-in-chat/page.tsxuseHumanInTheLoop({ agentId: hitl-in-chat, name: book_call, description: Ask the user to pick a time slot for a call. The picker UI presents fixed candidate slots; the users choice is returned to the agent., parameters: z.object({ topic: z .string() .describe(What the call is about (e.g. Intro with sales)), attendee: z .string() .describe(Who the call is with (e.g. Alice from Sales)), }), render: ({ args, status, respond }: any) ( TimePickerCard topic{args?.topic ?? a call} attendee{args?.attendee} slots{DEFAULT_SLOTS} status{status} onSubmit{(result) respond?.(result)} / ), });四个关键配置项逐一说明agentId与CopilotKit agenthitl-in-chat和CopilotChat agentIdhitl-in-chat保持一致指明该工具归属于哪个 Agent 会话。name工具名book_call即 LLM 在工具调用中引用的名字。后端提示词中明确要求 Agent 调用book_call见下文第四节。description工具的自然语言描述直接进入 LLM 的上下文帮助模型判断何时调用该工具、传什么参数。parameters使用zod声明 JSON Schematopic通话主题与attendee通话对象均有.describe()说明LLM 依据这些描述生成工具参数。render工具调用到达前端时的渲染回调接收{ args, status, respond }三个能力。args是 LLM 生成的参数status是工具生命周期状态respond是把用户选择回传给 Agent 的通道。3.1 工具生命周期状态TimePickerCard组件中导出了状态联合类型见 time-picker-card.tsxexport type TimePickerStatus inProgress | executing | complete;inProgress工具已被 Agent 选中正在等待前端响应。executing前端交互已就绪用户可以操作卡片。complete结果已回传流程结束。TimePickerCard用disabled status ! executing || picked ! null || cancelled控制按钮可用性——只有处于executing且用户尚未做出选择时才允许点击从组件层防止了重复提交。3.2 候选时间槽候选时间槽在前端定义为常量DEFAULT_SLOTS每个槽包含展示标签与 ISO 时间字符串const DEFAULT_SLOTS: TimeSlot[] [ { label: Tomorrow 10:00 AM, iso: 2026-04-19T10:00:00-07:00 }, { label: Tomorrow 2:00 PM, iso: 2026-04-19T14:00:00-07:00 }, { label: Monday 9:00 AM, iso: 2026-04-21T09:00:00-07:00 }, { label: Monday 3:30 PM, iso: 2026-04-21T15:30:00-07:00 }, ];四个槽位以两列网格渲染在卡片上每个按钮带data-testidtime-picker-slot供 QA 清单与自动化测试定位。四、时间选择卡片三态渲染的状态机TimePickerCardtime-picker-card.tsx用两个useState驱动三种渲染状态4.1 待选择态time-picker-card默认渲染标题「Book a call」 主题 「With {attendee}」 「Pick a time:」 2×2 时间槽网格 「None of these work」取消按钮。每个选择按钮点击后onClick{() { setPicked(s); onSubmit({ chosen_time: s.iso, chosen_label: s.label }); }}onSubmit由useHumanInTheLoop的render接线到respond因此{ chosen_time, chosen_label }会被作为结构化 tool result 回传。4.2 已选态time-picker-picked用户点选后卡片切换为绿色边框的成功态显示「Booked for {label}」data-testidtime-picker-picked。4.3 取消态time-picker-cancelled)点击「None of these work」触发onSubmit({ cancelled: true })卡片显示灰色取消文案「Cancelled — no time picked.」data-testidtime-picker-cancelled。回传协议的两种形状由组件 props 类型严格约束time-picker-card.tsxonSubmit: ( result: { chosen_time: string; chosen_label: string } | { cancelled: true }, ) void;Agent 端读取结构化响应后以纯文本回复确认。五、ADK 后端无需工具 stub 的 Agent 定义后端的hitl_in_chat_book_call_agentsrc/agents/hitl_in_chat_book_call_agent.py非常精简_INSTRUCTION ( You help users book an onboarding call with the sales team. When they ask to book a call, call the frontend-provided book_call tool with a short topic and the users name. Keep any chat reply to one short sentence. ) hitl_in_chat_book_call_agent LlmAgent( nameHitlInChatBookCallAgent, modelget_model(), instruction_INSTRUCTION, tools[AGUIToolset()], after_model_callbackstop_on_terminal_text, )要点有三tools[AGUIToolset()]是 CopilotKit 接入 ADK 的唯一接线点来自ag-ui-adk包它把 CopilotKit 的前端工具通道暴露给模型。get_model()动态解析模型当环境变量GOOGLE_GEMINI_BASE_URL设置时例如 Railway 上的 aimock 代理返回指向代理的Gemini实例并附带 header 转发钩子否则返回默认模型字符串gemini-3.1-flash-lite见 src/agents/shared_chat.py。stop_on_terminal_text回调这是每个注册 Agent 共用的after_model_callback用于解决 Gemini 3.1 Flash-Lite 在成功工具调用后不会自然结束 agentic loop、可能无限重复调用同一工具的问题。它只在响应包含纯文本且没有 pending function_call、且finish_reason STOP时设置end_invocation True从而终结本轮调用。仓库也提供了第二个hitl_in_chat_agentsrc/agents/hitl_in_chat_agent.py展示generate_task_steps前端工具思路完全一致后端无自有工具AGUIToolset()注入前端工具LLM 依提示词调用。六、建议 Pill引导用户进入 HITL 流程页面的Chat组件用useConfigureSuggestions配置了两条建议 pillpage.tsxuseConfigureSuggestions({ suggestions: [ { title: Book a call with sales, message: Please book an intro call with the sales team to discuss pricing., }, { title: Schedule a 1:1 with Alice, message: Schedule a 1:1 with Alice next week to review Q2 goals., }, ], available: always, });两条 pill 分别对应两个 HITL 分支sales分支attendee为销售团队与Alice分支attendee为 Alice。available: always表示建议始终可见方便 QA 与用户一键触发。七、QA 验收清单逐步执行指南QA 清单原文见 qa/hitl-in-chat.md前置条件为演示已部署且可访问Agent 后端健康GET /api/copilotkit返回的agent_status为reachable该健康探针由 route.ts 中的GET处理器实现。以下按步骤解读各检查项背后的验证要点。7.1 基础功能检查访问/demos/hitl-in-chat聊天界面应在居中、max-w-4xl宽度容器内加载对应页面外层max-w-4xl布局输入框 placeholder 显示「Type a message」整体加载时间应小于 3 秒。7.2 建议 pill 检查确认两条建议 pill 可见点击「Book a call with sales」后消息「Please book an intro call with the sales team to discuss pricing.」应被发送进聊天。7.3 book_call HITL 主流程Alice 分支发送「Schedule a 1:1 with Alice next week to review Q2 goals.」后60 秒内应出现data-testidtime-picker-card的卡片Agent 推理 工具调用往返的宽松窗口卡片显示「With Alice」即工具参数中的attendee被正确解析并渲染卡片显示四个时间槽data-testidtime-picker-slot点击第一个时间槽后卡片切换到data-testidtime-picker-picked态显示「Booked for ...」30 秒内收到 Agent 的跟进确认确认文案提及 Alice 或已预订的时间标签。7.4 销售团队分支发送「Please book an intro call with the sales team to discuss pricing.」重复上述流程确认卡片内容与最终确认文案均提及「sales team」。两条分支共用同一个book_call工具仅topic/attendee参数不同是验证参数传递正确性的关键场景。7.5 背靠背流程回归检查关键在同一会话、不刷新页面的情况下依次触发 Flow AAlice与 Flow BsalesFlow B 必须渲染出第二张时间选择卡片在第二张卡片中点选时间槽后确认文案必须是销售团队相关的不能与 Alice 流程混淆。这是历史上真实出现过的回归点早期版本中同一会话内第二次 booking 会跳过选择器直接跳到「Booked ...」文本。根因是 aimock 的确认 fixture 以hasToolResult: true为键——第一次流程结束后会话历史中已有 tool message导致第二次用户消息时确认 fixture 抢在 toolCall fixture 之前命中。修复方案是把确认 fixture 改为以toolCallId为键仅当最后一条消息是携带该 id 的工具结果时才匹配并去掉 toolCall fixture 上的hasToolResult: false约束。相关回归说明完整记录在 tests/e2e/hitl-in-chat.spec.ts。7.6 取消路径触发一次 booking 后点击「None of these work」应出现data-testidtime-picker-cancelled态卡片文案为「Cancelled — no time picked.」。此时回传给 Agent 的是{ cancelled: true }Agent 应据此给出对应回复。7.7 错误处理发送空消息应被优雅处理不抛异常正常使用全程控制台无报错、无布局破裂。7.8 预期结果汇总检查项期望值聊天加载3 秒内时间选择卡片出现booking 消息发出后 60 秒内Agent 确认回复点选时间槽后 30 秒内UI 状态无报错、无布局异常八、自动化佐证与 QA 清单一一对应的 Playwright 测试仓库为同一流程提供了完整的端到端测试 tests/e2e/hitl-in-chat.spec.ts可作为 QA 清单的自动化等价物「页面加载与输入框可见」断言page.getByPlaceholder(Type a message)可见「Alice 建议渲染时间选择器」填充消息 → 60 秒内断言time-picker-card可见 → 断言With Alice文案 → 断言至少一个time-picker-slot。该测试还断言Nice to meet you, Alice文本计数为 0用于防止 aimock 的宽泛userMessage: Alicefixture 拦截建议、返回泛化问候而绕过book_call工具的回归「点选时间槽并收到确认」点击第一个 slot → 10 秒内time-picker-picked可见 → 30 秒内断言 Assistant 消息匹配/Booked.*Alice/i「sales 建议端到端」断言卡片出现且包含「Sales team」点选后确认文案匹配/Booked.*sales team/i「背靠背双流程」同一 page 会话内依次跑完 Alice 与 sales 两条流程第二张time-picker-card必须出现toHaveCount(1)且各自的确认文案互不串扰。测试还揭示了一个运行时细节两流程之间需要page.waitForTimeout(1000)让运行时在 HITL 解析后完全稳定因为networkidle可能在 LangGraph 完成线程状态固化之前就 resolve从而引发下一次运行的过期状态竞争——手工 QA 时若遇到第二条流程不弹卡片可参考此做法稍作等待。九、自建此类功能的实操要点如果你想在自己的 CopilotKit ADK 应用中复制这个模式可按下述步骤后端接线参考 docs/setup/human-in-the-loop-setup.mdx安装 AG-UI 桥接包pip install ag-ui-adk后端 Agent 挂上AGUIToolset()在LlmAgent的tools列表中传入AGUIToolset()这是 CopilotKit 前端工具通道暴露给 ADK 模型的唯一入口。前端工具的注入发生在请求时后端无需为前端工具编写 stub前端用useHumanInTheLoop注册工具定义name、description、parameters推荐 zod describe与render回调render中渲染交互组件把用户选择通过respond(...)回传结构化结果如{ chosen_time, chosen_label }或取消信号{ cancelled: true }二选一在 Agent 提示词中明确要求调用该工具并保持文字回复简短别忘了after_model_callbackstop_on_terminal_text若使用 Gemini Flash 系列模型缺少该回调会导致 Agent 在工具成功后无限重复调用同一工具补上data-testid为卡片的待选、已选、取消三态分别打上测试标识便于 QA 与 e2e 断言。需要留意的一个 ADK 平台差异ADK 没有 LangGraph 那样的原生interrupt()原语。对于「图暂停式」的中断场景应改用前端 Promise 式的useFrontendTool模式而在聊天内的结构化提问场景本文章的book_call工具生命周期方案即为推荐范式。十、常见问题与排查速查现象可能原因与排查方向发送 booking 消息后 60 秒内无卡片检查后端GET /api/copilotkit的agent_status是否为reachable确认 Agent 提示词确实指示调用book_call确认前端agentId与后端挂载名一致卡片出现但「With Alice」缺失attendee参数未被 LLM 正确生成检查parameters的.describe()是否足够明确点选后无确认回复检查respond是否正确接线确认stop_on_terminal_text已配置否则工具结果后 Gemini 可能空转第二次 booking 不弹新卡片命中历史 aimock fixture 键冲突回归参照 e2e 测试注释中的修复方案核对 fixture 键设计建议 pill 点了没反应检查是否被 aimock 宽泛 fixture 拦截如userMessage: Alice测试中已用「Nice to meet you」计数为 0 作为护栏本模式的完整参考实现集中在google-adk集成的三处前端演示页 src/app/demos/hitl-in-chat/page.tsx、卡片组件 time-picker-card.tsx、后端 Agent hitl_in_chat_book_call_agent.py配合 QA 清单与 e2e 测试即可完整还原并验证整条链路。【免费下载链接】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),仅供参考
上一篇/下一篇内容由系统自动关联 返回资讯列表 →