> ## Documentation Index
> Fetch the complete documentation index at: https://docs.playapi.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# 推理模型接入

> 选择协议对应的推理参数，验证最终输出并保留可续用状态。

## 推理强度与可见输出是两件事

推理模型可能在最终回答前执行额外计算。推理强度、返回摘要和最终答案长度不是同一个开关；没有可见推理文本也不代表未使用推理。公开摘要不等同于模型内部完整思维过程。

| 接口 | 项目识别的表达 | 使用边界 |
| - | - | - |
| Chat Completions | `reasoning_effort`；另有 OpenRouter 风格的 `reasoning` 对象 | 原生直传与转换路径不同，不混用矛盾设置 |
| Responses | `reasoning.effort`、`summary`、`mode`、`context` | 可接受值随模型变化；后两项是条件支持的扩展 |
| Claude | `thinking`、`output_config` | 原生表达；预算、模式、签名和工具闭环需目标模型支持 |
| Gemini | 原生生成配置中的 thinking 设置 | 不能把 Chat 字段直接当成 Gemini 原生字段 |

项目转换器识别 `none`、`minimal`、`low`、`medium`、`high`、`xhigh`、`max`，但这不是每个模型的可选值列表。部分原生路径保留上游字段，转换路径会归一化或报错。模型名修饰符还可能覆盖请求设置，映射后的修饰符优先于对外名称；使用部署方公布的完整 ID，不自行拼后缀。

Chat 的嵌套 `reasoning` 可表达 `enabled`、`effort`、`max_tokens` 和 `exclude`；`exclude` 控制推理内容可见性，不等于关闭推理。此对象会依渠道方言转换，不能承诺每个字段逐字透传。Responses 的 `reasoning` 结构不使用同一套字段。

## 预算、流式和失败处理

为目标模型留足输入与输出空间；部分模型的生成预算包含推理与可见输出。不要同时堆叠 `max_tokens`、`max_completion_tokens` 或不适用的采样参数。`finish_reason=length`、Responses `status=incomplete` 或只有推理没有最终文本都不能当作完整答案。

流式推理、工具参数、摘要和文本事件应分别处理；不要把推理增量直接当成最终回复。工具调用需要走完整工具循环，见[流式工具](/zh/guides/streaming-tools)。不要要求应用解密 `encrypted_content` 或重写 Claude 的签名。

## 可运行：Chat 单轮或 Responses 无状态续聊

沿用 [REST 运行时](/zh/guides/http-runtime)，保存 `examples/reasoning.mjs` 与 `http-client.mjs`。默认 Responses 模式发起两次请求，使用 `store: false`，把第一轮完整输出按原顺序回放。该模式要求模型和原生链路支持可续用推理项；如果返回了 reasoning 项却没有可用密文，示例停止，不默默丢弃状态。没有 reasoning 项但有完整文本也可以完成。

```sh theme={null}
export API_ROOT_URL='https://gateway.example.com'
export API_KEY='YOUR_API_KEY'
export API_MODEL='YOUR_REASONING_MODEL_ID'
export API_REASONING_EFFORT='medium'
export API_REASONING_MODE='responses'
node examples/reasoning.mjs
```

选择支持 Chat 的模型后，可改用单轮模式：

```sh theme={null}
API_REASONING_MODE=chat node examples/reasoning.mjs
```

```javascript theme={null}
import { requestJSON } from './http-client.mjs';

try {
  const model = process.env.API_MODEL;
  const mode = process.env.API_REASONING_MODE || 'responses';
  const effort = process.env.API_REASONING_EFFORT || 'medium';
  if (!model?.trim() || !['chat', 'responses'].includes(mode)
    || !['none', 'minimal', 'low', 'medium', 'high', 'xhigh', 'max'].includes(effort)) {
    throw new Error('Invalid model, protocol, or effort.');
  }
  const history = [{ role: 'user', content: 'Find the smallest positive integer divisible by both 6 and 8.' }];
  const answers = [];
  for (let turn = 0; turn < (mode === 'chat' ? 1 : 2); turn++) {
    const result = await requestJSON(mode === 'chat' ? '/v1/chat/completions' : '/v1/responses',
      mode === 'chat' ? { model, messages: history, reasoning_effort: effort }
        : { model, input: history, reasoning: { effort }, store: false,
            include: ['reasoning.encrypted_content'], instructions: 'Return a short final answer.' });
    if (mode === 'chat') {
      const choice = result.choices?.[0];
      const message = choice?.message;
      if (!Array.isArray(result.choices) || result.choices.length !== 1 || choice?.index !== 0
        || choice.finish_reason !== 'stop' || message?.role !== 'assistant'
        || message.refusal != null || message.tool_calls != null || message.function_call != null
        || typeof message.content !== 'string' || !message.content.trim()) {
        throw new Error('No complete final text.');
      }
      answers.push(message.content);
      continue;
    }
    if (result.status !== 'completed' || !Array.isArray(result.output) || !result.output.length) {
      throw new Error('Incomplete response.');
    }
    const text = [];
    for (const item of result.output) {
      if (item?.type === 'reasoning') {
        // This stateless replay example requires opaque state whenever a reasoning item exists.
        if (typeof item.encrypted_content !== 'string' || !item.encrypted_content.trim()) {
          throw new Error('Reasoning state is not replayable by this example.');
        }
      } else if (item?.type === 'message' && item.role === 'assistant' && item.status === 'completed'
        && Array.isArray(item.content) && item.content.length) {
        for (const part of item.content) {
          if (part?.type !== 'output_text' || typeof part.text !== 'string') throw new Error('Unexpected content.');
          text.push(part.text);
        }
      } else { throw new Error('Unexpected output item.'); }
    }
    if (!text.join('').trim()) throw new Error('No final text.');
    answers.push(text.join(''));
    // Preserve complete items and unknown metadata, including phase; never log encrypted state.
    history.push(...result.output, { role: 'user', content: 'Now find the smallest positive multiple also divisible by 5.' });
  }
  console.log(JSON.stringify({ answers }));
} catch {
  console.error('Reasoning example failed. Check model support, replayable state, and completion status. No automatic retry.');
  process.exitCode = 1;
}
```

脚本不设置推理预算或模型特定采样参数。每次请求最多等待 60 秒、响应最多 1 MiB、无自动重试；仅在全部请求完整结束后输出答案。保留 `phase` 和未知元数据，不打印推理正文、摘要或密文。`include` 是兼容请求选项，不保证每条渠道返回密文；`store: false` 也不保证部署零留存。

## 验收顺序

先验证模型支持的 effort 和实际出站参数，再验证完成状态、工具闭环和第二轮状态回放。检查推理 token 用量时区分字段缺失与零；它不是答案字数，也不能仅凭数字推断质量或费用。跨模型切换、跨协议转换和上下文压缩后需重新验证可续用性。

依据：`30a6d88a4` 的请求 DTO、`relaykit/relayconvert/reasoning/intent.go`、`relay/channel/openai/adaptor.go` 和 `relay/helper/reasoning_suffix.go`。参考 [OpenRouter 推理专题](https://openrouter.ai/docs/guides/best-practices/reasoning-tokens)与 [OpenAI 推理说明](https://developers.openai.com/api/docs/guides/reasoning)。

[Chat API](/zh/api-reference/chat-completions) · [Responses API](/zh/api-reference/responses) · [上下文管理](/zh/guides/context-management)

[Read in English](/en/guides/reasoning-models)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.