Skip to content

Commit c2dc347

Browse files
committed
fix(litellm): 清理 DeepSeek 兜底 thinking 历史
GLM 429 fallback 走 Claude /v1/messages 原生 pass-through 时,LiteLLM 的 additional_drop_params 只能覆盖普通参数路径,不能删除历史 messages[].content[] 中的 thinking/redacted_thinking 块。DeepSeek 会校验这些历史块并返回 content[].thinking in the thinking mode must be passed back,导致降级链路仍然中断。 新增 DeepSeekThinkingSanitizer callback,在发往 DeepSeek Anthropic 端点前清理顶层 thinking/reasoning_effort 和历史 thinking 内容块;compose 挂载本地 callbacks,配置启用该回调,并把这次定位结论写入 LiteLLM 文档与 Trellis infra 规范。 Constraint: Claude Code 使用 /v1/messages?beta=true,LiteLLM 走 Anthropic 原生 messages pass-through Constraint: additional_drop_params 不覆盖 messages[].content[] 历史内容块 Rejected: 只继续调整 additional_drop_params | 容器代码与日志证明它够不着 DeepSeek 报错的 content[].thinking Confidence: high Scope-risk: narrow Directive: 修改 DeepSeek 兜底 thinking 策略时,必须同时验证 Anthropic messages pass-through 路径与 callback 挂载 Tested: pnpm qa Tested: uvx ruff check/format --check ai/gateway/litellm/callbacks/deepseek_thinking_sanitizer.py Tested: 容器重建后 /health/readiness 显示 DeepSeekThinkingSanitizer 已加载 Tested: 容器内模拟 DeepSeek Anthropic request body,确认 thinking/reasoning_effort 与历史 thinking/redacted_thinking 块被清理 Not-tested: 未强制制造真实 GLM 429 端到端 fallback,请求依赖上游额度和实时状态
1 parent 19a8abf commit c2dc347

6 files changed

Lines changed: 144 additions & 8 deletions

File tree

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

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
### 1. Scope / Trigger
1010

11-
- Trigger: 修改 `ai/gateway/litellm/*.yaml` 中 Claude Code GLM 入口、DeepSeek 兜底别名、`router_settings.fallbacks``additional_drop_params``litellm_settings.modify_params`
11+
- Trigger: 修改 `ai/gateway/litellm/*.yaml` 中 Claude Code GLM 入口、DeepSeek 兜底别名、`router_settings.fallbacks``additional_drop_params``litellm_settings.callbacks``callbacks/deepseek_thinking_sanitizer.py``litellm_settings.modify_params`
1212
- Scope: `cc-glmplan-opus` / `cc-glmplan-haiku` 主路由优先使用智谱 GLM Coding Plan;GLM 返回 429 或 LiteLLM `RateLimitError` 后短重试,仍失败才 fallback 到 DeepSeek Anthropic 兼容端点。
1313
- Design intent: 主路由尽量保留 Claude Code extended thinking;兜底路由优先保证请求不中断。
1414

@@ -37,6 +37,7 @@
3737
- `LITELLM_MASTER_KEY`: LiteLLM 对外鉴权密钥。
3838
- Fallback-only parameter policy:
3939
- DeepSeek 兜底别名必须显式丢弃 `thinking``reasoning_effort`
40+
- Claude `/v1/messages` 原生路径必须启用 `callbacks.deepseek_thinking_sanitizer.proxy_handler_instance`,因为该路径会把历史 `messages[].content[]` 直接传给上游,`additional_drop_params` 不能移除 `content[].thinking` / `redacted_thinking` 内容块。
4041
- DeepSeek 官方 Claude Code 直连配置推荐 `CLAUDE_CODE_EFFORT_LEVEL=max`;在 Anthropic 兼容接口里,DeepSeek 的 effort 语义对应 `output_config.effort`,不是 OpenAI 兼容接口里的 `reasoning_effort`
4142
- 不得把 `output_config` 加入 DeepSeek 兜底别名的 `additional_drop_params`;当前只丢弃 `thinking``reasoning_effort`,避免误伤 DeepSeek 官方 Anthropic effort 参数。
4243
- 丢弃 `thinking` 会让 DeepSeek 兜底不再显式请求 extended thinking;这是用 fallback 质量上限换取 GLM 429 后链路不中断。
@@ -45,6 +46,7 @@
4546
- LiteLLM settings:
4647
- `drop_params: true` 用于丢弃上游不识别的普通参数。
4748
- `modify_params: true` 用于允许 LiteLLM 修正 Anthropic tool/thinking 历史块兼容问题。
49+
- `callbacks` 必须包含 DeepSeek thinking sanitizer;`compose.yaml` 必须挂载 `./callbacks:/app/callbacks:ro`,否则配置中的 Python 回调无法导入。
4850

4951
### 4. Validation & Error Matrix
5052

@@ -53,14 +55,16 @@
5355
| GLM 正常可用 | `cc-glmplan-*` 直接走 GLM,保留 Claude Code thinking 语义 |
5456
| GLM 返回 429 / `RateLimitError` | LiteLLM 先按 retry policy 短重试 |
5557
| GLM 短重试耗尽 | Router fallback 到对应 `claude-code-deepseek-*` |
56-
| DeepSeek 收到 `thinking` / `reasoning_effort` | 配置必须在兜底别名处提前丢弃这些参数 |
58+
| DeepSeek 收到顶层 `thinking` / `reasoning_effort` | 兜底别名的 `additional_drop_params` 覆盖普通 Chat/Responses 路径;原生 Anthropic messages 路径由 sanitizer 在 HTTP pre-call 阶段移除 |
59+
| DeepSeek 收到历史 `content[].thinking` / `redacted_thinking` | sanitizer 必须在 HTTP pre-call 阶段移除这些历史块,否则 DeepSeek 会返回 `content[].thinking in the thinking mode must be passed back` |
5760
| DeepSeek 收到 `output_config.effort` | 不应通过 `additional_drop_params` 丢弃;这是 DeepSeek Anthropic 兼容接口承接 `CLAUDE_CODE_EFFORT_LEVEL=max` 的官方字段 |
5861
| 历史消息缺少完整 `thinking_blocks` | `modify_params` 允许 LiteLLM 做兼容修正,避免 fallback 被 Anthropic 兼容端点拒绝 |
5962
| GLM 与 DeepSeek 都失败 | LiteLLM 将最终错误返回给 Claude Code,不伪装成功 |
6063

6164
### 5. Good/Base/Bad Cases
6265

6366
- Good: GLM 429 后切到 DeepSeek,DeepSeek 不接收 `thinking` / `reasoning_effort`,请求以普通非-thinking 模式继续完成。
67+
- Good: 原生 Anthropic `/v1/messages` fallback 到 DeepSeek 前,sanitizer 移除顶层 `thinking` 和历史 `thinking` / `redacted_thinking` content 块。
6468
- Good: DeepSeek 兜底别名不丢弃 `output_config.effort`;如果 Claude Code / LiteLLM 以 DeepSeek Anthropic 官方字段表达 effort,`CLAUDE_CODE_EFFORT_LEVEL=max` 仍有机会透传。
6569
- Base: GLM 正常响应时不触发 fallback,不改变 Claude Code 对 GLM 主路由的 thinking 使用方式。
6670
- Bad: 全局丢弃 `thinking`,导致 GLM 主路由也失去 Claude Code extended thinking 能力。
@@ -73,6 +77,7 @@
7377
- Config sync: 如果 `newapi.yaml``litellm.local.yaml` 应保持一致,修改后需要确认两者没有非预期差异。
7478
- Route contract: 检查 `router_settings.fallbacks` 仍指向专用 DeepSeek 兜底别名。
7579
- Parameter contract: 检查 `additional_drop_params` 只出现在 DeepSeek 兜底别名或其它明确的兼容专用路由上。
80+
- Callback contract: 检查 `callbacks.deepseek_thinking_sanitizer.proxy_handler_instance` 能在 LiteLLM 镜像内导入,并能原地清理 DeepSeek Anthropic request body。
7681
- Runtime note: 真实 429 fallback 依赖上游额度、密钥和实时响应;本地配置验证不能证明线上额度恢复或供应商端协议行为。
7782

7883
### 7. Wrong vs Correct
@@ -108,9 +113,11 @@ model_list:
108113
litellm_settings:
109114
drop_params: true
110115
modify_params: true
116+
callbacks:
117+
- callbacks.deepseek_thinking_sanitizer.proxy_handler_instance
111118
```
112119

113-
理由:DeepSeek 兜底别名是降级链路专用入口,优先保证 GLM 429 后可用;主 GLM 路由仍保留 Claude Code extended thinking。
120+
理由:DeepSeek 兜底别名是降级链路专用入口,优先保证 GLM 429 后可用;主 GLM 路由仍保留 Claude Code extended thinking。`additional_drop_params` 处理普通参数,sanitizer 处理 Anthropic `/v1/messages` 历史内容块,两者不能互相替代。
114121

115122
#### DeepSeek effort vs thinking
116123

@@ -127,6 +134,16 @@ model_list:
127134

128135
结论:`CLAUDE_CODE_EFFORT_LEVEL=max` 是 DeepSeek 官方 Claude Code 直连推荐配置;在 DeepSeek Anthropic 兼容接口里,effort 对应 `output_config.effort`。兜底配置丢弃 `reasoning_effort` 主要影响 OpenAI 风格参数,丢弃 `thinking` 则会关闭显式 extended thinking。该取舍只应用于跨供应商 fallback,因为 GLM 生成的历史 `thinking` 块不一定满足 DeepSeek/Anthropic 兼容端点对完整 thinking 历史的校验。
129136

137+
#### Anthropic messages sanitizer
138+
139+
```yaml
140+
litellm_settings:
141+
callbacks:
142+
- callbacks.deepseek_thinking_sanitizer.proxy_handler_instance
143+
```
144+
145+
说明:Claude Code 使用 `/v1/messages?beta=true` 时,LiteLLM 走 Anthropic 原生 messages pass-through。该路径的 `messages` 与 `thinking` 不走普通 OpenAI 参数映射,`additional_drop_params` 不能删除历史 `messages[*].content[*]` 中的 thinking 内容块。DeepSeek 返回 `content[].thinking in the thinking mode must be passed back` 时,应先确认 sanitizer 已挂载并加载,而不是只改 `additional_drop_params`。
146+
130147
#### Deferred option: two-stage DeepSeek fallback
131148

132149
```yaml

ai/coding/claude/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,7 @@ pwsh -NoProfile -File ./ai/coding/claude/Sync-ClaudeConfig.ps1
222222
}
223223
```
224224

225-
这里的 key 是 LiteLLM 网关 `LITELLM_MASTER_KEY`,上游智谱和 DeepSeek 密钥仍放在 `ai/gateway/litellm/.env.local`。GLM 额度耗尽时 LiteLLM 会先短重试,再自动降级到 DeepSeek,并在冷却 1 小时后重新尝试 GLM。DeepSeek 兜底会丢弃 Claude Code 的 extended thinking 参数,避免跨供应商 fallback 时因为历史消息缺少 `thinking_blocks` 被兼容端点拒绝`CLAUDE_CODE_EFFORT_LEVEL=max` 在 DeepSeek Anthropic 官方接口中对应 `output_config.effort`,当前兜底配置不会丢弃 `output_config`,但会牺牲显式 `thinking` 模式以保证 fallback 可用。如果 GLM 重试和 DeepSeek 兜底都失败,最终错误仍会返回给 Claude Code。
225+
这里的 key 是 LiteLLM 网关 `LITELLM_MASTER_KEY`,上游智谱和 DeepSeek 密钥仍放在 `ai/gateway/litellm/.env.local`。GLM 额度耗尽时 LiteLLM 会先短重试,再自动降级到 DeepSeek,并在冷却 1 小时后重新尝试 GLM。DeepSeek 兜底会丢弃 Claude Code 的 extended thinking 参数,并由 LiteLLM 本地 callback 清理 `/v1/messages` 历史里的 `content[].thinking` 块,避免跨供应商 fallback 时被 DeepSeek 的 thinking 历史校验拒绝`CLAUDE_CODE_EFFORT_LEVEL=max` 在 DeepSeek Anthropic 官方接口中对应 `output_config.effort`,当前兜底配置不会丢弃 `output_config`,但会牺牲显式 `thinking` 模式以保证 fallback 可用。如果 GLM 重试和 DeepSeek 兜底都失败,最终错误仍会返回给 Claude Code。
226226

227227
### 场景 4:切换到新机器
228228

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
"""LiteLLM DeepSeek 兜底请求清洗回调。"""
2+
3+
from typing import Any
4+
5+
from litellm.integrations.custom_logger import CustomLogger
6+
7+
DEEPSEEK_MODEL_PREFIXES = ("deepseek-v4-pro", "deepseek-v4-flash")
8+
THINKING_BLOCK_TYPES = {"thinking", "redacted_thinking"}
9+
10+
11+
def _is_deepseek_anthropic_request(kwargs: dict[str, Any]) -> bool:
12+
"""判断当前请求是否即将发往 DeepSeek Anthropic 兼容端点。
13+
14+
Args:
15+
kwargs: LiteLLM 传入的模型调用上下文。
16+
17+
Returns:
18+
命中 DeepSeek Anthropic 请求时返回 True,否则返回 False。
19+
"""
20+
additional_args = kwargs.get("additional_args") or {}
21+
request_body = additional_args.get("complete_input_dict")
22+
request_model = ""
23+
if isinstance(request_body, dict):
24+
request_model = str(request_body.get("model") or "")
25+
26+
api_base = str(additional_args.get("api_base") or "")
27+
return request_model.startswith(DEEPSEEK_MODEL_PREFIXES) or ("deepseek" in api_base and "/anthropic/" in api_base)
28+
29+
30+
def _without_thinking_blocks(content: Any) -> Any:
31+
"""移除 Anthropic content 列表里的 thinking 内容块。
32+
33+
Args:
34+
content: 单条消息的 content 字段。
35+
36+
Returns:
37+
清理后的 content;非列表内容会原样返回。
38+
"""
39+
if not isinstance(content, list):
40+
return content
41+
42+
return [block for block in content if not (isinstance(block, dict) and block.get("type") in THINKING_BLOCK_TYPES)]
43+
44+
45+
def _sanitize_messages(messages: Any) -> Any:
46+
"""清理历史消息中 DeepSeek 兜底无法校验的 thinking 块。
47+
48+
Args:
49+
messages: Anthropic messages 请求体中的 messages 字段。
50+
51+
Returns:
52+
清理后的 messages;非列表内容会原样返回。
53+
"""
54+
if not isinstance(messages, list):
55+
return messages
56+
57+
sanitized_messages: list[Any] = []
58+
for message in messages:
59+
if not isinstance(message, dict):
60+
sanitized_messages.append(message)
61+
continue
62+
63+
sanitized_message = dict(message)
64+
sanitized_message.pop("thinking_blocks", None)
65+
sanitized_content = _without_thinking_blocks(sanitized_message.get("content"))
66+
if isinstance(sanitized_content, list) and len(sanitized_content) == 0:
67+
# thinking-only 历史消息对 DeepSeek 兜底没有可见上下文价值,
68+
# 保留空内容反而可能触发上游校验失败。
69+
if sanitized_message.get("role") == "assistant":
70+
continue
71+
sanitized_message["content"] = ""
72+
else:
73+
sanitized_message["content"] = sanitized_content
74+
75+
sanitized_messages.append(sanitized_message)
76+
77+
return sanitized_messages
78+
79+
80+
class DeepSeekThinkingSanitizer(CustomLogger):
81+
"""在 DeepSeek Anthropic 兜底请求发出前移除不兼容的 thinking 历史。"""
82+
83+
def log_pre_api_call(self, model: str, messages: list, kwargs: dict) -> None:
84+
"""在 LiteLLM 即将发送 HTTP 请求前清理 DeepSeek fallback 请求体。
85+
86+
Args:
87+
model: LiteLLM 记录的当前模型名。
88+
messages: LiteLLM 记录的原始消息列表。
89+
kwargs: LiteLLM 模型调用上下文,包含即将序列化的完整请求体引用。
90+
91+
Returns:
92+
无返回值;函数会原地修改 DeepSeek Anthropic 请求体。
93+
"""
94+
if not _is_deepseek_anthropic_request(kwargs):
95+
return
96+
97+
additional_args = kwargs.get("additional_args") or {}
98+
request_body = additional_args.get("complete_input_dict")
99+
if not isinstance(request_body, dict):
100+
return
101+
102+
request_body.pop("thinking", None)
103+
request_body.pop("reasoning_effort", None)
104+
request_body["messages"] = _sanitize_messages(request_body.get("messages"))
105+
106+
# 同步 LiteLLM 日志上下文,避免清理后的请求体和日志里的顶层字段不一致。
107+
kwargs.pop("thinking", None)
108+
kwargs.pop("reasoning_effort", None)
109+
kwargs["messages"] = request_body.get("messages")
110+
111+
112+
proxy_handler_instance = DeepSeekThinkingSanitizer()

ai/gateway/litellm/compose.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ services:
3737
DASHSCOPE_API_BASE: ${DASHSCOPE_API_BASE:-https://dashscope.aliyuncs.com/compatible-mode/v1}
3838
volumes:
3939
- ./litellm.local.yaml:/app/config.yaml:ro
40+
# LiteLLM 自定义回调用于处理跨供应商 Anthropic messages 兼容清洗。
41+
- ./callbacks:/app/callbacks:ro
4042
extra_hosts:
4143
# 在 Linux Docker Engine 场景补齐宿主机别名,保持默认 DATABASE_URL 可用。
4244
- "host.docker.internal:host-gateway"

ai/gateway/litellm/litellm.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ curl http://127.0.0.1:34000/v1/chat/completions `
180180
- `ANTHROPIC_API_KEY` 使用 LiteLLM 的 `LITELLM_MASTER_KEY`,不要填写上游真实密钥。
181181
- `cc-glmplan-opus` 先走智谱 `GLM-5.1`;遇到 429 / `RateLimitError` 时由 LiteLLM 网关短重试,仍失败后会降级到 `claude-code-deepseek-v4-pro`
182182
- `cc-glmplan-haiku` 先走智谱 `GLM-5.1`;遇到 429 / `RateLimitError` 时由 LiteLLM 网关短重试,仍失败后会降级到 `claude-code-deepseek-v4-flash`
183-
- DeepSeek 兜底路由会丢弃 `thinking` / `reasoning_effort` 这类 extended thinking 参数,并启用 LiteLLM 的 `modify_params` 兼容修正;这能避免跨供应商 fallback 时因为历史消息缺少 `thinking_blocks` 被 Anthropic 兼容端点拒绝
183+
- DeepSeek 兜底路由会丢弃 `thinking` / `reasoning_effort` 这类 extended thinking 参数;Claude `/v1/messages` 原生路径还会通过 `callbacks/deepseek_thinking_sanitizer.py` 在发往 DeepSeek 前清理历史 `content[].thinking` / `redacted_thinking` 块,避免跨供应商 fallback 时被 DeepSeek 的 thinking 历史校验拒绝
184184
- DeepSeek 官方 Claude Code 直连配置推荐 `CLAUDE_CODE_EFFORT_LEVEL=max`;在 DeepSeek Anthropic 兼容接口里,effort 对应 `output_config.effort`。当前兜底只丢弃 `thinking` / `reasoning_effort`,不丢弃 `output_config`,避免误伤官方 effort 字段;代价是 fallback 不再显式请求 extended thinking。
185185
- GLM 两个 Claude Code 入口的 `cooldown_time=3600` 表示失败后冷却 1 小时;冷却结束后下一次请求会重新尝试 GLM,如果额度恢复就会切回 GLM。
186186
- 429 无感只覆盖这两个 Claude Code GLM 入口;如果 GLM 重试和 DeepSeek fallback 全部失败,LiteLLM 仍会把最终错误返回给 Claude Code / 客户端。
@@ -252,9 +252,11 @@ curl "$env:Z_AI_CODING_API_BASE/models" `
252252
- `cc-glmplan-opus`:为 Claude Code 提供稳定主入口,优先走智谱 `GLM-5.1` 的 Anthropic 兼容端点。
253253
- `cc-glmplan-haiku`:为 Claude Code Haiku / subagent 流量提供独立入口,优先走智谱 `GLM-5.1` 的 Anthropic 兼容端点。
254254
- `claude-code-deepseek-v4-pro` / `claude-code-deepseek-v4-flash`:作为 GLM 额度耗尽或临时不可用时的 DeepSeek 兜底路由。
255-
- `additional_drop_params`:只在 DeepSeek 兜底路由上丢弃 `thinking` / `reasoning_effort`,优先保证 429 后请求能继续完成;GLM 主入口仍保留 Claude Code 传入的 thinking 语义。
255+
- `additional_drop_params`:只在 DeepSeek 兜底路由上丢弃普通参数路径里的 `thinking` / `reasoning_effort`;GLM 主入口仍保留 Claude Code 传入的 thinking 语义。
256+
- `callbacks/deepseek_thinking_sanitizer.py`:只在 DeepSeek Anthropic 请求发出前清理顶层 `thinking` / `reasoning_effort` 和历史 `thinking` / `redacted_thinking` content 块;这是给 Claude Code `/v1/messages` pass-through 路径补的兼容层,不能只靠 `additional_drop_params` 替代。
256257
- DeepSeek effort 兼容:不要把 `output_config` 加入 DeepSeek 兜底路由的丢弃参数;DeepSeek Anthropic 兼容接口使用 `output_config.effort` 承接 Claude Code 的 `CLAUDE_CODE_EFFORT_LEVEL=max`
257-
- `litellm_settings.modify_params`:允许 LiteLLM 对 Anthropic tool/thinking 消息做兼容修正;当历史消息缺少必要的 `thinking_blocks` 时,本轮兜底请求会降级掉不安全的 thinking 参数。
258+
- `litellm_settings.callbacks`:加载 DeepSeek thinking sanitizer;`compose.yaml` 必须挂载 `./callbacks:/app/callbacks:ro`,修改后需要重建容器才能让新挂载生效。
259+
- `litellm_settings.modify_params`:允许 LiteLLM 对 Anthropic tool/thinking 消息做兼容修正;但它不能替代上面的 sanitizer,因为 DeepSeek 报错来自原生 `messages[].content[]` 历史块校验。
258260
- `router_settings.num_retries` / `retry_policy.RateLimitErrorRetries`:让 Claude Code GLM 入口的瞬时 429 先在网关内短重试,避免直接把限流错误透给客户端。
259261
- `router_settings.fallbacks`:把 Claude Code 的 GLM 主入口分别降级到对应 DeepSeek 路由,并通过 GLM 部署自己的 1 小时冷却实现周期性恢复探测。
260262
- `GLM-*` fallback:对智谱 Coding Plan 已存在但未显式注册的 GLM 官方模型保留透传能力,同时避免误落到 NewAPI。

ai/gateway/litellm/newapi.yaml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ model_list:
6868
api_key: "os.environ/DEEPSEEK_API_KEY" # 从环境变量读 DeepSeek 密钥
6969
# 兜底时保持用户原先 Claude Code 的 pro 默认模型。
7070
model: "anthropic/deepseek-v4-pro[1m]"
71-
# DeepSeek 兜底优先接住 GLM 429;跨供应商时不强制保留 Claude Code extended thinking 参数。
71+
# 普通 Chat/Responses 路径不强制保留 Claude Code extended thinking 参数;原生 Anthropic messages 由回调清理历史 thinking 块
7272
additional_drop_params:
7373
- reasoning_effort
7474
- thinking
@@ -124,6 +124,9 @@ litellm_settings:
124124
drop_params: true
125125
# 允许 LiteLLM 修正 Anthropic tool/thinking 历史块不完整的问题,避免 DeepSeek 兜底被兼容性错误拦截。
126126
modify_params: true
127+
# DeepSeek Anthropic 兼容端点会校验 thinking 历史块;GLM fallback 时由本地回调移除不完整 thinking 历史。
128+
callbacks:
129+
- callbacks.deepseek_thinking_sanitizer.proxy_handler_instance
127130
# 默认重试 2 次,兼顾临时抖动恢复与整体响应时延。
128131
num_retries: 2
129132
# 统一请求超时为 60 秒,覆盖常规对话模型与较慢的推理模型。

0 commit comments

Comments
 (0)