统一 Thinking / Reasoning
网关将所有 provider 的 thinking 和 reasoning 统一为单一的请求规范和单一的响应格式。
请求:reasoning 参数
在任何 OpenAI 格式的请求体中发送顶层 reasoning 对象:
{
"model": "deepseek-reasoner",
"reasoning": {
"effort": "medium",
"max_tokens": 8000
},
"messages": [{ "role": "user", "content": "..." }]
}字段说明
| 字段 | 类型 | 说明 |
|---|---|---|
enabled | boolean | false 关闭 thinking,等效于 effort: "none" |
effort | string | "none" | "low" | "medium" | "high" | "xhigh",透传给支持 effort 的模型 |
max_tokens | number | 模型可用于推理的最大 token 数(thinking budget) |
exclude | boolean | 为 true 时,告知 provider 不在响应中包含 thinking 内容(Google 专用) |
所有字段均可选。reasoning: {} 空对象即以 provider 默认值启用 thinking。
关闭 thinking
{ "reasoning": { "enabled": false } }或:
{ "reasoning": { "effort": "none" } }Provider 原生 thinking 参数(Anthropic / Google)
Anthropic 不读取统一的 reasoning 字段,需直接传 Anthropic 原生 thinking 参数:
{
"thinking": { "type": "enabled", "budget_tokens": 8000 }
}{
"thinking": { "type": "disabled" }
}Google 同时支持 reasoning 和 thinking,两者会合并写入 generationConfig.thinking_config。
网关的 reasoning 参数映射规则
| Provider | reasoning.effort | reasoning.enabled / effort: "none" | reasoning.max_tokens |
|---|---|---|---|
| OpenAI | → reasoning_effort(字符串) | 不发送 | 不发送 |
| DeepSeek | → reasoning_effort + thinking.type | → thinking.type: "disabled" | 不发送 |
→ generationConfig.thinkingConfig.thinkingLevel | 不发送 | → generationConfig.thinking_config.thinking_budget | |
| Cohere | → thinking.type: "enabled" | → thinking.type: "disabled" | → thinking.budget_tokens + token_budget |
| Ollama | "low"/"medium"/"high" → think: <effort> | → think: false | 不发送 |
| DashScope | 不发送 | → enable_thinking: false | 不发送 |
| Anthropic | (不映射,请直接用 thinking 参数) | (不映射) | (不映射) |
响应:统一输出格式
无论使用哪个 provider,所有推理文本均返回在:
choices[n].message.reasoning (非流式)
choices[n].delta.reasoning (流式)两者均为纯 string。当模型没有产生 thinking 输出时,该字段省略(不设为 null 或 "")。
非流式
{
"choices": [{
"message": {
"role": "assistant",
"content": "答案是 42。",
"reasoning": "让我一步步分析..."
},
"finish_reason": "stop"
}]
}流式
data: {"choices":[{"delta":{"reasoning":"让我一步"},"index":0}]}
data: {"choices":[{"delta":{"reasoning":"步分析"},"index":0}]}
data: {"choices":[{"delta":{"content":"答案是 42。"},"index":0}]}
data: {"choices":[{"delta":{},"finish_reason":"stop","index":0}]}reasoning delta 和 content delta 在不同 chunk 中发送,每个 chunk 最多包含 delta.reasoning 或 delta.content 之一。
响应归一化流程
所有 provider 返回的 thinking 内容,在离开网关前均经过 normalizeOpenAIReasoningMessage 处理,按顺序从以下位置收集推理文本并拼接为一个 reasoning 字符串:
message.reasoning— 已是 OpenAI 兼容格式,直接使用。message.reasoning_content— DeepSeek 字段名,提取后删除。message.thinking— 部分 provider 的字段,提取后删除。message.content_blocks[]— 部分 provider 的数组格式,逐块提取 reasoning 字段。message.content[]中type: "thinking"/type: "redacted_thinking"的 part — Anthropic 流式 content part,提取后从数组中移除。message.content字符串中的<think>...</think>标签 — 开源模型(Qwen 等)的内联标签,提取后从 content 中删除。
归一化后:
message.reasoning_content被删除。message.thinking被删除。message.content_blocks被删除。message.content中的<think>标签和 thinking-type content part 被去除。- 若没有找到任何 reasoning,
reasoning字段完全省略(不设为null或"")。
流式响应的每个 delta 经过 normalizeOpenAIReasoningDelta 处理,在此基础上额外删除空字符串的 delta.content(避免发送 "content": "")。
Anthropic 的处理方式
Anthropic 将 thinking 以带 type 的 content block 数组返回。Anthropic 响应转换器在归一化前先遍历 response.content[]:
type: "text"block → 拼入message.contenttype: "thinking"block →block.thinking追加到reasoningtype: "redacted_thinking"block →block.data追加到reasoning
流式场景中,delta.type === "thinking_delta" 的 chunk 将 delta.thinking 映射为 delta.reasoning。
Google 的处理方式
Google 在 content parts[] 中通过 thought: true 标记返回推理内容。Google 响应转换器将这些 part 收集到 message.reasoning,并从 message.content 中排除。
Provider 支持情况
| Provider | reasoning 参数 | thinking 参数 | 响应已归一化 |
|---|---|---|---|
| OpenAI / Azure OpenAI | ✅ effort | — | ✅ |
| Anthropic | ❌ 请用 thinking | ✅ | ✅ |
| Google AI / Vertex AI | ✅ | ✅ | ✅ |
| DeepSeek | ✅ effort + enabled | — | ✅ |
| Cohere | ✅ | — | ✅ |
| Ollama | ✅ | — | ✅ |
| DashScope | ✅ 仅 enabled | — | ✅ |
| OpenRouter | — | — | ✅(透传后归一化) |
| Novita AI | — | — | ✅(透传后归一化) |
"响应已归一化"表示无论 upstream provider 返回何种格式,reasoning 内容都会被整合到 choices[n].message.reasoning 中。