Skip to content

Commit 680905f

Browse files
committed
chore(task): archive 05-12-glm-429-cooldown-thinking-fallback
1 parent 5e0c4d5 commit 680905f

5 files changed

Lines changed: 256 additions & 0 deletions

File tree

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"_example": "Fill with {\"file\": \"<path>\", \"reason\": \"<why>\"}. Put spec/research files only — no code paths. Run `python3 .trellis/scripts/get_context.py --mode packages` to list available specs. Delete this line once real entries are added."}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"_example": "Fill with {\"file\": \"<path>\", \"reason\": \"<why>\"}. Put spec/research files only — no code paths. Run `python3 .trellis/scripts/get_context.py --mode packages` to list available specs. Delete this line once real entries are added."}
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
# brainstorm: 优化 GLM 429 冷却与 fallback thinking 处理
2+
3+
## Goal
4+
5+
优化 Claude Code 通过 LiteLLM 使用 GLM Coding Plan 时的 429 处理:GLM 的额度窗口是 5 小时,而当前固定 1 小时 `cooldown_time` 会导致窗口内反复探测;同时把现有单用途 callback 演进为可组合的 LiteLLM callback adapter 框架,让 DeepSeek thinking sanitizer、GLM cooldown 等逻辑可以独立配置、独立测试、独立扩展。
6+
7+
## What I already know
8+
9+
* 用户提供的 GLM 429 示例会在错误体中返回中文重置时间:`已达到 5 小时的使用上限。您的限额将在 2026-05-08 05:32:56 重置。`
10+
* 用户期望按错误体中的恢复时间再往后延 1 分钟恢复,避免中途多次多余请求。
11+
* 当前 `ai/gateway/litellm/litellm.local.yaml``cc-glmplan-opus` / `cc-glmplan-haiku` 都配置 `cooldown_time: 3600`
12+
* `ai/gateway/litellm/newapi.yaml` 中对应配置同样是 `cooldown_time: 3600`,若改配置应同步。
13+
* DeepSeek sanitizer 已迁移为 `callbacks.gateway_callback.proxy_handler_instance` 下的 adapter,旧单用途 callback 入口已删除。
14+
* sanitizer 不是只在 fallback 时调用;LiteLLM 会按 callback 生命周期调用 hook,当前代码再用 call_type 与 DeepSeek deployment/api_base 过滤实际清理范围。
15+
* GLM 正常路径不会被 sanitizer 清理;GLM fallback 到 DeepSeek 或直接调用 `claude-code-deepseek-*` 时会被清理。
16+
* 当前 fallback thinking 报错更像是 DeepSeek 收到必须回传的 signed/redacted thinking 丢失或请求体未被正确清理导致;现有 spec 已要求保留带 `signature` / `data` 的 thinking 块。
17+
* 用户希望基于 callback 全生命周期特性开发一个可兼容多种配置的框架,GLM cooldown 适配逻辑应能单独抽出。
18+
19+
## Assumptions (temporary)
20+
21+
* GLM 429 reset 时间使用 Asia/Shanghai 语义,错误体没有显式时区。
22+
* “恢复时间 + 1 分钟”只针对智谱 GLM Coding Plan 5 小时额度 429,不应影响其它供应商或普通 429。
23+
* 当前 LiteLLM YAML 没有直接支持“解析上游错误 body 后动态设置 deployment cooldown 到指定时间”的配置项。
24+
25+
## Open Questions
26+
27+
* 无。
28+
29+
## Requirements (evolving)
30+
31+
* 减少 GLM 5 小时额度窗口内的重复请求探测。
32+
* 保持 `cc-glmplan-opus` fallback 到 `claude-code-deepseek-v4-pro``cc-glmplan-haiku` fallback 到 `claude-code-deepseek-v4-flash`
33+
* 保持 DeepSeek sanitizer 当前“按 DeepSeek Anthropic deployment 触发”的边界,不改成只看 fallback。
34+
* 文档说明 callback 的生命周期含义:不是 fallback-only,fallback 只是其中一个会命中 DeepSeek 清理条件的场景。
35+
* 新增 callback adapter 框架:LiteLLM 仍只挂载一个 `CustomLogger` 入口,该入口把各生命周期 hook 分发给多个 adapter。
36+
* DeepSeek thinking sanitizer 作为一个 adapter 接入框架,保持现有行为不回退。
37+
* GLM cooldown 作为独立 adapter 接入框架,负责识别 GLM 429 reset 时间并提供恢复时间策略。
38+
* adapter 必须可以按配置启用/禁用,并能表达适用范围,例如 model group、deployment、provider、api_base 或错误码。
39+
* 如果修改配置,`litellm.local.yaml``newapi.yaml` 需要保持预期一致。
40+
41+
## Acceptance Criteria (evolving)
42+
43+
* [x] GLM Claude Code 入口不再使用 1 小时冷却导致 5 小时窗口内反复探测。
44+
* [x] 429 示例中的 reset 时间策略被记录或实现为“reset + 60 秒”。
45+
* [x] LiteLLM 配置只需挂载统一 callback hub,具体行为由 adapter 注册/配置决定。
46+
* [x] DeepSeek thinking sanitizer adapter 与 GLM cooldown adapter 可以单独测试。
47+
* [x] DeepSeek fallback 的 thinking sanitizer 行为不回退:当前 top-level thinking/effort 保留,历史不兼容 thinking 清理,signed/redacted opaque thinking 保留。
48+
* [x] 文档解释 callback 不是只在 fallback 时调用,并说明当前代码的过滤条件。
49+
* [x] YAML 可被解析,相关 sanitizer 回归测试通过。
50+
51+
## Definition of Done
52+
53+
* Tests added/updated where behavior changes.
54+
* `pnpm qa` 通过,或记录无法运行的原因。
55+
* 若改动涉及 pwsh 相关内容,按项目规则额外执行对应 pwsh 测试。
56+
* Docs/spec updated if behavior changes.
57+
* Rollout/rollback considered if risky.
58+
59+
## Research References
60+
61+
* [`research/litellm-callback-router.md`](research/litellm-callback-router.md) — LiteLLM callback 生命周期与 Router 固定冷却能力调研。
62+
63+
## Research Notes
64+
65+
### Feasible approaches
66+
67+
**Approach A: 固定 5 小时冷却**
68+
69+
* How it works: 把两个 GLM Claude Code 入口的 `cooldown_time` 从 3600 改成 18000,并同步 `litellm.md` 与 Trellis spec。
70+
* Pros: 改动小,能直接覆盖 5 小时窗口,减少中途多余请求。
71+
* Cons: 不能利用错误体里的精确 reset 时间;如果 reset 时间与固定窗口不一致,仍可能早探测或晚恢复。
72+
73+
**Approach B: 动态解析 GLM reset 时间**
74+
75+
* How it works: 捕获 GLM 429 错误体,解析 reset 时间,按 reset + 60 秒让后续请求避让 GLM,期间直接使用 DeepSeek fallback。
76+
* Pros: 最贴合真实上游返回,恢复更精准。
77+
* Cons: LiteLLM 文档未暴露直接 YAML 配置,可能需要研究/使用内部 Router cooldown API 或本地缓存拦截,复杂度更高。
78+
79+
**Approach C: Callback hub + 独立 adapters(推荐)**
80+
81+
* How it works: 新增统一 callback hub 实现 LiteLLM `CustomLogger`,把生命周期 hook 分发给多个 adapter;现有 DeepSeek sanitizer 迁移为 request-mutation adapter,GLM cooldown 新增为 error/cooldown adapter。
82+
* Pros: 结构清晰,后续可继续加入 provider-specific 兼容逻辑;GLM cooldown 不污染 DeepSeek sanitizer;每个 adapter 可以独立测试和配置。
83+
* Cons: 初次改动比单纯改 YAML 大,需要设计 adapter 协议、配置读取、错误隔离与日志规范。
84+
85+
### Callback framework shape
86+
87+
**Recommended shape: hub + adapter registry**
88+
89+
* LiteLLM YAML 中挂载一个统一入口,例如 `callbacks.gateway_callback.proxy_handler_instance`
90+
* `GatewayCallbackHub(CustomLogger)` 实现 LiteLLM 常用 hook,并按顺序调用启用的 adapter。
91+
* adapter 可以只实现自己关心的 hook;未实现的 hook 自动跳过。
92+
* 单个 adapter 异常不能拖垮其它 adapter;框架记录安全日志,并按 adapter 标记决定是否 fail-open。
93+
* 默认 adapter 列表由本地 Python 注册,配置负责启用/禁用和传入范围参数,避免在 YAML 中写 Python import 细节。
94+
95+
### Adapter lifecycle model
96+
97+
* `GatewayCallbackHub` 实现 LiteLLM 的生命周期 hook,并把同一个 hook 分发给所有启用且实现了该 hook 的 adapter。
98+
* 所有 adapter 共享一个轻量 `GatewayCallbackAdapter` 协议/抽象基类,定义 `name``enabled`、异常策略与可选生命周期 hook。
99+
* `SanitizerAdapter` 属于请求改写类 adapter,主要运行在 `async_pre_call_deployment_hook`,必要时在 `log_pre_api_call` 做诊断/兜底。
100+
* `CooldownAdapter` 属于限流状态类 adapter,通常至少需要两个阶段:失败后 hook 解析 GLM 429 reset 时间并记录 `cooldown_until`;请求前 hook 根据 `cooldown_until` 决定是否避让 GLM 或提示 Router 不要选中该部署。
101+
* `SanitizerAdapter``CooldownAdapter` 应作为分类抽象或 mixin 存在,沉淀该类别必须实现/推荐实现的 hook、配置模型与测试契约;具体实现例如 `DeepSeekThinkingSanitizerAdapter``GlmCooldownAdapter`
102+
* adapter 是可组合的:同一个生命周期阶段可以串行调用多个 adapter,不同职责的 adapter 不互相替代。
103+
* adapter 是可替换的:例如未来可以把 `GlmCooldownAdapter` 替换成更通用的 `ProviderCooldownAdapter`,也可以把 DeepSeek sanitizer 替换成其它实现;替换范围限于同一职责/协议,不是用 cooldown 取代 sanitizer。
104+
105+
## Expansion Sweep
106+
107+
### Future evolution
108+
109+
* 后续可把 GLM 429 reset 时间缓存成 per-model-group 的恢复时间,让多个进程或容器共享状态。
110+
* 如果其它供应商也返回可解析 reset 时间,可抽象为 provider-specific cooldown parser。
111+
* callback adapter 框架可继续承接供应商参数修正、响应 header 注入、错误转换与观测日志。
112+
113+
### Related scenarios
114+
115+
* `cc-glmplan-opus``cc-glmplan-haiku` 应保持一致,否则 subagent/haiku 流量仍会撞额度。
116+
* `litellm.local.yaml``newapi.yaml` 的模型配置要同步,避免本地与模板行为漂移。
117+
118+
### Failure & edge cases
119+
120+
* 429 body 可能不是 JSON,或中文 reset 时间格式变化;解析失败时应回退到固定 5 小时冷却。
121+
* reset 时间可能已经过去;恢复时间应至少有一个很短的保护窗口,避免立即重试风暴。
122+
* callback 诊断日志不能输出 prompt、API key、完整 headers 或完整 request body。
123+
* 单个 adapter 出错时应默认 fail-open,除非该 adapter 明确声明该错误必须阻断请求。
124+
125+
## Technical Approach
126+
127+
推荐采用 `GatewayCallbackHub + adapters`
128+
129+
* `DeepSeekThinkingSanitizerAdapter` 复用现有 sanitizer core/logging,接入 `async_pre_call_deployment_hook``log_pre_api_call`
130+
* `GlmCooldownAdapter` 实现 GLM 429 reset 时间解析、恢复时间计算与安全日志;运行时通过 failure hook 记录 cooldown,并在请求进入 Router 前把冷却中的 `cc-glmplan-*` 改写到对应 DeepSeek fallback。
131+
* 配置层已迁移到统一 callback hub;旧单用途入口删除,避免 callback 顶层继续混杂实现细节。
132+
133+
## Decision (ADR-lite)
134+
135+
**Context**: LiteLLM callback 是全生命周期扩展点,单独挂载 `deepseek_thinking_sanitizer` 会把不同供应商兼容逻辑继续堆在单用途 callback 里;GLM cooldown 与 DeepSeek thinking sanitizer 的职责、触发阶段和状态需求不同。
136+
137+
**Decision**: 采用代码注册 adapter、配置只控制启用状态和策略参数的 `GatewayCallbackHub`。LiteLLM YAML 挂载统一 callback hub;DeepSeek sanitizer 与 GLM cooldown 作为独立 adapter 接入。
138+
139+
**Consequences**: 第一版配置稳定且容易测试;后续新增 provider-specific adapter 不需要改变 LiteLLM callback 挂载方式。代价是 adapter 列表不做完全 YAML 动态声明,如需新增 adapter 需要改 Python registry。
140+
141+
## Out of Scope
142+
143+
* 不改变 DeepSeek Claude Code 兜底路由的 thinking/effort 参数保留策略。
144+
* 不处理非 GLM 供应商的动态冷却。
145+
* 不在第一版引入跨容器共享状态,除非实现动态 cooldown 必须依赖数据库或外部缓存。
146+
147+
## Technical Notes
148+
149+
* 相关文件:
150+
* `ai/gateway/litellm/litellm.local.yaml`
151+
* `ai/gateway/litellm/newapi.yaml`
152+
* `ai/gateway/litellm/callbacks/gateway_callback.py`
153+
* `ai/gateway/litellm/callbacks/framework/hub.py`
154+
* `ai/gateway/litellm/callbacks/framework/adapters.py`
155+
* `ai/gateway/litellm/callbacks/adapters/deepseek/thinking_sanitizer.py`
156+
* `ai/gateway/litellm/callbacks/adapters/deepseek/thinking_sanitizer_core.py`
157+
* `ai/gateway/litellm/callbacks/adapters/glm/cooldown.py`
158+
* `ai/gateway/litellm/callbacks/tests/`
159+
* `ai/gateway/litellm/litellm.md`
160+
* `.trellis/spec/infra/litellm-gateway.md`
161+
* Context7 使用 `/websites/litellm_ai` 查询 LiteLLM callback 与 Router 文档。
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# LiteLLM callback 与 Router 冷却调研
2+
3+
## 结论
4+
5+
* `litellm_settings.callbacks` 注册的是 LiteLLM Proxy 的调用生命周期 hook,不是只在 fallback 时触发。
6+
* 本仓库的 `DeepSeekThinkingSanitizer` 在 hook 内部通过 `call_type == CallTypes.anthropic_messages` 与 DeepSeek deployment/model/api_base 判断是否实际清理;因此正常 GLM 请求会进入生命周期,但不会执行清理逻辑。
7+
* `async_pre_call_deployment_hook` 是 Router 选中具体 deployment 后、provider 构造请求体前的请求改写点,适合做 DeepSeek fallback 的 messages 清理。
8+
* `log_pre_api_call` 是 provider 发起 HTTP 请求前的 logging hook,可作为诊断与兜底,但不应作为唯一改写点。
9+
* LiteLLM 文档暴露的 `cooldown_time` 是固定冷却时长;Context7 文档未显示可直接按 429 响应体里的重置时间动态设置 deployment cooldown 的 YAML 配置。
10+
* 文档显示 Router 支持 `retry_policy``allowed_fails_policy``cooldown_time``retry_after`,但这些更像固定策略或重试等待,不等同于“解析上游错误 body 后冷却到指定时间”。
11+
12+
## 仓库现状
13+
14+
* `ai/gateway/litellm/litellm.local.yaml``ai/gateway/litellm/newapi.yaml``cc-glmplan-opus` / `cc-glmplan-haiku` 当前都配置 `cooldown_time: 3600`
15+
* 文档 `ai/gateway/litellm/litellm.md` 当前也说明 GLM 两个 Claude Code 入口失败后冷却 1 小时。
16+
* `.trellis/spec/infra/litellm-gateway.md` 已要求 sanitizer 的触发边界以 Router 选中的目标部署是否为 DeepSeek Anthropic 兼容端点为准,而不是只依赖 `fallback_depth`
17+
* sanitizer 当前实现已经符合“不是只在 fallback 时清理,而是只在 DeepSeek Anthropic 请求上清理”的设计:直接调用 `claude-code-deepseek-*` 时同样受保护。
18+
19+
## 可行方案
20+
21+
### 方案 A:固定冷却改成 5 小时
22+
23+
* 做法:把两个 GLM Claude Code 入口的 `cooldown_time` 从 3600 改为 18000,并同步文档。
24+
* 优点:实现最小,贴合 GLM 5 小时额度窗口,立刻减少中途多余探测请求。
25+
* 缺点:如果 429 响应明确给出更早或更晚的恢复时间,固定 5 小时仍不够精确;也无法自然表达“恢复时间 + 1 分钟”。
26+
27+
### 方案 B:新增 GLM 429 重置时间感知逻辑
28+
29+
* 做法:在 LiteLLM 可用 hook 或本地兼容层中识别 GLM 429 错误体,解析 `您的限额将在 YYYY-MM-DD HH:mm:ss 重置`,按该时间加 60 秒设置后续请求避让。
30+
* 优点:最贴近用户描述,能避免 GLM 窗口内重复探测,也能在额度提前恢复时及时切回。
31+
* 缺点:需要确认 LiteLLM 当前运行版本是否有可写的 deployment cooldown API;如果没有,可能要通过外部缓存/请求前拦截实现,复杂度和回归风险更高。
32+
33+
### 方案 C:先固定 5 小时,同时预留动态解析测试
34+
35+
* 做法:本次先把 cooldown 改为 18000,文档明确这是临时保守策略;同时新增纯函数和测试,用于解析 GLM 429 reset 时间,后续接入 Router 冷却 API。
36+
* 优点:兼顾当前止血和后续演进,测试先锁定中文错误格式。
37+
* 缺点:会留下暂未接入运行链路的解析代码,除非下一步马上补齐动态冷却。
38+
39+
### 方案 D:统一 callback hub + 独立 adapter
40+
41+
* 做法:LiteLLM YAML 只挂载一个统一 `CustomLogger` 实例,由 hub 分发生命周期 hook;DeepSeek thinking sanitizer 与 GLM cooldown 都作为 adapter 接入。
42+
* 优点:符合 callback 全生命周期扩展点的实际模型;不同供应商兼容逻辑可以独立启用、配置、测试。
43+
* 缺点:需要先定义 adapter 协议、配置加载方式、异常隔离策略和安全日志边界。
44+
45+
## callback hub 设计建议
46+
47+
* 保持 LiteLLM 配置简单:`litellm_settings.callbacks` 中只挂载一个 hub。
48+
* adapter 由 Python registry 管理,避免在 YAML 中直接散落多个 Python import 路径。
49+
* 配置只负责声明 adapter 是否启用、适用范围和少量策略参数,例如:
50+
* `deepseek_thinking_sanitizer.enabled`
51+
* `glm_cooldown.enabled`
52+
* `glm_cooldown.model_groups`
53+
* `glm_cooldown.reset_buffer_seconds`
54+
* `glm_cooldown.fallback_cooldown_seconds`
55+
* 每个 adapter 只实现自己需要的 hook;hub 对缺失 hook 自动跳过。
56+
* adapter 默认 fail-open:记录安全日志,但不因为某个辅助逻辑失败拖垮请求链路。
57+
* 对请求改写类 adapter,应明确 hook 阶段和可修改对象;例如 DeepSeek sanitizer 仍应在 `async_pre_call_deployment_hook` 做主改写。
58+
59+
## callback 问题回答
60+
61+
历史配置 `callbacks: - callbacks.deepseek_thinking_sanitizer.proxy_handler_instance` 表示 LiteLLM 启动时加载这个 `CustomLogger` 实例,并在请求生命周期对应阶段调用它实现的 hook。它不是 fallback 专用配置。当前实现已迁移到统一 `callbacks.gateway_callback.proxy_handler_instance`,再由 DeepSeek sanitizer adapter 通过以下条件缩小实际影响范围:
62+
63+
* `async_pre_call_deployment_hook` 只处理 `CallTypes.anthropic_messages`
64+
* `is_deepseek_anthropic_request(kwargs)` 必须识别到 DeepSeek 模型、DeepSeek deployment,或 DeepSeek Anthropic api_base。
65+
* `fallback_depth` 只出现在日志诊断中,用于确认是否来自 Router fallback,不作为触发条件。
66+
67+
因此:正常 GLM 主路由不会被 sanitizer 清理;GLM 429 后 fallback 到 DeepSeek 会清理;用户直接调用 `claude-code-deepseek-*` 也会清理,因为同样是 DeepSeek Anthropic 请求。
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
{
2+
"id": "glm-429-cooldown-thinking-fallback",
3+
"name": "glm-429-cooldown-thinking-fallback",
4+
"title": "brainstorm: 优化 GLM 429 冷却与 fallback thinking 处理",
5+
"description": "",
6+
"status": "completed",
7+
"dev_type": null,
8+
"scope": null,
9+
"package": null,
10+
"priority": "P2",
11+
"creator": "mudssky",
12+
"assignee": "mudssky",
13+
"createdAt": "2026-05-12",
14+
"completedAt": "2026-05-12",
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)