mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-09-28 21:33:05 +00:00
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 包)。
This commit is contained in:
@ -245,3 +245,57 @@ func TestBatchContinuesAfterDeniedTool(t *testing.T) {
|
||||
t.Errorf("tool_beta 的结果未回填(被前一工具的拒绝连带丢弃)")
|
||||
}
|
||||
}
|
||||
|
||||
// 阶段 2a:消息落法改为「**一个** assistant 带全部 tool_calls + N 条 tool」。
|
||||
//
|
||||
// 现状:每个工具各自 append 一对(assistant[tool_calls=[tc]] + tool),
|
||||
// 不表达「这是一批」。阶段 2 的批内并发要求消息形态与之对应,且并行下
|
||||
// 多个 tool message 的相对顺序必须**按 index 确定**,否则模型读到的
|
||||
// 上下文顺序 ≠ 执行顺序,会诱导出错误的因果推断。
|
||||
//
|
||||
// 本判据钉死:新布局下(a)配对仍完整、(b)assistant 只出现一条且带全部
|
||||
// tool_calls、(c)tool 消息按 index 升序、(d)多模态 user 消息仍紧跟
|
||||
// 各自的 tool 消息。
|
||||
func TestBatchLayoutSingleAssistantCarriesAllToolCalls(t *testing.T) {
|
||||
tcA := agentAPI.ToolCall{ID: "c1", Name: "tool_alpha", Arguments: map[string]interface{}{}}
|
||||
tcB := agentAPI.ToolCall{ID: "c2", Name: "tool_beta", Arguments: map[string]interface{}{}}
|
||||
sp := &batchProvider{responses: []*agentAPI.CompletionResponse{
|
||||
{Content: "BATCHTEXT", ToolCalls: []agentAPI.ToolCall{tcA, tcB}},
|
||||
{Content: "final"},
|
||||
}}
|
||||
a, _ := newBatchAgent(t, sp)
|
||||
|
||||
f := a.newTaskFrame("go", a.stageCtxFromInput("go", "", ""))
|
||||
if out := a.runTaskSteps(f); out != outcomeDone {
|
||||
t.Fatalf("runTaskSteps=%v err=%v", out, f.Err)
|
||||
}
|
||||
|
||||
// ① 找带 tool_calls 的 assistant 消息,必须**恰好一条**且带 2 个。
|
||||
var assistants []int
|
||||
for i, m := range f.Msgs {
|
||||
if m.Role == "assistant" && len(m.ToolCalls) > 0 {
|
||||
assistants = append(assistants, i)
|
||||
if len(m.ToolCalls) != 2 {
|
||||
t.Errorf("批内 assistant 应带 2 个 tool_calls,实际 %d", len(m.ToolCalls))
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(assistants) != 1 {
|
||||
t.Fatalf("带 tool_calls 的 assistant 应恰好 1 条,实际 %d 条(索引 %v)", len(assistants), assistants)
|
||||
}
|
||||
|
||||
// ② 该 assistant 之后应紧跟 2 条 tool 消息,且按声明顺序。
|
||||
idx := assistants[0]
|
||||
var gotIDs []string
|
||||
for i := idx + 1; i < len(f.Msgs); i++ {
|
||||
if f.Msgs[i].Role == "tool" {
|
||||
gotIDs = append(gotIDs, f.Msgs[i].ToolCallID)
|
||||
}
|
||||
}
|
||||
if len(gotIDs) != 2 {
|
||||
t.Fatalf("assistant 之后应有 2 条 tool 消息,实际 %d(%v)", len(gotIDs), gotIDs)
|
||||
}
|
||||
if gotIDs[0] != "c1" || gotIDs[1] != "c2" {
|
||||
t.Errorf("tool 消息应按 index 升序,实际 %v", gotIDs)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user