8.5 LangChain 前端(推理 Token)
·
推理 Token
展示高阶模型的内部思考过程
推理 Token 会暴露 OpenAI o1/o3、Anthropic Claude 等支持深度思考模型的内部推理逻辑。
这类模型会生成结构化内容块,把推理过程与最终回答分开,让你可以在界面中展示模型是如何得出答案的。
什么是推理 Token?
具备推理能力的模型在处理提示时,会生成两种不同类型的内容块:
- 推理块(reasoning block):模型的内在思考链、问题拆解、分步分析
- 文本块(text block):呈现给用户的最终、整理好的回答
这些块会放在 AIMessage 的 contentBlocks 属性中:
// 推理块
{ type: "reasoning", reasoning: "让我一步步思考这个问题..." }
// 文本块
{ type: "text", text: "答案是 42。" }
并非所有模型都支持推理 Token,仅适用于支持扩展思考 / 思维链的模型。
普通聊天模型只返回 text 块。
使用场景
- 透明化:向用户展示思考过程,建立信任
- 调试:查看模型思路,定位错误
- 教育工具:展示 AI 解题步骤,辅助教学
- 决策支持:让领域专家验证推理逻辑
- 合规审计:在受监管行业审核思维链
提取推理块和文本块
从 contentBlocks 按类型筛选、合并:
import { AIMessage } from "@langchain/core/messages";
function extractBlocks(msg: AIMessage) {
const reasoning = msg.contentBlocks
.filter(b => b.type === "reasoning")
.map(b => b.reasoning)
.join("");
const text = msg.contentBlocks
.filter(b => b.type === "text")
.map(b => b.text)
.join("");
return { reasoning, text };
}
一条消息可能包含多个推理块,合并后得到完整思路。
从 useStream 获取消息
import type { BaseMessage } from "@langchain/core/messages";
interface AgentState {
messages: BaseMessage[];
}
React 示例
import { useStream } from "@langchain/react";
import { AIMessage, HumanMessage } from "@langchain/core/messages";
function Chat() {
const stream = useStream<typeof myAgent>({
apiUrl: "http://localhost:2024",
assistantId: "reasoning",
});
return (
<div className="messages">
{stream.messages.map((msg, i) => {
if (HumanMessage.isInstance(msg)) {
return <HumanBubble key={i} text={msg.content} />;
}
if (AIMessage.isInstance(msg)) {
return (
<AIResponse
key={i}
message={msg}
isStreaming={stream.isLoading && i === stream.messages.length - 1}
/>
);
}
return null;
})}
</div>
);
}
构建 ThinkingBubble 组件
可折叠、带加载状态的思考气泡,展示模型推理过程:
import { useState } from "react";
function ThinkingBubble({ reasoning, isStreaming }) {
const [isExpanded, setIsExpanded] = useState(false);
const previewLength = 120;
const preview = reasoning.length > previewLength
? reasoning.slice(0, previewLength) + "..."
: reasoning;
return (
<div className="thinking-bubble">
<button className="thinking-header" onClick={() => setIsExpanded(!isExpanded)}>
<span className="thinking-icon">
{isStreaming ? <span className="thinking-spinner" /> : "💭"}
</span>
<span className="thinking-label">
{isStreaming ? "Thinking..." : `思考过程(${reasoning.length} 字符)`}
</span>
<span className={`chevron ${isExpanded ? "expanded" : ""}`}>▶</span>
</button>
{isExpanded && (
<div className="thinking-content">
<pre>{reasoning}</pre>
</div>
)}
{!isExpanded && !isStreaming && (
<div className="thinking-preview">{preview}</div>
)}
</div>
);
}
ThinkingBubble 样式(可直接复制)
.thinking-bubble {
background: #f8f5ff;
border: 1px solid #e2d9f3;
border-radius: 8px;
padding: 12px;
margin: 8px 0;
font-size: 0.9em;
}
.thinking-header {
display: flex;
align-items: center;
gap: 8px;
cursor: pointer;
background: none;
border: none;
width: 100%;
text-align: left;
color: #6b21a8;
font-weight: 500;
}
.thinking-content {
margin-top: 8px;
padding-top: 8px;
border-top: 1px solid #e2d9f3;
white-space: pre-wrap;
color: #4a4a4a;
line-height: 1.5;
}
.thinking-preview {
margin-top: 4px;
color: #9ca3af;
font-style: italic;
font-size: 0.85em;
}
.chevron {
margin-left: auto;
transition: transform 0.2s;
}
.chevron.expanded {
transform: rotate(90deg);
}
.thinking-spinner {
display: inline-block;
width: 16px;
height: 16px;
border: 2px solid #e2d9f3;
border-top-color: #6b21a8;
border-radius: 50%;
animation: spin 0.8s linear infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
渲染完整 AI 响应
function AIResponse({ message, isStreaming }) {
const reasoningBlocks = message.contentBlocks
.filter(b => b.type === "reasoning")
.map(b => b.reasoning)
.join("");
const textBlocks = message.contentBlocks
.filter(b => b.type === "text")
.map(b => b.text)
.join("");
const hasReasoning = reasoningBlocks.length > 0;
const hasText = textBlocks.length > 0;
const isReasoningPhase = isStreaming && !hasText;
return (
<div className="ai-response">
{hasReasoning && (
<ThinkingBubble
reasoning={reasoningBlocks}
isStreaming={isReasoningPhase}
/>
)}
{hasText && (
<div className="ai-text-bubble">
<p>{textBlocks}</p>
{isStreaming && hasText && <span className="cursor-blink">▊</span>}
</div>
)}
</div>
);
}
边界情况处理
- 无推理块的消息:只渲染普通文本气泡
- 空推理块:过滤掉空白内容
.filter(b => b.type === "reasoning" && b.reasoning.trim()) - 多次推理-文本交替:按
contentBlocks原始顺序渲染
最佳实践
- 默认折叠,按需展开
- 显示字符数,让用户感知思考量
- 视觉上明显区分推理与回答
- 使用平滑展开/收起动画
- 支持无障碍(ARIA 属性)
- 折叠时显示预览摘要
- 流式推理中默认保持折叠,避免布局抖动
更多推荐



所有评论(0)