Skip to content

第 11 章 · 思考与推理模式

本章目标:掌握 pi-ai 的思考/推理(Thinking/Reasoning)统一接口,学会按模型能力开启推理、流式接收思考内容并控制 token 预算。

11.1 什么是 Thinking/Reasoning

许多模型支持"思考"能力——在给出最终答案前先生成内部推理过程(如 Claude 的 extended thinking、DeepSeek-R1 的思维链)。pi-ai 把这些差异巨大的 Provider 参数统一成了一套接口。

关键点:

  • 通过模型的 reasoning 属性可以判断它是否支持推理;
  • 把推理选项传给不支持的模型时会被静默忽略,不会报错;
  • 思考内容以独立 block 类型(thinking)返回,与正文 text 分离。
typescript
import { createModels } from '@earendil-works/pi-ai';
import { anthropicProvider } from '@earendil-works/pi-ai/providers/anthropic';

const models = createModels();
models.setProvider(anthropicProvider());

// 查询模型是否支持推理
const model = models.getModel('anthropic', 'claude-sonnet-4-5')!;
if (model.reasoning) {
  console.log('Model supports reasoning/thinking');
}

11.2 统一接口:streamSimple / completeSimple

streamSimplecompleteSimple 是 pi-ai 推荐的高级接口。它们接受统一的推理选项(如 reasoning: 'medium'),由库自动翻译成各 Provider 的原生参数:

typescript
// 统一推理等级:off | minimal | low | medium | high | xhigh | max
const response = await models.completeSimple(model, context, {
  reasoning: 'medium', // 自动映射为对应 Provider 的参数
});

// 流式版本同样支持
const stream = models.streamSimple(model, context, { reasoning: 'low' });
for await (const event of stream) {
  if (event.type === 'message_update') {
    console.log(event.assistantMessageEvent.type);
  }
}

静默降级

如果你给一个不支持推理的模型传了 reasoning: 'high',pi-ai 不会抛错——选项被静默忽略。这让同一份代码可以在不同档位模型间自由切换。

11.3 Provider 特定选项:stream / complete

低级接口 stream / complete 暴露 Provider 特定的原始选项,适合精细控制:

typescript
// 低级接口:直接传递 Provider 原生支持的选项
models.stream(m, context, {
  thinkingEnabled: true,        // 开启思考(Anthropic 扩展思考)
  thinkingBudgetTokens: 2048,   // 思考 token 预算上限
});

两种接口的取舍:

接口推理控制方式适用场景
streamSimple / completeSimple统一等级字符串跨提供商通用代码
stream / completeProvider 原生参数需要精确预算等细节

11.4 流式接收思考内容

思考内容以独立事件流出。注意:不同内容块的事件不保证连续——Provider 可能在同一段上游数据里交替发出 text、thinking、toolcall 的 delta,必须用 contentIndex 关联事件所属的块:

typescript
for await (const event of models.stream(model, context)) {
  switch (event.type) {
    case 'thinking_start':
      // 思考块开始,contentIndex 标记它在 content 数组中的位置
      console.log('[Model is thinking...]');
      break;
    case 'thinking_delta':
      // 收到思考片段增量
      process.stdout.write(event.delta);
      break;
    case 'thinking_end':
      // 思考块完成,event.content 是完整思考文本
      console.log('\n[Thinking complete]');
      break;
  }
}

事件参考:

事件含义关键字段
thinking_start思考块开始contentIndex
thinking_delta思考片段增量delta, contentIndex
thinking_end思考块完成content(完整思考), contentIndex

11.5 在 Agent 中使用思考等级

Agent 类通过 initialState.thinkingLevel 或运行时修改状态来控制思考深度:

typescript
import { Agent } from '@earendil-works/pi-agent-core';

const agent = new Agent({
  initialState: {
    systemPrompt: '你是一个严谨的算法助手。',
    model,
    thinkingLevel: 'medium', // off/minimal/low/medium/high/xhigh/max
    tools: [],
    messages: [],
  },
  streamFn: models.streamSimple.bind(models),
});

// 运行时动态调整难度:简单问题关掉思考省 token
agent.state.thinkingLevel = 'off';
await agent.prompt('1+1 等于几?');

agent.state.thinkingLevel = 'high'; // 复杂问题开高档推理
await agent.prompt('证明:任意大于 2 的偶数都可写成两个素数之和的猜想为何未被证明?');

对按 token 计费思考的 Provider,还可以自定义每个等级的预算:

typescript
// 为 token 型思考 Provider 自定义各级预算
agent.thinkingBudgets = {
  minimal: 128,
  low: 512,
  medium: 1024,
  high: 2048,
};

另外,部分模型暴露独有的超高推理档位(如 xhighmax)。用 getSupportedThinkingLevels(model) 可以查询某个具体模型实际支持哪些等级,避免盲目设置。

本章小结

  • model.reasoning 判断模型是否支持推理;不支持的模型会静默忽略推理选项;
  • streamSimple/completeSimple 提供统一等级字符串,stream/complete 暴露原生参数;
  • 思考事件有 start/delta/end 三种,必须用 contentIndex 关联块,不能假设连续;
  • Agent 通过 thinkingLevelthinkingBudgets 控制思考深度与预算;
  • getSupportedThinkingLevels() 查询具体模型支持的等级集合。

🧪 随堂测验

点击你认为正确的选项。答错时会展示正确答案与原因解析。

1. 把 reasoning: "high" 传给一个不支持思考的模型会发生什么?

2. 处理流式思考事件时,为什么必须使用 contentIndex?

3. 想让同一份代码跨 Provider 使用统一的推理控制,应该用哪个接口?

4. 如何查询某个模型实际支持哪些思考等级?

🛠️ 动手实践

  1. 分别用 reasoning: 'off''low''high' 向同一个数学问题发起请求,对比回答质量与耗时。
  2. 编写一个终端程序,流式渲染思考过程为灰色文字、正文为白色文字。
  3. 给你的 Agent 加一个"自适应思考"策略:根据用户输入长度自动选择 thinkingLevel。

下一章我们将让 Agent"看见"图片——第 12 章 · 图片输入与图像生成