Files
HomeAgent/internal
JianFeeeee ec035951cc fix(agent): 批内附加消息延迟到整批落完,修复上游 tool_calls 配对 400
生产实测(llmsproxy tier 1,每 5 秒持续复现,非偶发):

    api error 400: An assistant message with 'tool_calls' must be followed by
    tool messages responding to each 'tool_call_id'.
    (insufficient tool messages following tool_calls message)

根因在 stepToolAfter:它在**批内循环**里对每个工具各调一次,却当场追加
两类附加消息(多模态注入 mediaMsg=user、记忆召回=system)。于是 N 个工具
的批变成

    assistant tool_calls=[c1,c2]
    tool c1
    user            ← 插在中间
    tool c2         ← 被挤开,与 assistant 不再相邻

上游按「紧跟 assistant 的连续 tool 消息」计数,只数到 1 条却声明了 2 条。

修复:TaskFrame 增加 batchAttachments / batchRecall 积压附加消息,
新增 flushBatchAttachments() 只在**整批 tool 消息全部落完之后**调用。
两个调用点:并行路径 stepToolBatch 收尾循环之后;串行路径 stepToolBegin
发现批已耗尽时。

这也落实了「多模态注入点应在当前轮工具调用之后」这一设计约束。

判据(internal/agent/core/toolcall_adjacency_test.go):
- assertToolCallsAdjacent —— assistant(tool_calls=[c1..cn]) 之后必须**紧接**
  n 条 role=tool 且 id 一一对应。既有 TestBatchToolCallIDsAllPaired 只断言
  **数量**配对(其注释明确写着「不断言消息的物理排列」),而数量对、排列错
  上游照样 400。
- assertMediaNotSilentlyDropped —— 注入的媒体不得静默丢失。
- 三条用例覆盖串行路径 / 并行路径 / 最小回归。

变异验证(M1 当场追加 media / M3 删并行 flush / M4 删串行 flush)全部报红。
其中两轮暴露「判据太松」并已补强:
  1. 测试工具未声明 ParallelSafe ⇒ 整批降级走串行,并行判据形同虚设。
     补并行专用判据 + 前置断言(不满足即 t.Fatalf,不静默跳过)。
  2. 仅断言相邻性时,删掉 flush 反而"更相邻"(附加消息压根没进来),
     判据全绿而媒体被**静默丢弃** ⇒ 补 assertMediaNotSilentlyDropped。
     教训:只断言一个性质会漏掉另一个,相邻性与不丢失必须同时断言。

验证:core 包三次全绿 + -race 干净;全仓 48 包 0 FAIL;
端到端真实多工具并行对话正常完成,日志 insufficient tool messages = 0 次。
2026-10-13 16:18:13 +08:00
..