fix(relaykit): preserve Gemini text alongside tool calls (#7998)

This commit is contained in:
Xudong Zhang authored and GitHub committed 2026-10-25 21:01:06 +08:00
1 parent 4d962e464d
commit 0165fef98d
2 files changed
+51 -1

No files matched your search

@@ -131,7 +131,8 @@ func GeminiGenerateContentRequestToOpenAIChat(ctx context.Context, geminiRequest
if len(toolCalls) > 0 {
message.SetToolCalls(toolCalls)
} else if len(mediaContents) == 1 && mediaContents[0].Type == "text" {
}
if len(mediaContents) == 1 && mediaContents[0].Type == "text" {
message.Content = mediaContents[0].Text
} else if len(mediaContents) > 0 {
message.SetMediaContent(mediaContents)
@@ -1110,3 +1110,52 @@ func mustMarshalRequestJSON(t *testing.T, value any) []byte {
require.NoError(t, err)
return raw
}
func TestGeminiToOpenAIChatPreservesTextAlongsideToolCalls(t *testing.T) {
const text = "I will check the weather."
call := dto.GeminiPart{FunctionCall: &dto.FunctionCall{
ID: "call_weather", FunctionName: "get_weather", Arguments: map[string]any{"city": "Paris"},
}}
for _, tt := range []struct {
name string
parts []dto.GeminiPart
wantText []string
wantCalls int
}{
{name: "text and call", parts: []dto.GeminiPart{{Text: text}, call}, wantText: []string{text}, wantCalls: 1},
{name: "text around call", parts: []dto.GeminiPart{{Text: text}, call, {Text: "Please wait."}}, wantText: []string{text, "Please wait."}, wantCalls: 1},
{name: "call only", parts: []dto.GeminiPart{call}, wantCalls: 1},
{name: "text only", parts: []dto.GeminiPart{{Text: text}}, wantText: []string{text}},
} {
t.Run(tt.name, func(t *testing.T) {
request := &dto.GeminiChatRequest{Contents: []dto.GeminiChatContent{{Role: "model", Parts: tt.parts}}}
result, err := ConvertRequest(context.Background(), &convmeta.Values{
UpstreamModelName: "gpt-4o", ChannelMetaAttached: true,
}, types.RelayFormatOpenAI, request)
require.NoError(t, err)
converted, ok := result.Value.(*dto.GeneralOpenAIRequest)
require.True(t, ok)
// Verify the JSON sent upstream, including custom DTO serialization.
body, err := kitutil.Marshal(converted)
require.NoError(t, err)
var wire dto.GeneralOpenAIRequest
require.NoError(t, kitutil.Unmarshal(body, &wire))
require.Len(t, wire.Messages, 1)
assistant := wire.Messages[0]
assert.Equal(t, "assistant", assistant.Role)
content := assistant.ParseContent()
require.Len(t, content, len(tt.wantText))
for i, want := range tt.wantText {
assert.Equal(t, "text", content[i].Type)
assert.Equal(t, want, content[i].Text)
}
calls := assistant.ParseToolCalls()
require.Len(t, calls, tt.wantCalls)
if tt.wantCalls > 0 {
assert.Equal(t, "call_weather", calls[0].ID)
assert.Equal(t, "get_weather", calls[0].Function.Name)
assert.JSONEq(t, `{"city":"Paris"}`, calls[0].Function.Arguments)
}
})
}
}