Description
When a Gemini/Vertex turn contains both the provider-executed code_execution tool and a client-defined function tool, the next turn fails with:
Please ensure that the number of function response parts is equal to the number of function call parts of the function call turn.
The root cause is that the assistant message generated by convertToModelMessages puts both the code_execution tool-call and its tool-result into the assistant content (because providerExecuted === true), and then convertToGoogleGenerativeAIMessages in @ai-sdk/google round-trips them incorrectly:
- The inbound parser for
executableCode / codeExecutionResult parts (dist/index.mjs:1584-1610 in 3.0.79, equivalent in src/google-generative-ai-language-model.ts) emits tool-call / tool-result content parts with providerExecuted: true and toolName: "code_execution", but does not attach providerMetadata.google.serverToolCallId / serverToolType — unlike the sibling toolCall / toolResponse branches a few lines below.
- On the outbound side in
convert-to-google-generative-ai-messages.ts (3.0.79):
- The assistant
tool-call branch sees no serverToolCallId/serverToolType and falls through to functionCall: { name: 'code_execution', args: ... }.
- The assistant
tool-result branch sees no serverToolCallId/serverToolType and return undefined — the result is silently dropped.
So Vertex receives a model turn with N+1 functionCalls but only N functionResponses in the next user turn (one for the client tool, none for code_execution), and rejects the request.
Reproduction
import { createVertex } from '@ai-sdk/google-vertex';
import { streamText, tool } from 'ai';
import { z } from 'zod';
const vertex = createVertex({ project: 'YOUR_PROJECT', location: 'us-east1' });
const model = vertex('gemini-2.5-flash');
const tools = {
code_execution: {
type: 'provider' as const,
id: 'google.code_execution',
name: 'codeExecution',
args: {},
},
listItems: tool({
description: 'List items',
inputSchema: z.object({}),
execute: async () => ({ items: ['a', 'b'] }),
}),
};
// First call — model emits code_execution AND listItems in the same step.
const r1 = await streamText({
model,
tools,
messages: [
{ role: 'user', content: 'Compute 2+2 with Python, then call listItems.' },
],
});
const messages = (await r1.response).messages;
// Second call — fails with the Vertex 400 above.
const r2 = await streamText({
model,
tools,
messages: [
{ role: 'user', content: 'Compute 2+2 with Python, then call listItems.' },
...messages,
{ role: 'user', content: 'Now repeat the analysis on numbers 3 and 4.' },
],
});
Proposed fix
In convertToGoogleGenerativeAIMessages, special-case toolName === 'code_execution' with providerExecuted === true so that:
- assistant
tool-call → { executableCode: { code, language } } (mirroring the inbound parser)
- assistant
tool-result → { codeExecutionResult: { outcome, output } }
Symmetry with the inbound parser at dist/index.mjs:1584-1610 would close the round-trip. Alternatively, attach serverToolCallId / serverToolType on the code-execution parts at inbound time so the existing toolCall / toolResponse outbound branches handle them (though Vertex's API for code_execution actually uses the executable_code / code_execution_result parts, not the newer toolCall/toolResponse shape, so the executable-code branch is the more correct fix).
Versions
@ai-sdk/google@3.0.79 (latest stable)
@ai-sdk/google-vertex@4.0.137
ai@6.0.174
Related
Description
When a Gemini/Vertex turn contains both the provider-executed
code_executiontool and a client-defined function tool, the next turn fails with:The root cause is that the assistant message generated by
convertToModelMessagesputs both thecode_executiontool-calland itstool-resultinto the assistant content (becauseproviderExecuted === true), and thenconvertToGoogleGenerativeAIMessagesin@ai-sdk/googleround-trips them incorrectly:executableCode/codeExecutionResultparts (dist/index.mjs:1584-1610in3.0.79, equivalent insrc/google-generative-ai-language-model.ts) emitstool-call/tool-resultcontent parts withproviderExecuted: trueandtoolName: "code_execution", but does not attachproviderMetadata.google.serverToolCallId/serverToolType— unlike the siblingtoolCall/toolResponsebranches a few lines below.convert-to-google-generative-ai-messages.ts(3.0.79):tool-callbranch sees noserverToolCallId/serverToolTypeand falls through tofunctionCall: { name: 'code_execution', args: ... }.tool-resultbranch sees noserverToolCallId/serverToolTypeandreturn undefined— the result is silently dropped.So Vertex receives a model turn with N+1
functionCalls but only NfunctionResponses in the next user turn (one for the client tool, none forcode_execution), and rejects the request.Reproduction
Proposed fix
In
convertToGoogleGenerativeAIMessages, special-casetoolName === 'code_execution'withproviderExecuted === trueso that:tool-call→{ executableCode: { code, language } }(mirroring the inbound parser)tool-result→{ codeExecutionResult: { outcome, output } }Symmetry with the inbound parser at
dist/index.mjs:1584-1610would close the round-trip. Alternatively, attachserverToolCallId/serverToolTypeon the code-execution parts at inbound time so the existingtoolCall/toolResponseoutbound branches handle them (though Vertex's API forcode_executionactually uses theexecutable_code/code_execution_resultparts, not the newertoolCall/toolResponseshape, so the executable-code branch is the more correct fix).Versions
@ai-sdk/google@3.0.79(latest stable)@ai-sdk/google-vertex@4.0.137ai@6.0.174Related
code_executiontool-name mapping issuesincludeServerSideToolInvocationsfor Vertex (related path, different bug)