zcbot/core/storage
caoqianming 1685004f96 feat(loop): 空响应防御——provider 吐空自动重试 + warn 自停不静默 done + 面板留痕(bump 0.58.32)
task 2a1bc25d 复盘:unifyllm 网关对某档 Claude 偶发把 tool_use 漏成正文/直接吐空,回来的轮
tool_calls=[] 且正文空,loop 当"模型答完"静默 done(run_status=idle、无报错、无终态失败),
表现为"自己中断",只能人肉挖 DB 才发现。复测该 bug 现已消失/瞬态,但"空 tool_calls 直接
done"这个失败模式本身太隐蔽,任何 provider 未来吐一次空都会静默卡死,故加防御(对称既有畸形
salvage 链)。

- _is_empty_response(tc 空且 content 去空白为空;纯 tool_call 轮不误判)挂进 _stream_llm 的
  attempt 循环:空-空丢弃本轮走非流式重试(多数瞬态重发一次即恢复,用户无感)
- run() 收尾点分空-空分支:重试耗尽仍空不发静默 done,改发可见 warn「模型返回空响应,已停止,
  回复继续可重试」+ done(复用 stall 熔断自停话术,run_status 落 idle 可续),不引入终态 error
- _log_empty_response stdout + usage_events(kind=empty_response,cost 0)双写留痕;core/toolfail
  加第四段扫描聚成 tool=(empty)/kind=empty cluster(sample=model_profile 看哪个网关档在吐空),
  admin 面板 + 巡检邮件零改动自动带上(单次瞬态 <阈值不触发,跨 task 系统性吐空才冒头)
- 刻意不做内容嗅探式"narrated tool_call 特征→重试":marker 每次变且与正常正文高度重叠,假阳性
  (合法回答判坏反复重试)比罕见静默停更糟,那类只靠面板留痕兜、不改热路径行为
- tests: test_loop_empty_response.py 新增 7 件 + test_toolfail_malformed.py +2;顺手补回
  test_loop_malformed_retry.py 因 salvage(0.58.24)落地后失修的 loop.executor 桩。全 247 测试绿

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 15:29:49 +08:00
..
__init__.py feat(loop): 空响应防御——provider 吐空自动重试 + warn 自停不静默 done + 面板留痕(bump 0.58.32) 2026-07-15 15:29:49 +08:00
disk_quota.py feat(web): 文件面板底部展示用户已用存储 + 配额 2026-06-04 15:00:36 +08:00
engine.py auth(dev SPA): 邀请码登录(invites 表 0005) + SENTINEL user 彻底撤 2026-05-19 13:14:31 +08:00
models.py fix(web): 用户停止后补持久「已停止」提示,cancel 落一等终态(bump 0.58.26) 2026-07-15 09:38:54 +08:00
usage.py feat(loop): 空响应防御——provider 吐空自动重试 + warn 自停不静默 done + 面板留痕(bump 0.58.32) 2026-07-15 15:29:49 +08:00
utils.py feat: 定时任务对话归属 + push 统一记录到渠道对话(bump 0.28.0) 2026-06-26 10:51:06 +08:00