caoqianming
|
cac0bfcfe4
|
refactor(core): loop.py 传输健壮性层析出 llm_transport.py + _execute_tool_call 拆分
架构审查 P1#4:loop.py 把「provider wire 层瞬态故障自愈」与「agent 控制流」
塞在同一文件;_execute_tool_call 一个函数线性堆 10 个关注点(148 行)。
- 新增 core/llm_transport.py(438 行):畸形/必填 key 被吞/空响应检测、三类
故障留痕、usage/delta 提取、robust_stream 重试策略(首败降级非流式 +
salvage 可救当轮续)。取流两路径与 salvage 以 callable 注入——不 import
loop,单测在 AgentLoop 实例上打桩 _collect_stream_once/_nonstream_once
的现有缝隙原样保留
- loop.py 1158→812 行,回归 ReAct 主干:_stream_llm 只留上下文压缩准备
(context 关注点),wire 健壮性委托 robust_stream;_collect_stream_once/
_nonstream_once/_try_salvage_response 留在 loop(持 llm/emit 态 + 测试缝)
- _execute_tool_call 148 行拆为编排 + 4 个正交方法:_check_repeat_block
(两道拦截)/_maybe_skill_model_switch(热切)/_repeat_feedback(登记+
软提示)/_maybe_pptx_guard(产物机检)
- 4 个测试文件 import 指向同步更新(is_empty_response 等改公有名)
292 测试全过(loop 重试/空响应/repeat-guard/salvage 套件覆盖改动路径)。
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
2026-07-23 12:44:22 +08:00 |
caoqianming
|
f535dbaa9a
|
fix(llm,loop): glm.pro52 空响应治本——禁 thinking 免推理烧穿输出上限 + loop 区分截断(bump 0.58.49)
失败面板 empty_response 簇 2026-07 主角 glm.pro52。探针 + DB + 代码三重定层根因链:
1. caps.max_output 是全仓死字段(只在 capabilities.py 定义,_build_kwargs 从不作为
max_tokens 发出)→ 网关放任 glm-5.2 跑到自带 65536 输出硬上限;
2. glm-5.2 thinking 网关侧默认开(线上探针实测 reasoning_content=766>0,尽管 config
thinking_mode:false —— 那开关是 glm.yaml 未做的 TODO,根本没透传);
3. 重任务(100k 上下文)上思考膨胀烧满 65536 被截断(finish_reason=length)、content 空
→ loop 判空响应整轮丢弃 + 同上下文无效重试(task 35744bea:5 次 empty 全
tokens_out=65536,事件4=attempt2)。单任务烧 ~327k 输出 token。
与 opus48/deepseek 的网关 wire bug 不同根 —— 这是我方 max_output 死 + 未约束推理模型。
修(禁 thinking + loop 健壮化):
- core/llm.py _build_kwargs 加 family=="glm" 分支,据 thinking_mode 透传
extra_body={"thinking":{"type":"enabled|disabled"}}(GLM body 级协议,与 OpenAI 的
reasoning_effort 不同族;litellm zai provider 转发 extra_body)。当前 glm 档均 false=disabled。
线上探针 A/B 实测:disabled 后 reasoning_content 766->0、正文/工具照常。
- core/loop.py 加 _finish_reason,空响应路径区分 length(截断,我方预算烧穿,重试无效)与
wire 吐空,warn 措辞据实;record_empty_response units 记 finish_reason(JSON 免 migration)。
- 修既坏的 test_loop_empty_response(mock 缺 executor)+ 补截断措辞用例,61 测试全绿。
- 探针 diag_glm_empty_probe.py 加 monkeypatch 绕沙箱池 + PROBE_THINKING_OFF A/B 开关。
行为变化(知情):glm.pro52 用户现在拿禁思考的 glm-5.2(config 本就 thinking_mode:false)——
不再卡壳/空转,代价是硬任务少了推理链。遗留:max_output 对所有模型仍未生效(本次没盲发
max_tokens,怕截断大 write 的 args),需逐档评估安全值后另做。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
2026-07-21 08:16:54 +08:00 |
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 |