mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-10-03 15:53:56 +00:00
fix(toolcall): 工具「不存在」类型化 + 修父 io 兜底吞错误 + 修并行 tool_call 落序随机
主线:工具调用并行化改造(阶段 0 与 0.2)。
① flush 顺序随机(process.go)
flushToolCall 由 `for idx := range accs` 驱动,Go map 迭代顺序随机化
⇒ 同一批并行 tool_call 进入 resp.ToolCalls 的顺序每次运行都可能不同。
对 output_send__ 这类用户可见通道,分段消息到达顺序不可复现。
改为收集 index 后 sort.Ints 再 flush(两个调用点统一走 flushAll)。
判据 stream_flush_order_test.go(8 工具 × 200 轮),已变异验证可检测。
② 工具「不存在」类型化(io/channel.go、core/stages.go、core/toolcall.go)
工具是动态注册的,「不存在」是运行期常态而非异常。原先内核用
strings.Contains(err, "not found in any plugin") 判别——约定而非契约,
插件文案含该子串即被误判。改用哨兵 ErrToolNotFound + errors.Is
(沿用仓内 ErrInputChannelUnknown 的先例)。
⚠️ 顺带修一个静默 bug:IOManager 向父兜底时吞掉父的执行失败,
误报为「工具不存在」。后果是设备离线这类本该 retry 的失败被判为
「工具没了」⇒ 整组被跳过,与「插件真没加载」无法区分。改为只传递
「确实不存在」,其余如实上抛。
「不存在」的文案改为可执行指引(get_plugin_tools / output_list_channels),
而非含糊的「执行失败」——后者会让模型反复重试同一个不存在的名字。
判据:toolcall_error_test.go(类型化 vs 诱饵子串、%w 穿透、执行期文案)、
channel_error_test.go(父失败不吞、真的不存在仍可判别)。
两者均经变异验证。回归:internal/agent/... 与 internal/plugins/... 全绿(14 包)。
设计文档:docs/zh/toolcall-contract-and-sequence-design.md
执行计划:docs/zh/toolcall-parallel-execution-plan.md
This commit is contained in:
@ -7,6 +7,7 @@ import (
|
||||
"fmt"
|
||||
"log"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
agentAPI "gitcode.com/JianFeeeee/HomeAgent/internal/agent/api"
|
||||
@ -287,13 +288,31 @@ func accumulateStream(ctx context.Context, ch <-chan agentAPI.StreamChunk, a *Ag
|
||||
delete(accs, idx)
|
||||
}
|
||||
|
||||
// flushAll 按 index 升序 flush 所有累积中的 tool call。
|
||||
//
|
||||
// ❗必须排序,不能直接 `for idx := range accs`:Go 的 map 迭代顺序是**随机化**的,
|
||||
// 同一批并行 tool_call 因此会以任意顺序进入 resp.ToolCalls。对 output_send__
|
||||
// 这类**用户可见消息**通道,后果是分段消息的到达顺序每次运行都可能不同
|
||||
// (不可复现的外部行为);对 tool_call_id 配对虽无影响(靠 id 而非位置),
|
||||
// 但让「同一输入产生同一执行序列」这条最基本的可复现性失守。
|
||||
//
|
||||
// 排序也让判据可写:stream_flush_order_test 断言输出严格按 index 升序。
|
||||
flushAll := func() {
|
||||
idxs := make([]int, 0, len(accs))
|
||||
for idx := range accs {
|
||||
idxs = append(idxs, idx)
|
||||
}
|
||||
sort.Ints(idxs)
|
||||
for _, idx := range idxs {
|
||||
flushToolCall(idx)
|
||||
}
|
||||
}
|
||||
|
||||
for {
|
||||
select {
|
||||
case ck, ok := <-ch:
|
||||
if !ok {
|
||||
for idx := range accs {
|
||||
flushToolCall(idx)
|
||||
}
|
||||
flushAll()
|
||||
if lastFinish != "" {
|
||||
resp.FinishReason = lastFinish
|
||||
}
|
||||
@ -357,9 +376,7 @@ func accumulateStream(ctx context.Context, ch <-chan agentAPI.StreamChunk, a *Ag
|
||||
}
|
||||
|
||||
case <-ctx.Done():
|
||||
for idx := range accs {
|
||||
flushToolCall(idx)
|
||||
}
|
||||
flushAll()
|
||||
return resp, ctx.Err()
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user