CopilotKit 推理消息零配置渲染实战:Google ADK 集成中的 reasoning-default Demo 与 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)集成中的reasoning-defaultDemo,讲解如何在不编写任何自定义渲染组件的前提下,让 Agent 的推理(reasoning/thinking)过程以内置可折叠卡片的形式呈现在聊天界面中。读完本文,你将掌握 reasoning 消息在前端零配置渲染的完整链路——从 ADK 后端的 Gemini thinking 配置、到 v2 React Core 内置CopilotChatReasoningMessage组件的底层实现、再到该功能的 QA 验证清单与 E2E 自动化断言方法。
一、Demo 定位:一个用于验证"默认推理渲染"的 QA 单元
在 showcase/integrations/google-adk 中,reasoning-default与reasoning-custom是一对刻意设计的对照 Demo,其 QA 文档位于 qa/reasoning-default.md。
文档开篇明确指出其定位:
该 Demo 验证内置的
CopilotChatReasoningMessage在没有自定义 slot 的情况下也能正常渲染,因此无需维护完整的人工检查清单(full manual checklist)。
这意味着该页面承担的是**功能验证单元(column)**的职责:它不追求花哨的定制外观,而是为 CI 与人工 QA 提供一个稳定、可重复的基准——证明 reasoning 渲染能力是 CopilotKit 的"开箱即用"能力,而非某个 Demo 手工拼装出来的特性。
二、后端支撑:Gemini thinking 模式如何产生 reasoning 消息
reasoning 渲染的前提是后端真的把推理过程作为独立消息流式输出。在 src/agents/shared_chat.py 中,build_thinking_chat_agent工厂函数负责构造带 thinking 能力的 ADKLlmAgent:
def build_thinking_chat_agent( *, name: str, instruction: str, model: str = DEFAULT_MODEL, ) -> LlmAgent: return LlmAgent( name=name, model=get_model(model), instruction=instruction, tools=[AGUIToolset()], generate_content_config=types.GenerateContentConfig( thinking_config=types.ThinkingConfig( include_thoughts=True, thinking_budget=-1, ), ), after_model_callback=stop_on_terminal_text, )关键参数说明:
model:默认模型为gemini-3.1-flash-lite(见 shared_chat.py#L44),通过get_model()解析。当设置了GOOGLE_GEMINI_BASE_URL环境变量时,会返回一个指向 aimock 代理的Gemini实例(用于 Railway 部署的确定性录制回放),否则返回普通模型字符串。include_thoughts=True:让 Gemini 在生成答案的同时,以thought=True的 part 输出推理过程。从源码注释可以确认,ADK 会把这些 thought parts 通过 ag-ui 协议转发为 reasoning chunk,从而被 v2 前端识别。thinking_budget=-1:-1 表示让模型自行决定推理投入的算力,不人为限制。after_model_callback=stop_on_terminal_text:这是该包内所有注册 Agent 共享的终止条件回调。它解决 Gemini 3.1 Flash-Lite 在成功工具调用后不会自然结束 agentic 循环的问题——回调检查每个非 partial 的模型响应,仅当响应含文本、无待处理function_call且finish_reason为STOP时才设置end_invocation = True终止循环,否则跳过。同时它守护了 thinking 模式下的双 chunk 结构:Gemini 在 thinking 模式下会把一轮 turn 拆成"文本 chunk"与"function_call chunk"两个非 partial 响应,若不加finish_reason守卫,文本 chunk 会触发提前终止,导致后续工具链断裂。
Agent 注册:两个 Demo 共享同一后端
在 src/agents/registry.py#L154-L156 中可以看到:
# ----- Reasoning demos ----- "reasoning-custom": AgentSpec(_thinking_chat), "reasoning-default": AgentSpec(_thinking_chat),reasoning-default与reasoning-custom都解析到同一个_thinking_chatAgent。也就是说,前后端唯一的差异只在前端是否覆盖reasoningMessageslot,这为对照实验提供了干净的变量控制:后端能力完全相同,差的只是前端一行渲染配置。
三、前端零配置:完整可复现的 Demo 源码
src/app/demos/reasoning-default/page.tsx 的完整实现仅 40 行,核心代码全部来自@copilotkit/react-core/v2:
"use client"; import { CopilotKit, CopilotChat } from "@copilotkit/react-core/v2"; import { useReasoningDefaultSuggestions } from "./suggestions"; const AGENT_ID = "reasoning-default"; export default function ReasoningDefaultDemo() { return ( <CopilotKit runtimeUrl="/api/copilotkit" agent={AGENT_ID}> <div className="flex justify-center items-center h-screen w-full"> <div className="h-full w-full max-w-4xl"> <Chat /> </div> </div> </CopilotKit> ); } function Chat() { useReasoningDefaultSuggestions(); return <CopilotChat agentId={AGENT_ID} className="h-full rounded-2xl" />; }逐行解读其"零配置"含义:
<CopilotKit runtimeUrl="/api/copilotkit" agent={AGENT_ID}>:Provider 指向同一个运行时 API 路由(与reasoning-custom完全相同),agent指定后端 Agent 名。<CopilotChat agentId={AGENT_ID} ...>:直接使用预构建的CopilotChat组件,没有传messageView属性,即不覆盖任何消息渲染槽位。reasoning 消息由此交给 CopilotKit 内置的CopilotChatReasoningMessage渲染。useReasoningDefaultSuggestions():来自同目录的 suggestions.ts,通过useConfigureSuggestions注册一个"Show reasoning"建议按钮:
export function useReasoningDefaultSuggestions() { useConfigureSuggestions({ suggestions: [ { title: "Show reasoning", message: "Explain step by step why the sky appears blue during the day but red at sunset.", }, ], available: "always", }); }这个建议词不是随意挑选的:源码注释说明,reasoning 模型只有在遇到"真正需要思考的问题"时才会输出 reasoning 流,类似"show your reasoning"这样的元提示词往往不会触发推理,导致渲染槽位永远不会亮起。而"天空为何白天蓝、日落红"这类需要分步因果推理的问题,能稳定诱发 reasoning 输出——这是保证 QA 与 E2E 稳定可重复的关键细节。
四、底层原理:内置 CopilotChatReasoningMessage 是怎么工作的
要理解"默认渲染"到底渲染成什么样,需要阅读 React Core 的组件实现:packages/react-core/src/v2/components/chat/CopilotChatReasoningMessage.tsx。
消息分派:reasoning 是一等消息类型
在 CopilotChatMessageView.tsx#L16 中,CopilotChatReasoningMessage被直接导入,并根据message.role === "reasoning"对消息进行判别分派(custom 变体页面的源码注释也印证了这一点)。因此 reasoning 消息在 v2 中是独立的一等消息类型,与 user / assistant / tool 消息平级。
渲染形态:可折叠卡片
CopilotChatReasoningMessage组件按三段式结构组织(源码中以 namespace 形式导出子组件,支持 slot 覆盖):
- Header(头部):显示状态标签。流式进行中显示
Thinking…(并带一个脉动动画圆点),完成后显示Thought for X(如Thought for 12 seconds),时长由内部计时器计算(见 CopilotChatReasoningMessage.tsx#L25-L32 的formatDuration与每秒 tick 的计时逻辑)。有内容时头部右侧会出现一个旋转 90° 的 chevron 图标,提示可展开。 - Content(内容区):通过
Streamdown组件流式渲染 reasoning 文本(markdown 风格流式解析),流式中带一个脉动光标指示符;无内容且未流式时不渲染,避免空卡片。 - Toggle(折叠容器):基于
grid-template-rows: 1fr/0fr的过渡动画实现平滑展开/收起。
交互细节:自动展开与用户意图保护
源码实现了两条容易被忽略的交互规则:
- 默认展开、完成后自动收起:流式开始时
isOpen初始为true(让用户实时看到思考过程),流式结束后自动折叠为头部摘要。 - 用户手动操作优先:组件用
userToggledRef记录用户是否手动切换过。一旦用户手动点开/收起,自动收起逻辑不再覆盖用户意图(源码注释说明这是为了避免 CI 上异步 forceUpdate 与点击事件的竞态导致测试抖动)。
组件 Props 类型(CopilotChatReasoningMessageProps)同时暴露了header、contentView、toggle三个子 slot,这意味着即便默认渲染零配置可用,开发者仍可通过这三个子槽位做精细化的局部定制,而不必重写整个 reasoning 渲染。
五、QA 验证清单:继承自官方文档的测试步骤
根据 qa/reasoning-default.md,该 Demo 的验证包含前置条件、测试步骤与预期结果三个部分,下面完整保留并补充实操说明。
前置条件(Prerequisites)
- Demo 已部署且可访问:即
reasoning-default页面已通过 Next.js 应用提供(本地npm run dev或已部署环境均可)。 - Agent 后端健康:确认
/api/copilotkit路由可达,且_thinking_chatADK Agent(挂载于对应后端路径)能正常返回 Gemini 响应。若使用 aimock 录制回放,还需确认GOOGLE_GEMINI_BASE_URL指向的代理正常。
测试步骤(Test Steps)
- 导航到
/demos/reasoning-default:确认页面正常加载、聊天输入框可见。 - 发送任意能诱发 reasoning 的提示词,验证内置
CopilotChatReasoningMessage可折叠卡片正确渲染推理 token。实操建议:直接点击聊天输入区上方的"Show reasoning"建议按钮(其消息词固定为"逐步解释天空白天为何是蓝色、日落为何变红"),这样能稳定触发 reasoning 流,避免自由输入不触发思考。 - 验证没有自定义 reasoning slot 被接线:检查页面只使用默认样式——不存在
ReasoningBlock或任何定制容器。对照检查方法:访问/demos/reasoning-custom可以看到带琥珀色标签横幅的自定义渲染,而本页面应保持最朴素的默认卡片形态。
预期结果(Expected Results)
- 页面无错误加载。
- Reasoning 通过 CopilotKit 默认的
CopilotChatReasoningMessage组件渲染,前端零配置(即不写任何 slot override)。
六、自动化验证:E2E 测试如何断言默认渲染
该 QA 文档对应的自动化测试位于 tests/e2e/reasoning-default.spec.ts,它把人工清单翻译成了两条可重复的 Playwright 断言:
test.describe("Reasoning: Default", () => { test.setTimeout(120_000); test.beforeEach(async ({ page }) => { await page.goto("/demos/reasoning-default"); }); test("page renders without errors", async ({ page }) => { await expect( page.locator('[data-testid="copilot-chat-input"]'), ).toBeVisible(); }); test("Show reasoning pill renders a reasoning-role message", async ({ page, }) => { const pill = page.getByRole("button", { name: /Show reasoning/i }).first(); await expect(pill).toBeVisible({ timeout: 30_000 }); await pill.click(); await expect(page.getByText(/Thinking…|Thought for/i).first()).toBeVisible({ timeout: 60_000, }); }); });值得注意的测试设计:
- 可见信号选择:默认的
CopilotChatReasoningMessage不发射任何testid,所以测试用其"流式中/完成后的头部标签"(Thinking…或Thought for …)作为可折叠卡片已挂载的判定信号——这与组件源码中label的取值逻辑(CopilotChatReasoningMessage.tsx#L100-L102)完全一致,实现了测试与实现的语义对齐。 - 确定性流式:测试注释说明,"Show reasoning" 建议词对应的消息与 aimock fixture(
showcase/aimock下的录制数据)匹配,因此 CI 中的流式响应是确定性的,不会因模型输出的随机性导致断言失败。 - 超时配置:单个用例最长 120 秒,其中 reasoning 标签等待 60 秒,为较慢的流式响应留足余量。
七、与 reasoning-custom 的对照:何时用默认、何时自定义
src/app/demos/reasoning-custom/page.tsx 与默认版唯一的结构差异,是给CopilotChat传入了messageView.reasoningMessage槽位:
<CopilotChat agentId={AGENT_ID} className="h-full rounded-2xl" messageView={{ reasoningMessage: ReasoningBlock as unknown as typeof CopilotChatReasoningMessage, }} />这从对照角度回答了"何时选默认、何时自定义":
| 场景 | 推荐方式 | 理由 |
|---|---|---|
| 快速集成、验证 reasoning 能力、保持 UI 一致 | 默认CopilotChatReasoningMessage | 零配置,自带流式/完成状态标签、自动展开与折叠动画、Streamdown 流式文本渲染 |
| 需要品牌化视觉强调(如琥珀色横幅、定制标签) | 覆盖messageView.reasoningMessage槽位 | 槽位是 v2 公开、稳定的定制入口;也可以只覆盖header/contentView/toggle子槽位做局部微调 |
| 无头(headless)场景自建聊天界面 | 使用message.role === "reasoning"判别 +useRenderReasoning | 见 headless-complete 等 Demo 的源码模式 |
八、如何自行运行与复现验证
- 克隆仓库:
git clone https://gitcode.com/GitHub_Trending/co/CopilotKit.git,进入showcase/integrations/google-adk目录。 - 配置环境:按 showcase/integrations/google-adk 目录下的 README 配置 Google AI Studio 凭据;若在 Railway 等托管环境,设置
GOOGLE_GEMINI_BASE_URL指向 aimock 代理以保证流式响应确定性。 - 启动前后端:启动 Next.js 应用(
/api/copilotkit路由代理到挂载的 ADK Agent 后端路径)。 - 人工验证:按上文第五节清单逐项勾选。
- 自动化验证:在配置好 Playwright 的环境执行
tests/e2e/reasoning-default.spec.ts,观察两条断言是否全部通过。
结语
reasoning-default这个看似"简单"的 QA 单元,实际上贯穿了 CopilotKit reasoning 能力的完整链路:ADK 后端通过include_thoughts=True让 Gemini 输出 thought parts,ag-ui 协议将其转换为 reasoning 消息,React Core 的CopilotChatMessageView按message.role分派给内置CopilotChatReasoningMessage,最终以带流式状态标签、自动折叠动画的可展开卡片呈现。理解这条链路后,你既能开箱即用地获得推理可视化,也能在需要品牌化定制时准确找到messageView.reasoningMessage这个扩展点。
【免费下载链接】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),仅供参考