diff --git a/server/utils/agents/aibitat/providers/gemini.js b/server/utils/agents/aibitat/providers/gemini.js index 53d3113a..9b43d49f 100644 --- a/server/utils/agents/aibitat/providers/gemini.js +++ b/server/utils/agents/aibitat/providers/gemini.js @@ -83,6 +83,10 @@ class GeminiProvider extends Provider { * Format the messages to the Gemini API Responses format. * - Gemini has some loosely documented format for tool calls and it can change at any time. * - We need to map the function call to the correct id and Gemini will throw an error if it does not. + * - Gemini requires a `thought_signature` (via `extra_content.google.thought_signature`) on function call + * parts in multi-turn tool conversations. This is an encrypted token Gemini attaches to every tool call + * it makes, and it must be passed back when sending tool results or Gemini rejects the request with a 400. + * See: https://ai.google.dev/gemini-api/docs/thought-signatures * @param {any[]} messages - The messages to format. * @returns {OpenAI.OpenAI.Responses.ResponseInput[]} The formatted messages. */ @@ -101,17 +105,25 @@ class GeminiProvider extends Provider { return; } + const prefixedName = this.prefixToolCall( + message.originalFunctionCall.name, + "add" + ); formattedMessages.push( { role: "assistant", + content: "", tool_calls: [ { type: "function", + ...(message.originalFunctionCall.extra_content + ? { extra_content: message.originalFunctionCall.extra_content } + : {}), function: { arguments: JSON.stringify( message.originalFunctionCall.arguments ), - name: message.originalFunctionCall.name, + name: prefixedName, }, id: message.originalFunctionCall.id, }, @@ -120,6 +132,7 @@ class GeminiProvider extends Provider { { role: "tool", tool_call_id: message.originalFunctionCall.id, + name: prefixedName, content: message.content, } ); @@ -188,6 +201,8 @@ class GeminiProvider extends Provider { name: this.prefixToolCall(toolCall.function.name, "strip"), call_id: toolCall.id, arguments: toolCall.function.arguments, + // Preserve Gemini's thought_signature so it can be passed back in #formatMessages + extra_content: toolCall.extra_content ?? null, }; eventHandler?.("reportStreamEvent", { type: "toolCallInvocation", @@ -208,6 +223,7 @@ class GeminiProvider extends Provider { id: completion.functionCall.call_id, name: completion.functionCall.name, arguments: completion.functionCall.arguments, + extra_content: completion.functionCall.extra_content, }, cost: this.getCost(), }; @@ -265,6 +281,8 @@ class GeminiProvider extends Provider { name: this.prefixToolCall(toolCall.function.name, "strip"), arguments: functionArgs, id: toolCall.id, + // Preserve Gemini's thought_signature so it can be passed back in #formatMessages + extra_content: toolCall.extra_content ?? null, }, cost, };