1 Commits

Author SHA1 Message Date
lpf
53f5a81e17 fix orphan tool messages in openai compat 2026-05-11 19:48:35 +08:00
2 changed files with 44 additions and 1 deletions

View File

@@ -183,6 +183,7 @@ func (p *HTTPProvider) buildOpenAICompatChatRequest(messages []Message, tools []
func openAICompatMessages(messages []Message) []map[string]interface{} {
out := make([]map[string]interface{}, 0, len(messages))
pendingCalls := map[string]struct{}{}
for _, msg := range messages {
role := strings.ToLower(strings.TrimSpace(msg.Role))
content := openAICompatMessageContent(msg)
@@ -199,6 +200,9 @@ func openAICompatMessages(messages []Message) []map[string]interface{} {
if len(msg.ToolCalls) > 0 {
toolCalls := make([]map[string]interface{}, 0, len(msg.ToolCalls))
for _, tc := range msg.ToolCalls {
if strings.TrimSpace(tc.ID) != "" {
pendingCalls[strings.TrimSpace(tc.ID)] = struct{}{}
}
args := ""
if tc.Function != nil {
args = tc.Function.Arguments
@@ -224,9 +228,17 @@ func openAICompatMessages(messages []Message) []map[string]interface{} {
}
out = append(out, item)
case "tool":
callID := strings.TrimSpace(msg.ToolCallID)
if callID == "" {
continue
}
if _, ok := pendingCalls[callID]; !ok {
continue
}
delete(pendingCalls, callID)
out = append(out, map[string]interface{}{
"role": "tool",
"tool_call_id": msg.ToolCallID,
"tool_call_id": callID,
"content": content,
})
default:

View File

@@ -159,6 +159,37 @@ func TestOpenAICompatMessagesPreserveMultimodalContentParts(t *testing.T) {
}
}
func TestOpenAICompatMessagesDropsOrphanToolOutputs(t *testing.T) {
msgs := openAICompatMessages([]Message{
{Role: "user", Content: "hi"},
{Role: "tool", ToolCallID: "call-orphan", Content: "orphan output"},
{
Role: "assistant",
ToolCalls: []ToolCall{{
ID: "call-1",
Name: "read_file",
Function: &FunctionCall{
Name: "read_file",
Arguments: `{"path":"README.md"}`,
},
}},
},
{Role: "tool", ToolCallID: "call-1", Content: "file content"},
})
if len(msgs) != 3 {
t.Fatalf("messages = %#v, want 3 items", msgs)
}
if got := msgs[1]["role"]; got != "assistant" {
t.Fatalf("second role = %#v, want assistant", got)
}
if got := msgs[2]["role"]; got != "tool" {
t.Fatalf("third role = %#v, want tool", got)
}
if got := msgs[2]["tool_call_id"]; got != "call-1" {
t.Fatalf("tool_call_id = %#v, want call-1", got)
}
}
func TestBuildOpenAICompatChatRequestAppliesThinkingSuffix(t *testing.T) {
base := NewHTTPProvider("openai", "token", "https://example.com/v1", "gpt-5", false, "api_key", 5*time.Second, nil)
body := base.buildOpenAICompatChatRequest([]Message{{Role: "user", Content: "hi"}}, nil, "gpt-5(high)", nil)