|
|
8ca28eb071
|
feat(toolcall): 工具结果只统计不裁剪(方案 B),并治掉 seq 侧的静默截断
问题(核实过):工具结果进 f.Msgs 时**没有任何长度上限**(task.go 直接
`Content: result`),内核也**不预检**是否超长 —— 超限由上游 API 报错。
时间线那侧有预算(ContextTokens = 0.8×窗口,进消息前就裁过),但那只管
a.context 的历史事件,**不管单条工具结果** ⇒ 一条巨大结果可能直接冲破
预算而内核不会提前发现。
为什么**不裁剪**(与方案 A 的取舍):
· 截断会让模型拿到**残缺**信息,而截断位置由内核武断决定;
· 模型无法得知"这里被截断了",会基于残缺数据下结论 —— 与本仓反复
吃亏的「静默降级」同族(`20s` 少引号 → 静默降级 → cmd_run 失败率 34%);
· 处置权应交给调度器/上层(告警、拒绝、或让模型自己换更窄的查询),
而不是内核单方面替模型决定。
改动:
· core/toolresult_budget.go: checkToolResultSize 只**计数+报告**;
阈值默认 = ContextTokens/8(一条吃掉全部预算会把其它上下文全挤掉);
报告经 toolResultReporter(可替换),默认 logReporter —— **不给模型发
消息**:那是在已花掉的 token 之上再加一条 system,且对当前这轮决策无帮助。
· 接入点在 stepToolAfter 的 toolMsg 落定**之后**(那里才是模型最终看到的
内容;stepToolExec 拿到的尚未经 after_toolcall 改写)。
· TaskFrame 记 oversizeTools / oversizeToolNames,供调度器与状态面查询
"是否有工具在稳定产出超大结果"。
★ 顺带治掉 seq 侧一处**我自己留下的静默截断**:
handlers.go 里我当初随手写了 truncate(…, 160),把变量槽静默截到 160 字
且**无任何标注** —— 正是我批评过的静默降级。
改为 renderSlot:≤160 给全;超过则显式标注「已截断:共 N 字,此处显示前
160 字」并给出改法。**槽里存的始终是完整值**,截断只影响回填文本长度。
端到端判据 TestSeqRunDoesNotSilentlyTruncateSlot 抓到了这个缺陷
("变量槽被截到 160/5000 字却没有任何标注")。
判据(toolresult_budget_test.go,4 条):
· 400KB 结果触发超限报告(含工具名与 token 数)
· ★ **默认不裁剪**:200KB 结果原样进 tool 消息(方案 B 的核心不变式)
· 小结果不误报(噪音会淹没有效信号)
· 报告文案可执行:带工具名、token 数、改法建议
变异验证:去掉统计调用 ⇒ 两条判据 FAIL("统计没生效" + "被裁剪了")。
另:检查项报 stepToolBatch 的 goroutine 竞态,-race 实测**误报**——
循环变量显式传参(非闭包捕获)、且按索引写各自槽位(非共享 map),
`-race` 下 20 轮并发判据全绿。
|
2026-09-27 14:05:38 +08:00 |
|
|
|
3d31037f63
|
test(seq): 端到端接线判据,抓出「传参方式完全不可用」的真 bug
P1–P4 的判据都在**包内**(假 runner / 直接调函数),覆盖的是**语义**;
本轮补的是**接线**层——参数名对不对、返回值模型读不读得懂、跨层调用断不断。
接线层的 bug 语义判据抓不到:例如工具注册了但参数名拼错,单元判据全绿
而模型永远传不进来。
★ 抓到一个真 bug:**seq_create 走 groups 传参时完全不可用**。
根因:marshalGroups 只把 groups 包进 JSON 文档、不带 name,而 Parse 要求
name 非空 ⇒ 报「序列缺少 name」。而 seqCreate 里那句
`if seq.Name == "" { seq.Name = name }` 回落分支是**死代码**(Parse 早就失败了)。
后果:**只有 file 方式能用,传参方式一律失败**。
已修(name 一并包装)。包内判据抓不到这个——它们直接构造 *Sequence,
不经过这条路径;只有真正 dispatch 一遍才暴露。
判据(e2e_test.go,7 条):真实 dispatch 串通
seq_create → seq_list(须展示签名,模型据此按名调用)→ seq_run
(执行序按 tools 声明序、槽回填、结果里**不得**出现 Go 的 `map[` 语法)
· seq_call 按名调用 group 并返回其出参
· seq_when_call 条件为假 ⇒ 跳过且**零工具被执行**
· seq_when_call 条件畸形 ⇒ **报错**且零执行(不得静默跳过)
· seq_create 走**文件**(长序列的主力用法)
· groups 与 file 同时传 ⇒ 报错「二选一」
· seq_delete 不存在 ⇒ 报错(模型会以为删掉了)
过程中又一次臆造 helper(`writeFile`),改用 os.WriteFile;
并把三处 `_, _ = p.dispatch(...)` 补上 err 检查(正是
go-ignored-call-result 报的那类)。
回归:seq -race 全绿;go build ./... 通过;go test ./internal/... 全绿。
|
2026-09-27 13:18:19 +08:00 |
|