CopilotKit 默认推理渲染实战:内置 CopilotChatReasoningMessage 的 “Thought for X“ 可折叠卡片
CopilotKit 默认推理渲染实战:内置 CopilotChatReasoningMessage 的 “Thought for X“ 可折叠卡片
发布时间:2026/9/12 16:25:08
CopilotKit 默认推理渲染实战内置 CopilotChatReasoningMessage 的 Thought for X 可折叠卡片【免费下载链接】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本指南以 Claude SDK Python 集成示例中的reasoning-default演示为切入点讲解在 CopilotKit v2 中零配置渲染 Agent 思考链reasoning chain的完整方案如何让后端通过 AG-UI 协议以REASONING_MESSAGE_*事件流式推送推理内容如何让前端内置的CopilotChatReasoningMessage组件把它呈现为带 Thinking… / Thought for X 头部、可展开折叠的卡片以及它与自定义reasoningMessage插槽渲染方案的对比与取舍。读完本文你将掌握在 reasoning-default 演示目录 对应的场景下零插槽覆盖启用思考链展示的能力并能依据 内置组件源码 理解其底层实现。一、演示背景同一后端两种前端渲染reasoning-default演示的全部代码都在 showcase/integrations/claude-sdk-python/src/app/demos/reasoning-default/ 下其 README 点明了它的设计定位Same backend asreasoning-custom, but the page passes NO customreasoningMessageslot — CopilotKits built-inCopilotChatReasoningMessagerenders the reasoning as a collapsible Thought for X card.翻译过来即它与reasoning-custom自定义渲染演示共享同一个后端reasoning_agent图唯一的区别在于前端是否覆盖reasoningMessage插槽。这个同后端、双前端的结构是理解 CopilotKit 推理渲染体系的最佳入口reasoning-default不传任何自定义插槽由内置组件CopilotChatReasoningMessage负责渲染产出可折叠的 Thought for X 卡片reasoning-custom通过messageView.reasoningMessage插槽传入自定义的ReasoningBlock组件把思考链重绘为琥珀色标签的 Agent reasoning 卡片具体见 reasoning-custom 目录。两个演示共用同一个后端端点/reasoning这在 route.ts 的路由映射 中写得很明确const dedicatedAgentPaths: Recordstring, string { // ... // Reasoning demos share a single backend that emits AG-UI // REASONING_MESSAGE_* events (parsed out of reasoning.../reasoning // blocks the model emits). The two demo cells differ only on the // frontend slot configuration. reasoning-default: /reasoning, reasoning-custom: /reasoning, };也就是说前端渲染方式的选择完全不影响后端协议无论你最终用内置卡片还是自定义卡片后端都以同一套 AG-UI 推理事件向客户端推送数据。这正是 CopilotKit v2 将消息类型与渲染实现解耦的体现。二、零配置渲染默认插槽的完整页面代码reasoning-default的页面代码极其精简全部核心逻辑只有两个文件2.1 页面入口 page.tsx完整代码如下来自 page.tsxuse client; // Reasoning — Default // // Pairs with reasoning-custom (the Custom variant) so users can // compare default vs custom reasoning rendering side by side. This cell // renders CopilotChat with NO slot override — reasoning messages are // rendered by the built-in CopilotChatReasoningMessage component // (Thinking… / Thought for X header with an expandable content region). // // Both demos share the same backend (reasoning_agent graph) and the // same runtime URL (/api/copilotkit). The only difference is whether the // messageView.reasoningMessage slot is overridden. import { CopilotKit, CopilotChat } from copilotkit/react-core/v2; import { useReasoningDefaultSuggestions } from ./suggestions; // region[default-reasoning-zero-config] const AGENT_ID reasoning-default; export default function ReasoningDefaultDemo() { return ( CopilotKit runtimeUrl/api/copilotkit agent{AGENT_ID} div classNameflex justify-center items-center h-screen w-full div classNameh-full w-full max-w-4xl Chat / /div /div /CopilotKit ); } function Chat() { useReasoningDefaultSuggestions(); return CopilotChat agentId{AGENT_ID} classNameh-full rounded-2xl /; } // endregion[default-reasoning-zero-config]关键点逐条拆解CopilotKit runtimeUrl/api/copilotkit agent{AGENT_ID}运行时挂载在 Next.js 的/api/copilotkit单路由上并通过agent属性绑定名为reasoning-default的 Agent。该 Agent 名在 route.ts 的 agentNames 列表 中被注册同时被dedicatedAgentPaths重定向到后端/reasoning路径。CopilotChat agentId{AGENT_ID} ...CopilotChat预置聊天组件只传了agentId和样式类名完全没有messageView属性更没有reasoningMessage插槽。这意味着消息列表将使用全部默认插槽组件其中reasoningMessage默认指向内置的CopilotChatReasoningMessage。classNameh-full rounded-2xl仅做布局与圆角样式定制与推理渲染逻辑无关。2.2 建议提示 suggestions.ts建议提示文件 用useConfigureSuggestions注册了一个推理诱发问题use client; import { useConfigureSuggestions } from copilotkit/react-core/v2; // Suggestions registered via the v2 chat composer hook. The prompt is a // concrete reasoning-eliciting question — gpt-5-mini (and other OpenAI // reasoning models) only emit response.reasoning_summary_text.delta // events when theres a real problem to think about. Meta-prompts like // show your reasoning produce no reasoning summary, so the reasoning // slot would never light up. 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 输出而类似show your reasoning这种元提示meta-prompt往往不会触发推理流导致推理插槽永远不亮。因此演示特意选用了一个具体的、可诱发分步推理的问题作为默认建议。三、后端数据来源AG-UI 的 REASONING_MESSAGE_* 事件前端能渲染思考链前提是后端把推理内容以 AG-UI 协议事件推送过来。与reasoning-custom共享的reasoning_agent图在 reasoning_agent.py 中实现其 docstring 说明了核心思路The Anthropic Python SDK supports Claudes extended-thinking (thinking budget) parameter onmessages.stream, which streamsthinking_deltacontent blocks separately from text. We map those onto AG-UIsREASONING_MESSAGE_*events. Models without extended-thinking fall back to an inlinereasoning.../reasoningsystem-prompt convention that this agent parses out of the text stream.即两条推理通道原生 extended-thinking 通道默认启用通过messages.stream(..., thinking{type: adaptive})开启 Claude 的原生思考块流式事件中的RawContentBlockStartEventblock.type thinking与RawContentBlockDeltaEventdelta.type thinking_delta被逐一转发为 AG-UI 的ReasoningMessageStartEvent/ReasoningMessageContentEvent/ReasoningMessageEndEvent。reasoning.../reasoning内联标签回退通道当无法启用原生思考时系统提示词指示模型先在正文中输出reasoning.../reasoning标签包裹的思考过程再由 Agent 中的状态机把标签内的内容切分并映射为同样的REASONING_MESSAGE_*事件。代码中对应REASONING_SYSTEM_PROMPT与一套以REASONING_OPEN/REASONING_CLOSE为界、带缓冲区的流式解析状态机。值得注意的工程细节是原生通道启用时系统提示词刻意不要求模型输出reasoning标签NATIVE_REASONING_SYSTEM_PROMPT明确写了 Do not wrap your answer in any XML or markup tags。原因在源码注释中说明得很清楚如果同时启用原生思考与标签指令真实 Claude 会产出两份推理内容原生 thinking 块 标签文本造成 double-bubble 重复显示。此外reasoning_agent.py 还对消息生命周期做了健壮性兜底无论流正常结束、中途截断还是抛出异常只要存在已开始但未结束的推理块都会补发ReasoningMessageEndEvent避免前端渲染出永远 Thinking的悬空气泡。该后端通过 agent_server.py 中的/reasoning端点 暴露为 FastAPI 流式接口并经由createClaudeHttpAgent包装为ag-ui/client的HttpAgent具体见 claude-http-agent.ts。前端 Next.js 运行时通过CopilotRuntimecreateCopilotRuntimeHandler在单路由模式下把这个 Agent 代理给浏览器。四、内置组件源码解析CopilotChatReasoningMessage 的 Thought for X 卡片reasoning-default的核心看点就是内置组件 CopilotChatReasoningMessage.tsx 如何把ReasoningMessage渲染成可折叠卡片。该组件实现了头部Header、内容区Content与展开切换Toggle三个可独立覆写的子插槽默认组合起来就是 README 所述的 collapsible Thought for X card。4.1 主组件标签与计时主组件接收messageReasoningMessage类型、messages与isRunning其标签逻辑为const isLatest messages?.[messages.length - 1]?.id message.id; const isStreaming !!(isRunning isLatest); const hasContent !!(message.content message.content.length 0); const label isStreaming ? Thinking… : Thought for ${formatDuration(elapsed)};流式进行中isStreaming为 true头部显示Thinking…并伴随一个脉冲小圆点loading 指示流式结束头部切换为Thought for X其中 X 是formatDuration(elapsed)生成的耗时描述。formatDuration对秒数做人性化处理小于 1 秒显示 a few seconds小于 60 秒显示 N seconds超过 60 秒显示 Xm Ys。耗时通过startTimeRefsetInterval每秒 tick 一次计算仅在流式期间计时流结束时会取一个最终快照。4.2 展开 / 折叠行为展开状态的核心逻辑是流式中默认展开流结束后自动折叠但尊重用户手动操作const [isOpen, setIsOpen] useState(isStreaming); const userToggledRef useRef(false); useEffect(() { if (isStreaming) { userToggledRef.current false; setIsOpen(true); } else if (!userToggledRef.current) { setIsOpen(false); } }, [isStreaming]);新的流式会话开始时重置userToggledRef并强制展开让用户实时看到思考过程流结束、且用户未曾手动点击过时自动折叠保持对话界面整洁若用户手动展开/折叠过userToggledRef.current true则自动折叠逻辑不再覆盖用户的显式意图——源码注释提到这个设计还避免了 CI 上异步forceUpdate时序与点击处理器竞争导致的测试抖动。折叠动画由Toggle子插槽完成使用grid-template-rows在1fr与0fr之间过渡配合overflow-hidden实现平滑的高度动画。4.3 内容渲染Streamdown 流式 Markdown内容区Content子插槽在无内容且非流式时不渲染任何 DOM有内容时通过Streamdown组件渲染推理文本推理内容通常是 Markdown 格式的思考链流式期间还会在末尾附加一个脉冲光标动画div classNamecpk:text-sm cpk:text-muted-foreground Streamdown {typeof contentChildren string ? contentChildren : } /Streamdown {isStreaming hasContent ( span classNamecpk:inline-flex cpk:items-center cpk:ml-1 cpk:align-middle span classNamecpk:w-2 cpk:h-2 cpk:rounded-full cpk:bg-muted-foreground cpk:animate-pulse-cursor / /span )} /div头部的ChevronRight图标会在可展开有内容时渲染并随展开状态旋转 90 度配合aria-expanded保证无障碍可访问性。4.4 为什么是默认消息分发的实现依据CopilotChatReasoningMessage之所以能成为默认渲染根因在消息视图组件 CopilotChatMessageView.tsx 中v2 将reasoning视为一等公民消息类型在renderMessageBlock中按message.role分发} else if (message.role reasoning) { elements.push( MemoizedReasoningMessage key{message.id} message{message as ReasoningMessage} messages{messages} isRunning{isRunning} ReasoningMessageComponent{ReasoningComponent} slotProps{reasoningSlotProps} /, ); }ReasoningComponent由resolveSlotComponent(reasoningMessage, CopilotChatReasoningMessage)解析当reasoningMessage插槽未提供任何值时默认回落到CopilotChatReasoningMessage。这正是reasoning-default页面什么都不传也能渲染思考链的原因。若传了组件则替换之reasoning-custom的做法传字符串则视为 className 应用于默认组件传对象则视为默认组件的部分 props——三种插槽形态都在resolveSlotComponent中统一处理。此外MemoizedReasoningMessage做了精细化 memo仅在消息 id、内容、最新状态isStreaming 切换、组件引用或 slot props 变化时重渲染避免无关消息更新引起整个推理卡片重绘同时CopilotChatMessageView还会在最后一条消息是reasoning时隐藏聊天气泡级 loading 光标showCursor逻辑因为推理卡片本身已带自己的 loading 指示避免双重闪烁。五、默认 vs 自定义如何选择渲染方案对照 reasoning-custom 的自定义渲染两种方案的本质差异是维度reasoning-default内置默认reasoning-custom自定义插槽是否覆盖messageView.reasoningMessage否是传入ReasoningBlock头部样式Thinking… / Thought for X含流式计时Thinking… / Agent reasoning琥珀色标签交互默认展开、流毕自动折叠可手动切换常驻内联展示思考链不折叠渲染组件CopilotChatReasoningMessage含 Header/Content/Toggle 三个可再细分插槽自绘ReasoningBlock源码见此处适用场景开箱即用、想省事地获得规范可折叠 UI需要品牌化、强视觉强调如琥珀色标签或定制交互自定义方案在reasoning-custom/page.tsx中通过以下方式接入CopilotChat agentId{AGENT_ID} classNameh-full rounded-2xl messageView{{ reasoningMessage: ReasoningBlock as unknown as typeof CopilotChatReasoningMessage, }} /自定义组件接收messageReasoningMessage、messages与isRunning三个插槽入参ReasoningBlock用它来判断当前是否正在流式、是否有内容并据此显示 Thinking… / Agent reasoning / … 三种状态。这类插槽入参协议与内置组件完全一致因此自定义组件可以无缝替换默认组件。六、完整调用链路与本地运行方式综合上述源码reasoning-default的完整链路为用户在CopilotChat中输入或点击建议问题前端通过runtimeUrl/api/copilotkit把请求发到 Next.js 运行时运行时按agent{AGENT_ID}查找名为reasoning-default的 Agentroute.ts 将该 Agent 映射到后端http://localhost:8000/reasoningAGENT_URL环境变量可覆盖经HttpAgent以 AG-UI 协议代理后端 reasoning_agent.py 通过ANTHROPIC_API_KEY调用 Claude模型名取ANTHROPIC_REASONING_MODEL缺省回落到ANTHROPIC_MODEL最终经normalize_claude_model归一开启 adaptive extended thinking把thinking_delta映射为REASONING_MESSAGE_START / CONTENT / END事件流返回前端运行时把role reasoning的消息交给默认的CopilotChatReasoningMessage渲染出可折叠的 Thought for X 卡片。运行该演示需要满足的前提与仓库其他示例一致后端服务运行在 8000 端口通过 agent_server.py 提供/reasoning端点/health探针供运行时健康检查设置ANTHROPIC_API_KEY模型为 Claude 系列需支持 extended-thinking 能力才能走原生思考通道前端 Next.js 应用在/api/copilotkit挂载 CopilotKit 运行时确保AGENT_URL指向后端地址。七、小结reasoning-default演示用最短的代码量一个CopilotChat、零插槽覆盖验证了 CopilotKit v2 的推理渲染体系AG-UI 协议让推理成为与文本、工具调用并列的一等消息类型内置的CopilotChatReasoningMessage提供开箱即用的 Thinking… / Thought for X 可折叠卡片而插槽机制则为需要深度定制的场景保留了reasoningMessage出口。参考实现与源码均可在本仓库中查阅前端演示位于 reasoning-default 目录内置组件见 CopilotChatReasoningMessage.tsx消息分发逻辑见 CopilotChatMessageView.tsx后端事件生成见 reasoning_agent.py。【免费下载链接】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),仅供参考