Skip to content

Commit 28a1cf3

Browse files
authored
fix: add reasoning content in the request for gcp gemini (#1603)
**Description** This is motivated by https://docs.cloud.google.com/vertex-ai/generative-ai/docs/thought-signatures: ``` Gemini 3 Pro enforces stricter validation on thought signatures than previous Gemini versions because they improve model performance for function calling. To ensure the model maintains full context across multiple turns of a conversation, you must return the thought signatures from previous responses in your subsequent requests. If a required thought signature is not returned when using Gemini 3 Pro, the model returns a 400 error. ``` Without this, gemini-3 can not even finish a complete tool call procedure. --------- Signed-off-by: yxia216 <[email protected]>
1 parent 1975330 commit 28a1cf3

File tree

2 files changed

+126
-0
lines changed

2 files changed

+126
-0
lines changed

internal/translator/gemini_helper.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,12 @@ func assistantMsgToGeminiParts(msg openai.ChatCompletionAssistantMessageParam) (
279279
if contPart.Text != nil && *contPart.Text != "" {
280280
parts = append(parts, genai.NewPartFromText(*contPart.Text))
281281
}
282+
case openai.ChatCompletionAssistantMessageParamContentTypeThinking:
283+
if contPart.Text != nil && *contPart.Text != "" {
284+
thoughtPart := genai.NewPartFromText(*contPart.Text)
285+
thoughtPart.Thought = true
286+
parts = append(parts, thoughtPart)
287+
}
282288
case openai.ChatCompletionAssistantMessageParamContentTypeRefusal:
283289
// Refusal messages are currently ignored in this implementation.
284290
default:

internal/translator/gemini_helper_test.go

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,126 @@ func TestAssistantMsgToGeminiParts(t *testing.T) {
319319
expectedParts: nil,
320320
expectedToolCalls: map[string]string{},
321321
},
322+
{
323+
name: "thinking content with valid text",
324+
msg: openai.ChatCompletionAssistantMessageParam{
325+
Content: openai.StringOrAssistantRoleContentUnion{
326+
Value: []openai.ChatCompletionAssistantMessageParamContent{
327+
{
328+
Type: openai.ChatCompletionAssistantMessageParamContentTypeThinking,
329+
Text: ptr.To("Let me think step by step..."),
330+
},
331+
},
332+
},
333+
Role: openai.ChatMessageRoleAssistant,
334+
},
335+
expectedParts: []*genai.Part{
336+
{
337+
Text: "Let me think step by step...",
338+
Thought: true,
339+
},
340+
},
341+
expectedToolCalls: map[string]string{},
342+
},
343+
{
344+
name: "thinking content with nil text",
345+
msg: openai.ChatCompletionAssistantMessageParam{
346+
Content: openai.StringOrAssistantRoleContentUnion{
347+
Value: []openai.ChatCompletionAssistantMessageParamContent{
348+
{
349+
Type: openai.ChatCompletionAssistantMessageParamContentTypeThinking,
350+
Text: nil,
351+
},
352+
},
353+
},
354+
Role: openai.ChatMessageRoleAssistant,
355+
},
356+
expectedParts: nil,
357+
expectedToolCalls: map[string]string{},
358+
},
359+
{
360+
name: "thinking content with empty text",
361+
msg: openai.ChatCompletionAssistantMessageParam{
362+
Content: openai.StringOrAssistantRoleContentUnion{
363+
Value: []openai.ChatCompletionAssistantMessageParamContent{
364+
{
365+
Type: openai.ChatCompletionAssistantMessageParamContentTypeThinking,
366+
Text: ptr.To(""),
367+
},
368+
},
369+
},
370+
Role: openai.ChatMessageRoleAssistant,
371+
},
372+
expectedParts: nil,
373+
expectedToolCalls: map[string]string{},
374+
},
375+
{
376+
name: "mixed content with thinking and regular text",
377+
msg: openai.ChatCompletionAssistantMessageParam{
378+
Content: openai.StringOrAssistantRoleContentUnion{
379+
Value: []openai.ChatCompletionAssistantMessageParamContent{
380+
{
381+
Type: openai.ChatCompletionAssistantMessageParamContentTypeThinking,
382+
Text: ptr.To("First, I need to analyze this problem..."),
383+
},
384+
{
385+
Type: openai.ChatCompletionAssistantMessageParamContentTypeText,
386+
Text: ptr.To("Based on my analysis, here's the answer."),
387+
},
388+
},
389+
},
390+
Role: openai.ChatMessageRoleAssistant,
391+
},
392+
expectedParts: []*genai.Part{
393+
{
394+
Text: "First, I need to analyze this problem...",
395+
Thought: true,
396+
},
397+
genai.NewPartFromText("Based on my analysis, here's the answer."),
398+
},
399+
expectedToolCalls: map[string]string{},
400+
},
401+
{
402+
name: "thinking content mixed with tool calls",
403+
msg: openai.ChatCompletionAssistantMessageParam{
404+
Content: openai.StringOrAssistantRoleContentUnion{
405+
Value: []openai.ChatCompletionAssistantMessageParamContent{
406+
{
407+
Type: openai.ChatCompletionAssistantMessageParamContentTypeThinking,
408+
Text: ptr.To("I need to call a function to get the weather"),
409+
},
410+
{
411+
Type: openai.ChatCompletionAssistantMessageParamContentTypeText,
412+
Text: ptr.To("Let me get the weather for you"),
413+
},
414+
},
415+
},
416+
Role: openai.ChatMessageRoleAssistant,
417+
ToolCalls: []openai.ChatCompletionMessageToolCallParam{
418+
{
419+
ID: ptr.To("call_weather"),
420+
Function: openai.ChatCompletionMessageToolCallFunctionParam{
421+
Name: "get_weather",
422+
Arguments: `{"location":"San Francisco"}`,
423+
},
424+
Type: openai.ChatCompletionMessageToolCallTypeFunction,
425+
},
426+
},
427+
},
428+
expectedParts: []*genai.Part{
429+
genai.NewPartFromFunctionCall("get_weather", map[string]any{
430+
"location": "San Francisco",
431+
}),
432+
{
433+
Text: "I need to call a function to get the weather",
434+
Thought: true,
435+
},
436+
genai.NewPartFromText("Let me get the weather for you"),
437+
},
438+
expectedToolCalls: map[string]string{
439+
"call_weather": "get_weather",
440+
},
441+
},
322442
}
323443

324444
for _, tc := range tests {

0 commit comments

Comments
 (0)