Skip to main content
推理令牌暴露了高级模型(如 OpenAI 的 o1/o3 和具有扩展思考能力的 Anthropic 的 Claude)的内部思维过程。这些模型生成结构化的内容块,将推理与最终答案分开,让您能够构建显示模型如何得出其响应的用户界面。

什么是推理令牌?

当具有推理能力的模型处理提示时,它们会生成两种不同类型的内容:
  1. 推理块:模型的内部思维链、问题分解和逐步分析
  2. 文本块:呈现给用户的最终、精炼的响应
这些内容作为类型化的内容块在 AIMessage 中提供,可通过 contentBlocks 属性访问:
// 推理块
{ type: "reasoning", reasoning: "让我一步一步思考这个问题..." }

// 文本块
{ type: "text", text: "答案是 42。" }
并非所有模型都会生成推理令牌。此模式专门适用于支持扩展思考或思维链输出的模型。标准聊天模型仅返回文本块。

用例

  • 透明度:向用户展示模型的推理过程,以建立对其答案的信任
  • 调试:检查模型的思维过程,以识别其出错之处
  • 教育工具:通过揭示 AI 如何处理问题来教授学生解决问题
  • 决策支持:让领域专家验证建议背后的推理
  • 质量保证:在受监管行业中审核推理链以确保合规性

提取推理和文本块

AIMessage 上的 contentBlocks 数组包含所有按生成顺序排列的块。按 type 过滤它们以分离推理和文本:
import { AIMessage } from "@langchain/core/messages";

function extractBlocks(msg: AIMessage) {
  const reasoningBlocks = msg.contentBlocks
    .filter((b) => b.type === "reasoning")
    .map((b) => b.reasoning);

  const textBlocks = msg.contentBlocks
    .filter((b) => b.type === "text")
    .map((b) => b.text);

  return {
    reasoning: reasoningBlocks.join(""),
    text: textBlocks.join(""),
  };
}
单个消息可能包含多个推理块(例如,如果模型暂停推理、生成部分文本,然后进一步推理)。将它们连接起来即可获得完整的思维过程。

useStream 访问消息

定义一个与您的代理状态模式匹配的 TypeScript 接口,并将其作为类型参数传递给 useStream,以类型安全地访问状态值。在下面的示例中,将 typeof myAgent 替换为您的接口名称:
import type { BaseMessage } from "@langchain/core/messages";

interface AgentState {
  messages: BaseMessage[];
}
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 组件

ThinkingBubble 以视觉上独特、可折叠的容器呈现推理令牌。用户可以展开它以查看完整的思维过程,或将其折叠以专注于最终答案。
import { useState } from "react";

function ThinkingBubble({
  reasoning,
  isStreaming,
}: {
  reasoning: string;
  isStreaming: boolean;
}) {
  const [isExpanded, setIsExpanded] = useState(false);

  const charCount = reasoning.length;
  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..." : `Thought process (${charCount} chars)`}
        </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-color: #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); }
}
在流式传输期间,默认保持 ThinkingBubble 折叠状态,并仅显示旋转器。在流式传输过程中展开可能会导致布局抖动,因为新令牌会不断到达。让用户在推理阶段完成后展开。

渲染完整的 AI 响应

ThinkingBubble 和标准文本气泡组合到单个 AIResponse 组件中:
function AIResponse({
  message,
  isStreaming,
}: {
  message: AIMessage;
  isStreaming: boolean;
}) {
  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;
  const isTextPhase = isStreaming && hasText;

  return (
    <div className="ai-response">
      {hasReasoning && (
        <ThinkingBubble
          reasoning={reasoningBlocks}
          isStreaming={isReasoningPhase}
        />
      )}
      {hasText && (
        <div className="ai-text-bubble">
          <p>{textBlocks}</p>
          {isTextPhase && <span className="cursor-blink"></span>}
        </div>
      )}
    </div>
  );
}

处理边缘情况

不带推理的消息

并非每条 AI 消息都包含推理块。当 contentBlocks 仅包含文本块时,渲染标准消息气泡,而不使用 ThinkingBubble。

空推理块

某些模型会生成空推理块作为占位符。过滤掉这些块:
const meaningfulReasoning = message.contentBlocks
  .filter((b) => b.type === "reasoning" && b.reasoning.trim().length > 0);

多个推理-文本循环

单个消息可以在推理块和文本块之间交替。如果需要保留这种交错,请按顺序迭代 contentBlocks,而不是按类型分组:
message.contentBlocks.forEach((block) => {
  if (block.type === "reasoning") {
    // 渲染 ThinkingBubble
  } else if (block.type === "text") {
    // 渲染文本段落
  }
});

最佳实践

  • 默认折叠:按需显示推理,而不是默认显示
  • 显示字符数:让用户快速了解响应中投入了多少思考
  • 视觉区分:使用不同的颜色、边框或背景,使推理永远不会与实际答案混淆
  • 动画过渡:平滑的展开/折叠动画可提高感知质量
  • 考虑可访问性:在切换按钮上使用适当的 ARIA 属性(aria-expandedaria-controls
  • 在预览中截断:折叠时显示推理的简短预览,以便用户决定是否展开