Skip to content

Commit 561455b

Browse files
committed
fix(litellm): 保留 DeepSeek 兜底 thinking 参数
1 parent 5fb34aa commit 561455b

7 files changed

Lines changed: 146 additions & 28 deletions

File tree

.trellis/spec/infra/litellm-gateway.md

Lines changed: 13 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -36,14 +36,13 @@
3636
- `DEEPSEEK_API_KEY`: DeepSeek 密钥。
3737
- `LITELLM_MASTER_KEY`: LiteLLM 对外鉴权密钥。
3838
- Fallback-only parameter policy:
39-
- DeepSeek 兜底别名必须显式丢弃 `thinking``reasoning_effort`,用于覆盖普通 Chat/Responses 参数转换路径;原生 Anthropic messages 路径不要把当前请求的 top-level `thinking` 改成 disabled
39+
- `claude-code-deepseek-*` 是 Claude Code Anthropic messages 专用兜底入口,必须保留当前请求的 top-level `thinking``reasoning_effort``output_config.effort`;不得再通过 `additional_drop_params` 丢弃 `thinking` / `reasoning_effort`
4040
- Claude `/v1/messages` 原生路径必须启用 `callbacks.deepseek_thinking_sanitizer.proxy_handler_instance`,因为该路径会把历史 `messages[].content[]` 直接传给上游,`additional_drop_params` 不能移除 `content[].thinking` / `redacted_thinking` 内容块。
4141
- DeepSeek 官方 Claude Code 直连配置推荐 `CLAUDE_CODE_EFFORT_LEVEL=max`;在 Anthropic 兼容接口里,DeepSeek 的 effort 语义对应 `output_config.effort`,不是 OpenAI 兼容接口里的 `reasoning_effort`
4242
- 原生 Anthropic messages fallback 的核心问题是历史 assistant thinking 内容块有两类语义:带 `signature``thinking` 与带 `data``redacted_thinking` 是上游要求完整回传的不透明块;缺少签名/不透明数据的 thinking 块通常来自跨供应商或中间层转换,DeepSeek 无法校验。sanitizer 应保留可回传块,只清理不兼容块与 `thinking_blocks` 辅助字段。
43-
- 不得把 `output_config``output_config.effort` 加入 DeepSeek 兜底别名的 `additional_drop_params`;DeepSeek Anthropic 兼容接口使用它承接 effort。
44-
- 普通 Chat/Responses 路径丢弃 `thinking` 会让 DeepSeek 兜底不再显式请求 extended thinking;原生 Anthropic messages 路径由 sanitizer 只清历史块,不禁用当前请求 thinking。
45-
- 丢弃范围只绑定到 DeepSeek 兜底别名;不得在 GLM 主路由上全局禁用 Claude Code thinking。
46-
- 如果 `claude-code-deepseek-*` 被直接调用,也会应用同一丢弃策略;因此该别名应被视为 fallback/兼容专用入口。
43+
- 不得把 `thinking``reasoning_effort``output_config``output_config.effort` 加入 DeepSeek Claude Code 兜底别名的 `additional_drop_params`;DeepSeek Anthropic 兼容接口使用 top-level thinking 与 `output_config.effort` 承接 Claude Code effort。
44+
- 如果未来需要面向 Chat/Responses 的保守 DeepSeek 兼容入口,应新增独立 safe 路由,而不是让 `claude-code-deepseek-*` 牺牲 Claude Code thinking 能力。
45+
- 不得在 GLM 主路由上全局禁用 Claude Code thinking。
4746
- LiteLLM settings:
4847
- `drop_params: true` 用于丢弃上游不识别的普通参数。
4948
- `modify_params: true` 用于允许 LiteLLM 修正 Anthropic tool/thinking 历史块兼容问题。
@@ -65,7 +64,7 @@
6564
| GLM 正常可用 | `cc-glmplan-*` 直接走 GLM,保留 Claude Code thinking 语义 |
6665
| GLM 返回 429 / `RateLimitError` | LiteLLM 先按 retry policy 短重试 |
6766
| GLM 短重试耗尽 | Router fallback 到对应 `claude-code-deepseek-*` |
68-
| DeepSeek 收到顶层 `thinking` / `reasoning_effort` | 普通 Chat/Responses 路径由兜底别名的 `additional_drop_params` 处理;原生 Anthropic messages 路径应保留 top-level `thinking`,只清历史 thinking 块 |
67+
| DeepSeek 收到顶层 `thinking` / `reasoning_effort` | Claude Code DeepSeek 兜底路由应保留这些当前请求参数;sanitizer 只处理历史 content thinking 块 |
6968
| DeepSeek 收到带签名/不透明数据的历史 `content[].thinking` / `redacted_thinking` | sanitizer 必须原样保留这些块;DeepSeek thinking mode 需要它们维持工具调用回合的推理连续性 |
7069
| DeepSeek 收到无签名/不完整的历史 `content[].thinking` / `redacted_thinking` | sanitizer 必须在 deployment pre-call 阶段移除这些不兼容块,否则 DeepSeek 可能返回 thinking 历史校验错误 |
7170
| DeepSeek 返回 `thinking options type cannot be disabled when reasoning_effort is set` | 优先检查 sanitizer 是否错误设置了 `top_level_thinking_after: disabled`;正确策略是保留 top-level thinking,而不是 disabled + effort 共存 |
@@ -80,20 +79,20 @@
8079

8180
- Good: GLM 429 后切到 DeepSeek,原生 Anthropic `/v1/messages` fallback 保留当前 top-level `thinking`,同时只移除无签名/不完整的历史 thinking content 块。
8281
- Good: sanitizer 原地修改 `messages` 列表并递归清理嵌套 content;日志显示 `top_level_thinking_before: enabled/adaptive``top_level_thinking_after: enabled/adaptive``remaining_thinking_paths: []`,同时 `preserved_thinking_blocks_after` 可大于 0。
83-
- Good: DeepSeek 兜底别名不丢弃 `output_config.effort`;如果 Claude Code / LiteLLM 以 DeepSeek Anthropic 官方字段表达 effort,`CLAUDE_CODE_EFFORT_LEVEL=max` 仍有机会透传。
82+
- Good: DeepSeek 兜底别名不丢弃 `thinking``reasoning_effort``output_config.effort`;如果 Claude Code / LiteLLM 以 DeepSeek Anthropic 官方字段表达 effort,`CLAUDE_CODE_EFFORT_LEVEL=max` 仍有机会透传。
8483
- Base: GLM 正常响应时不触发 fallback,不改变 Claude Code 对 GLM 主路由的 thinking 使用方式。
8584
- Bad: 全局丢弃 `thinking`,导致 GLM 主路由也失去 Claude Code extended thinking 能力。
8685
- Bad: sanitizer 为了绕过历史校验而设置 `thinking: disabled`,导致 DeepSeek 报 `thinking options type cannot be disabled when reasoning_effort is set`
8786
- Bad: sanitizer 删除所有 `content[].thinking`,导致 DeepSeek 在带工具调用历史的 thinking mode 中报 `content[].thinking in the thinking mode must be passed back`
8887
- Bad: sanitizer 诊断函数直接用 `value.get("type") in THINKING_BLOCK_TYPES`,真实请求里 `type` 是 dict 时会在 LiteLLM logging pre-call 阶段抛异常,反而遮蔽 fallback 的真实错误。
89-
- Bad: 看到 DeepSeek 官方推荐 `CLAUDE_CODE_EFFORT_LEVEL=max` 后,把 fallback 别名改成保留 `thinking`;直连 DeepSeek 与跨供应商 fallback 的历史消息完整性不同,不能混为一谈
88+
- Bad: 为了兼容 Chat/Responses,把 `claude-code-deepseek-*` 继续配置成丢弃 `thinking` / `reasoning_effort`,导致 Claude Code 兜底链路失去 DeepSeek thinking / effort 能力
9089

9190
### 6. Tests Required
9291

9392
- Config parse: YAML 必须能被项目现有解析方式读取。
9493
- Config sync: 如果 `newapi.yaml``litellm.local.yaml` 应保持一致,修改后需要确认两者没有非预期差异。
9594
- Route contract: 检查 `router_settings.fallbacks` 仍指向专用 DeepSeek 兜底别名。
96-
- Parameter contract: 检查 `additional_drop_params` 只出现在 DeepSeek 兜底别名或其它明确的兼容专用路由上
95+
- Parameter contract: 检查 `claude-code-deepseek-*` 不再配置 `additional_drop_params` 丢弃 `thinking` / `reasoning_effort`;如果出现 safe 兼容路由,其命名必须与 Claude Code 兜底路由区分
9796
- Callback contract: 检查 `callbacks.deepseek_thinking_sanitizer.proxy_handler_instance` 能在 LiteLLM 镜像内导入,并实现 `async_pre_call_deployment_hook`,能在 `CallTypes.anthropic_messages` 且 deployment metadata 指向 DeepSeek 时原地清理请求参数。
9897
- Hook-stage contract: 离线测试必须直接调用 `async_pre_call_deployment_hook`,输入包含 `litellm_metadata.deployment` / `deployment_model_name` / `api_base`、顶层 `thinking` / `reasoning_effort`、历史 `content[].thinking` / `redacted_thinking`,断言清理发生在 provider 请求体构造前。
9998
- Reference contract: 离线测试必须断言原始 `messages` 列表对象 ID 不变,且清理后 `kwargs["messages"] is messages`;这是 Anthropic messages pass-through 位置参数链路的关键行为。
@@ -121,7 +120,7 @@ model_list:
121120
model: "anthropic/deepseek-v4-pro[1m]"
122121
```
123122
124-
问题:DeepSeek 兜底仍可能收到 Claude Code extended thinking 参数;跨供应商 fallback 时,历史消息缺少完整 `thinking_blocks` 会触发 `invalid_request_error`。
123+
问题:只配置 DeepSeek 路由而不挂载 sanitizer,会让跨供应商 fallback 的历史 thinking 内容块直接进入 DeepSeek;历史消息缺少完整签名或不透明数据时会触发 `invalid_request_error`。
125124

126125
#### Correct
127126

@@ -130,9 +129,6 @@ model_list:
130129
- model_name: "claude-code-deepseek-v4-pro"
131130
litellm_params:
132131
model: "anthropic/deepseek-v4-pro[1m]"
133-
additional_drop_params:
134-
- reasoning_effort
135-
- thinking
136132
137133
litellm_settings:
138134
drop_params: true
@@ -141,7 +137,7 @@ litellm_settings:
141137
- callbacks.deepseek_thinking_sanitizer.proxy_handler_instance
142138
```
143139

144-
理由:DeepSeek 兜底别名是降级链路专用入口,优先保证 GLM 429 后可用;主 GLM 路由仍保留 Claude Code extended thinking。`additional_drop_params` 处理普通参数,sanitizer 处理 Anthropic `/v1/messages` 历史内容块,两者不能互相替代。
140+
理由:DeepSeek 兜底别名是 Claude Code 降级链路专用入口,应保留当前请求的 thinking / effort 能力;sanitizer 只处理 Anthropic `/v1/messages` 历史 content thinking 块,两者不能互相替代。
145141

146142
#### DeepSeek effort vs thinking
147143

@@ -150,10 +146,8 @@ model_list:
150146
- model_name: "claude-code-deepseek-v4-pro"
151147
litellm_params:
152148
model: "anthropic/deepseek-v4-pro[1m]"
153-
additional_drop_params:
154-
- reasoning_effort
155-
- thinking
156-
# 不要加入 output_config 或 output_config.effort;DeepSeek Anthropic 兼容接口用它承接 effort。
149+
# 不要在 Claude Code 兜底路由上丢弃 thinking / reasoning_effort / output_config.effort;
150+
# DeepSeek Anthropic 兼容接口用它们承接 Claude Code thinking / effort。
157151
```
158152

159153
结论:`CLAUDE_CODE_EFFORT_LEVEL=max` 是 DeepSeek 官方 Claude Code 直连推荐配置;在 DeepSeek Anthropic 兼容接口里,effort 对应 `output_config.effort`。原生 Anthropic messages fallback 不应把当前请求降级为 `thinking: disabled`,否则会与 effort 冲突;正确做法是保留当前 thinking/effort,只清理历史 assistant thinking 块。
@@ -201,4 +195,4 @@ router_settings:
201195
- claude-code-deepseek-v4-pro-safe
202196
```
203197

204-
说明:可以先尝试完整 DeepSeek 路由,再 fallback 到只清理历史 thinking 块的 safe 路由,以尽量保留 DeepSeek 官方 thinking 能力。但 LiteLLM YAML 不能按 DeepSeek 返回的精确错误文本改写同一请求后重放;两级路由会增加配置复杂度和一次失败重试延迟。当前策略选择直接让 DeepSeek 兜底路由进入 safe 模式,优先保证 GLM 429 后 Claude Code 不被中断
198+
说明:可以先尝试完整 DeepSeek 路由,再 fallback 到只清理历史 thinking 块的 safe 路由,以尽量保留 DeepSeek 官方 thinking 能力。但 LiteLLM YAML 不能按 DeepSeek 返回的精确错误文本改写同一请求后重放;两级路由会增加配置复杂度和一次失败重试延迟。当前策略选择让 `claude-code-deepseek-*` 保留当前 thinking / effort,并由 sanitizer 在单一路由内清理历史 thinking 块
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
{"file": ".trellis/tasks/05-09-litellm-deepseek-thinking-param-policy/prd.md", "reason": "验收标准与范围边界"}
2+
{"file": ".trellis/spec/infra/litellm-gateway.md", "reason": "检查 LiteLLM DeepSeek 兜底参数与 sanitizer 契约是否同步"}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
{"file": ".trellis/tasks/05-09-litellm-deepseek-thinking-param-policy/prd.md", "reason": "实现范围、参数策略决策与验收标准"}
2+
{"file": ".trellis/spec/infra/litellm-gateway.md", "reason": "LiteLLM DeepSeek 兜底参数与 sanitizer 契约"}
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
# brainstorm: DeepSeek 兜底 thinking 参数策略
2+
3+
## Goal
4+
5+
厘清并收敛 `claude-code-deepseek-*` 兜底别名是否还需要在 `additional_drop_params` 中丢弃 `thinking` / `reasoning_effort`。目标是让 Claude Code 的 Anthropic `/v1/messages` fallback 保留 DeepSeek thinking 能力,同时避免 Chat/Responses 或 OpenAI 兼容路径把不兼容参数传给 DeepSeek。
6+
7+
## What I already know
8+
9+
* 用户质疑 `ai/gateway/litellm/litellm.local.yaml``claude-code-deepseek-v4-pro` 仍配置 `additional_drop_params: [reasoning_effort, thinking]`
10+
* 当前 sanitizer 已不再禁用 top-level `thinking`,并且只清理无签名/不完整的历史 thinking block;带 `signature``thinking` 与带 `data``redacted_thinking` 会保留。
11+
* 运行态源码显示原生 Anthropic messages 路径只对 `additional_drop_params` 中的 dotted nested path 做删除;普通字段名 `thinking` / `reasoning_effort` 主要影响 Chat/Responses / OpenAI 兼容参数转换路径。
12+
* DeepSeek Anthropic 兼容接口可接受 top-level `thinking: {"type": "adaptive"}``output_config.effort`,真实 smoke 已验证 `/v1/messages` fallback 返回 200。
13+
* 用户已选择 Approach B:移除 `claude-code-deepseek-*``additional_drop_params`,把该别名定位为 Claude Code Anthropic messages 专用兜底入口。
14+
15+
## Assumptions (temporary)
16+
17+
* Claude Code 主路径是原生 Anthropic `/v1/messages?beta=true`,不是 OpenAI chat/completions。
18+
* `claude-code-deepseek-*` 不承担 Chat/Responses 保守兼容职责;如果未来需要,应新增独立 safe 路由。
19+
20+
## Open Questions
21+
22+
* 无。
23+
24+
## Requirements (evolving)
25+
26+
* 明确 `additional_drop_params` 在 Anthropic messages 与 Chat/Responses 路径中的真实影响范围。
27+
* 保持 GLM 429 fallback 到 DeepSeek 的 `/v1/messages` 链路可用。
28+
* 避免再次引入 `thinking: disabled` 与 effort 冲突。
29+
* 文档必须区分 “top-level current thinking 参数” 与 “历史 content thinking block”。
30+
* `claude-code-deepseek-*` 不得通过 `additional_drop_params` 丢弃 `thinking` / `reasoning_effort`
31+
32+
## Acceptance Criteria (evolving)
33+
34+
* [x] 选定并记录 `claude-code-deepseek-*` 是否保留 `additional_drop_params` 的策略。
35+
* [x] 如果修改 YAML,`newapi.yaml``litellm.local.yaml` 保持预期一致。
36+
* [x] `/v1/messages?beta=true` fallback 仍返回 200。
37+
* [x] sanitizer 日志中 top-level `thinking` 不被降级,signed thinking 不被误删。
38+
* [x] 文档与 Trellis spec 同步记录最终策略。
39+
40+
## Definition of Done (team quality bar)
41+
42+
* Tests added/updated (unit/integration where appropriate)
43+
* Lint / typecheck / CI green
44+
* Docs/notes updated if behavior changes
45+
* Rollout/rollback considered if risky
46+
47+
## Out of Scope (explicit)
48+
49+
* 不在本任务中重写 LiteLLM Router fallback 机制。
50+
* 不新增外部代理或替换 DeepSeek Anthropic 上游。
51+
* 不处理 `.codex/config.toml``.shrimp-data/` 这些已有未提交工作区改动。
52+
53+
## Research Notes
54+
55+
### Code inspection
56+
57+
* `ai/gateway/litellm/litellm.local.yaml``ai/gateway/litellm/newapi.yaml` 的 DeepSeek 兜底别名仍配置 `additional_drop_params: [reasoning_effort, thinking]`
58+
* LiteLLM 容器内 `llm_http_handler.py` 的 Anthropic messages path 只从 `additional_drop_params` 中取 dotted nested path 并对 `anthropic_messages_optional_request_params` 做删除,普通字段名不会删除原生 messages 的 top-level `thinking`
59+
* LiteLLM `utils.py``_should_drop_param` 会让普通字段名 drop 作用于 OpenAI 兼容参数映射路径。
60+
61+
### Feasible approaches
62+
63+
**Approach A: 保守保留 drop(当前状态)**
64+
65+
* How it works: 保留 `additional_drop_params: [reasoning_effort, thinking]`,原生 `/v1/messages` 依靠 sanitizer 保留 top-level thinking;Chat/Responses 继续丢弃这两个参数。
66+
* Pros: 对非 Claude Code 路径更保守,减少未知兼容风险。
67+
* Cons: 配置语义容易误导,名字叫 Claude Code 但 Responses 路径会失去 thinking。
68+
69+
**Approach B: 移除 drop(推荐,如果该别名只给 Claude Code 用)**
70+
71+
* How it works: 从 `claude-code-deepseek-*` 别名移除 `additional_drop_params`,让 DeepSeek Anthropic 兼容接口接收当前 thinking/effort;历史 content block 仍由 sanitizer 清理。
72+
* Pros: 语义最一致,DeepSeek thinking 能力不被配置层静默拿掉。
73+
* Cons: 如果有人直接用该别名走 Chat/Responses,可能重新暴露 provider 参数兼容问题。
74+
75+
**Approach C: 拆路由**
76+
77+
* How it works: `claude-code-deepseek-*` 移除 drop,新增 `deepseek-compat-safe-*` 之类路由给 Chat/Responses 保守 drop。
78+
* Pros: 能力与兼容边界最清晰。
79+
* Cons: 配置更复杂,fallback 规则和文档都要增加维护成本。
80+
81+
## Technical Notes
82+
83+
* 相关文件:
84+
* `ai/gateway/litellm/litellm.local.yaml`
85+
* `ai/gateway/litellm/newapi.yaml`
86+
* `ai/gateway/litellm/callbacks/deepseek_thinking_sanitizer_core.py`
87+
* `.trellis/spec/infra/litellm-gateway.md`
88+
* `ai/gateway/litellm/litellm.md`
89+
* Context7 查询 LiteLLM 文档未直接返回 `additional_drop_params` 专门章节;以运行容器源码为准。
90+
91+
## Decision (ADR-lite)
92+
93+
**Context**: `claude-code-deepseek-*` 是 GLM 429 后的 Claude Code Anthropic messages 兜底入口。上轮保留 `additional_drop_params: [thinking, reasoning_effort]` 是为了 Chat/Responses 路径保守兼容,但会让配置语义变成“Claude Code 兜底仍静默丢弃 thinking/effort”。
94+
95+
**Decision**: 采用 Approach B,移除 `claude-code-deepseek-*``additional_drop_params`。Claude Code 兜底路由保留当前 top-level `thinking``reasoning_effort``output_config.effort`;历史 content thinking 兼容仍由 sanitizer 处理。
96+
97+
**Consequences**: Claude Code fallback 保留 DeepSeek thinking / effort 能力;如果未来要支持 Chat/Responses 的保守 DeepSeek 兼容,应新增独立 safe 路由,而不是复用 `claude-code-deepseek-*`
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
{
2+
"id": "litellm-deepseek-thinking-param-policy",
3+
"name": "litellm-deepseek-thinking-param-policy",
4+
"title": "brainstorm: DeepSeek 兜底 thinking 参数策略",
5+
"description": "",
6+
"status": "in_progress",
7+
"dev_type": null,
8+
"scope": null,
9+
"package": null,
10+
"priority": "P2",
11+
"creator": "codex",
12+
"assignee": "codex",
13+
"createdAt": "2026-05-09",
14+
"completedAt": null,
15+
"branch": null,
16+
"base_branch": "master",
17+
"worktree_path": null,
18+
"commit": null,
19+
"pr_url": null,
20+
"subtasks": [],
21+
"children": [],
22+
"parent": null,
23+
"relatedFiles": [],
24+
"notes": "",
25+
"meta": {}
26+
}

0 commit comments

Comments
 (0)