fix(model): Anthropic 并行工具结果合并为同一条 user 消息(修真机 400)
根因(agent-193,文件:行):loop 的 tool_history 按调用逐条产出 RoleTool 消息, 而 anthropic_encode 把每条各自编码成一条独立 user 消息 ⇒ 上游只认紧邻 assistant 的那一条,第 2..N 个 tool_use id 被判「没有 tool_result」⇒ `messages.1: tool_use ids were found without tool_result blocks immediately after`。 单调用回合只有 1 条结果消息,所以长期潜伏;KB1 让 knowledge 真正成功后它才被暴露。 修法:编码器在**线格式**层把相邻的 RoleTool 合并进同一条 user 消息(块序 = 调用序), 遇到任何非工具结果回合即复位(不同 assistant 回合的结果绝不混入同一条)。 未碰 openai_encode(OpenAI 要求每个 tool_call_id 一条独立 tool 消息)。 测试 3 个新文件:编码器线格式(非流式 + 流式)/ loop 端到端(真适配器打假上游)/ 回填侧契约;含配对断言(每个 tool_use 在下一条消息里有同 id 的 tool_result)。 变异:还原旧编码 ⇒ 4 条红,端到端逐字复现真机 400。 是否本轮引入:否(编码与回填两文件在本轮零改动,属基线潜伏缺陷)。
This commit is contained in:
1 parent
fd13b49b24
commit
15181e7063
4 files changed
+652
No files matched your search
@@ -12,12 +12,26 @@ import (
|
||||
// 会被丢弃,因为 Anthropic 拒绝空内容。挂在非用户回合上的
|
||||
// 文档部件在这里就被拒绝(design/24 §2.3-③):文档本期永不
|
||||
// 抵达编码器,漏网即编程错误,绝不静默丢块。
|
||||
//
|
||||
// 连续的 RoleTool 回合在这里被合并成**一条** user 消息。这是
|
||||
// Anthropic 线格式的硬要求,不是风格取舍:assistant 回合里每个
|
||||
// tool_use 的应答块都必须落在**紧接其后的那一条**消息里,而回填侧
|
||||
// (loop/tool_history.go)是按调用逐条产出 RoleTool 消息的,因此
|
||||
// 一批并行调用的多个结果不合并就会分散在多条 user 消息中,上游
|
||||
// 只在下一条里看到其中一个 id,其余 id 被判「没有 tool_result」,
|
||||
// 整个请求 400(真机 P0;单调用回合因此永远看不出问题)。
|
||||
func buildAnthropicMessages(msgs []Message) (string, []anthropicMessage, error) {
|
||||
if err := ValidateDocumentParts(msgs); err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
var sys strings.Builder
|
||||
out := make([]anthropicMessage, 0, len(msgs))
|
||||
// pendingToolResult 是 out 中正在累积的工具结果回合下标,-1 表示
|
||||
// 没有可合并的目标。每追加一条非工具结果回合就复位它,因此只有
|
||||
// **真正相邻**的结果才会被并入同一条消息 —— 两批不同 assistant
|
||||
// 回合的结果绝不会被混在一起。RoleSystem 回合被抽到顶层 system
|
||||
// 字段、不进 messages,不打断相邻性,所以不参与复位。
|
||||
pendingToolResult := -1
|
||||
for _, m := range msgs {
|
||||
if m.Role == RoleSystem {
|
||||
if t := messageText(m); t != "" {
|
||||
@@ -35,7 +49,22 @@ func buildAnthropicMessages(msgs []Message) (string, []anthropicMessage, error)
|
||||
if len(blocks) == 0 {
|
||||
continue
|
||||
}
|
||||
if m.Role != RoleTool {
|
||||
pendingToolResult = -1
|
||||
out = append(out, anthropicMessage{Role: anthropicRole(m.Role), Content: blocks})
|
||||
continue
|
||||
}
|
||||
if pendingToolResult >= 0 {
|
||||
// 合并进上一条工具结果回合。类型断言必然成立(上面只写
|
||||
// []any);不成立时故意**落到下面**再开一条消息,而不是
|
||||
// 静默丢弃已有块 —— 丢块会让请求更坏,且无从察觉。
|
||||
if merged, ok := out[pendingToolResult].Content.([]any); ok {
|
||||
out[pendingToolResult].Content = append(merged, blocks...)
|
||||
continue
|
||||
}
|
||||
}
|
||||
out = append(out, anthropicMessage{Role: anthropicRole(m.Role), Content: blocks})
|
||||
pendingToolResult = len(out) - 1
|
||||
}
|
||||
return sys.String(), out, nil
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user