推理 Token

展示高阶模型的内部思考过程

推理 Token 会暴露 OpenAI o1/o3、Anthropic Claude 等支持深度思考模型的内部推理逻辑。
这类模型会生成结构化内容块,把推理过程最终回答分开,让你可以在界面中展示模型是如何得出答案的。


什么是推理 Token?

具备推理能力的模型在处理提示时,会生成两种不同类型的内容块:

  • 推理块(reasoning block):模型的内在思考链、问题拆解、分步分析
  • 文本块(text block):呈现给用户的最终、整理好的回答

这些块会放在 AIMessagecontentBlocks 属性中:

// 推理块
{ 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 属性)
  • 折叠时显示预览摘要
  • 流式推理中默认保持折叠,避免布局抖动
Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐