fix(edge): Gemini 병렬 도구 인덱스를 정규화한다
This commit is contained in:
parent
1e2aaf4c64
commit
03aff0b8f8
2 changed files with 38 additions and 4 deletions
|
|
@ -510,9 +510,6 @@ func rewriteProviderJSONResponse(body []byte, model, toolCallWire string) []byte
|
|||
}
|
||||
|
||||
func normalizeGeminiChatProviderResponse(body []byte) []byte {
|
||||
if !bytes.Contains(body, []byte(`"thought_signature"`)) {
|
||||
return body
|
||||
}
|
||||
decoder := json.NewDecoder(bytes.NewReader(body))
|
||||
decoder.UseNumber()
|
||||
var response map[string]any
|
||||
|
|
@ -530,11 +527,22 @@ func normalizeGeminiChatProviderResponse(body []byte) []byte {
|
|||
if !ok {
|
||||
continue
|
||||
}
|
||||
for _, rawCall := range anySlice(message["tool_calls"]) {
|
||||
toolCalls := anySlice(message["tool_calls"])
|
||||
for index, rawCall := range toolCalls {
|
||||
call, ok := rawCall.(map[string]any)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
// Gemini's Chat-compatible stream may emit several complete tool
|
||||
// calls in one delta without OpenAI's per-call index. Preserve their
|
||||
// positional identity so the stream decoder does not concatenate
|
||||
// independent argument objects into index zero.
|
||||
if messageKey == "delta" && len(toolCalls) > 1 {
|
||||
if _, present := call["index"]; !present {
|
||||
call["index"] = index
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
id, idOK := call["id"].(string)
|
||||
extra, extraOK := call["extra_content"].(map[string]any)
|
||||
if !idOK || id == "" || !extraOK {
|
||||
|
|
|
|||
|
|
@ -76,6 +76,32 @@ func TestProviderThoughtSignatureNormalizationIsGeminiProfileOnly(t *testing.T)
|
|||
}
|
||||
}
|
||||
|
||||
func TestGeminiChatProviderResponseAddsMissingParallelToolIndices(t *testing.T) {
|
||||
response := []byte(`{"id":"chat-1","choices":[{"delta":{"tool_calls":[{"id":"call-1","function":{"name":"read_file","arguments":"{\"path\":\"plan.md\"}"}},{"id":"call-2","function":{"name":"read_file","arguments":"{\"path\":\"review.md\"}"}},{"id":"call-3","function":{"name":"bash","arguments":"{\"command\":\"test -f index.html\"}"}}]}}]}`)
|
||||
normalized := normalizeGeminiChatProviderResponse(response)
|
||||
var body map[string]any
|
||||
if err := json.Unmarshal(normalized, &body); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
choice := anySlice(body["choices"])[0].(map[string]any)
|
||||
delta := choice["delta"].(map[string]any)
|
||||
calls := anySlice(delta["tool_calls"])
|
||||
for index, raw := range calls {
|
||||
call := raw.(map[string]any)
|
||||
if got := int(call["index"].(float64)); got != index {
|
||||
t.Fatalf("tool call %d index = %d", index, got)
|
||||
}
|
||||
}
|
||||
|
||||
stage, err := decodeOpenAIPresetSSE([]byte("data: " + string(normalized) + "\n\ndata: {\"id\":\"chat-1\",\"choices\":[{\"delta\":{},\"finish_reason\":\"tool_calls\"}],\"usage\":{\"prompt_tokens\":1,\"completion_tokens\":1,\"total_tokens\":2}}\n\ndata: [DONE]\n\n"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(stage.ToolCalls) != 3 {
|
||||
t.Fatalf("decoded tool calls = %+v", stage.ToolCalls)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProviderChatTokenLimitNormalizationUsesSelectedProfile(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
|
|
|
|||
Loading…
Reference in a new issue