|
|
3d12e82f65
|
refactor(toolcall): StageContext 拆 per-tool,为并发执行消除共享槽(阶段 2c)
问题:f.StageCtx 是**单槽**,批内每个工具都覆写它(ToolCalls=[单元素]、
ToolResults 覆写、Results[0] 回读)。串行下看不出问题,但并发下
N 个 goroutine 同写一个 ctx = 数据竞争,且 after_toolcall 插件可能读到
**别的工具**的结果。
改动(task.go):
· TaskFrame 增 toolCtxs []sdk.StageContext(每工具一份)
· buildToolContexts 在 stepLLM 设 PendingTools 时建池;
Extra **逐份浅拷贝**——共享同一 map 即竞争(stage handler 会写它)
· toolCtxFor(i) 取第 i 份,越界/未建时回落 f.StageCtx(测试替身安全)
· stepToolBegin(before_toolcall + 参数回填)、stepToolExec(写结果)、
stepToolAfter(读结果)三处全部切到 per-tool ctx
判据(toolbatch_test.go 追加两条):
· 每个工具的 before_toolcall ctx 只带自己的 ToolCalls[0].Name,
且 Extra[output_channel] 逐份带过去(stage.go:18 依赖它)
· after_toolcall 读到的 Result 必须属于当前工具,不能是批内另一个的
★ 诚实记录:这两条判据在**串行**下**测不出与单槽的差别**——串行时
每工具跑完才进下一个,不存在交错。变体验证(toolCtxFor 退回单槽)后
判据仍全绿。故 2c 记为「实现已就位、判据未闭合」,真正判据必须与 2d
(并发执行)一起写,并以 -race 确认无竞争。已在执行计划中标注。
过程中两次自伤:
· 我的 harness 没设 Extra[output_channel](那是 prepareInputTask 才写的,
task.go:380),判据一度报「产品缺陷」——核实后是我造的场景,已对齐生产;
· 阶段 1 的 TestStageCtxSuccessIsHonestEndToEnd 读 f.StageCtx.ToolResults,
拆分后失效——判据跟随新结构改为按批索引取 toolCtxs[i],
断言的仍是内核产出的 Success 值本身。
顺带记录(非本次引入):TestResidualKeep/Drop 偶发失败,根因是
offload_test.go 的 SpawnResident 起了子调度器 goroutine,而测试
enqueue 后无同步就读同一队列。干净基线 3/3 全绿属运气。已在计划中
记为待修,避免后续误判为并行化引入的回归。
|
2026-09-27 11:17:06 +08:00 |
|
|
|
28b42bcf0f
|
refactor(toolcall): 批内消息改为「一个 assistant 带全部 tool_calls」(阶段 2a)
问题:现状每个工具各自 append 一对(assistant[tool_calls=[tc]] + tool),
既不表达「这是一批」,也无法支撑并行:
· 产生 N 条 assistant 消息,同一段 assistant 文本语义上只该出现一次
· 并行下完成顺序不确定,若等结果回来再落消息,assistant 就必须等所有
结果齐了才能写——而 OpenAI 协议要求 assistant(tool_calls) 在结果**之前**
改动(task.go):
· TaskFrame 增 assistantMsgIdx
· 新增 ensureBatchAssistant:惰性写入,全批只写**一条** assistant,
携带 f.PendingTools 全部 tool_calls;后续工具只补 tool 消息
· stepToolBegin 的 denied / unhealthy 分支与 stepToolAfter 统一改用它
· stepLLM 设 PendingTools 时清零 assistantMsgIdx
· msgContent 的 ContentOnce 归位移入 ensureBatchAssistant(仍是只挂第一条)
⚠️ 依赖:before_toolcall 阶段**不得**改写工具参数——已核实全仓无此用法
(grep ToolCalls[0].Arguments 赋值无结果)。若将来某插件要改写 args,
需改为「回填后重写该条 assistant」。该前提已写入代码注释。
判据(toolbatch_test.go 追加 TestBatchLayoutSingleAssistantCarriesAllToolCalls):
· 带 tool_calls 的 assistant **恰好一条**且携带 2 个 tool_calls
· 其后紧跟 2 条 tool 消息且按声明顺序(c1、c2)
变异验证:让 ensureBatchAssistant 退化为「每工具一条」⇒ 判据 FAIL
「批内 assistant 应带 2 个 tool_calls,实际 1」。
stage 0.5 补的三条判据(配对完整性 / ContentOnce / denied 后继续)
在本改动后**仍然全绿**——它们正是为这种改动准备的保护网。
回归:internal/agent/... internal/sdk/... internal/plugins/... 全绿(18 包)。
|
2026-09-27 11:09:02 +08:00 |
|
|
|
4ba72977d2
|
test(toolcall): 补批内路径的三条缺失判据(阶段 0.5)
同一批多个 tool_call 的循环(StepToolBegin→Exec→After)此前只被
scheduler_critical_test.go:125 一条用例覆盖「按序执行」,缺的三条正是
阶段 2(并行执行层)要改的地方:
· tool_call_id 配对完整性 —— 阶段 2 改消息落法(一个 assistant 带全部
tool_calls + N 条 tool)时,配对断裂上游会直接报错
· ContentOnce 批内语义 —— 同一段 assistant 文本在批内重复 N 次,撑爆上下文
· denied 后继续批内 —— 改成 abort 会丢掉本可执行的后续调用
判据 toolbatch_test.go(4 条),全部确定性断言:阶段 0 已消除 map
迭代随机性,同批工具的落序与配对可稳定断言。
变异验证:令 stepToolBegin 跳过批内最后一个工具后,4 条判据同时 FAIL
(既有那条也 FAIL),报错直指 ToolsUsed=[tool_alpha]、
tool_call_id "c2" 被声明 0 次。
更正一处此前的不准确表述:我曾说「无任何测试直接驱动批内路径」——
不准确。scheduler_critical_test.go:125 已驱动「同批两工具按序执行」;
漏查是因为只 grep 了 PendingTools/ToolIdx 字段名,没查断言内容。
真正缺的是上表三条。
过程中三次自伤(均由「判据先写」暴露):臆造不存在的 helper;
stageHost 置 nil 后又使用;给 newTaskFrame 传 nil 导致 stepPrepare 于
task.go:519 nil 解引用 panic(改用仓内既有 a.stageCtxFromInput)。
顺带记录:生产两处 newTaskFrame 调用都传真实 ctx,但 stepPrepare 对
f.StageCtx 无 nil 兜底——本次不修(无生产触发路径),记为潜在缺口。
回归:internal/agent/... 与 internal/plugins/... 全绿。
|
2026-09-27 08:59:30 +08:00 |
|