Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/six-pumas-refuse.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@ai-sdk/google": patch
---

fix(google-vertex): don't send streamFunctionCallArguments for unary API calls and change default to false
7 changes: 3 additions & 4 deletions content/providers/01-ai-sdk-providers/16-google-vertex.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -350,7 +350,7 @@ The following optional provider options are available for Google Vertex models:
Optional. When set to true, function call arguments will be streamed
incrementally in streaming responses. This enables `tool-input-delta` events
to arrive as the model generates function call arguments, reducing perceived
latency for tool calls. Defaults to `true` for Vertex AI providers. Only supported on the Vertex AI API (not the Gemini API).
latency for tool calls. Defaults to `false`. Only supported on the Vertex AI API (not the Gemini API) with Gemini 3+ models.

Consult [Google's Documentation](https://docs.cloud.google.com/vertex-ai/generative-ai/docs/multimodal/function-calling#streaming-fc) for details.

Expand Down Expand Up @@ -469,8 +469,7 @@ For Gemini 3 Pro and later models on Vertex AI, you can stream function call
arguments as they are generated by setting `streamFunctionCallArguments` to
`true`. This reduces perceived latency when functions need to be called, as
`tool-input-delta` events arrive incrementally instead of waiting for the
complete arguments. This option is `true` by default and you can opt out by
setting it to false.
complete arguments. This option defaults to `false`.

```ts
import { vertex } from '@ai-sdk/google-vertex';
Expand All @@ -491,7 +490,7 @@ const result = streamText({
},
providerOptions: {
vertex: {
streamFunctionCallArguments: false,
streamFunctionCallArguments: true,
} satisfies GoogleLanguageModelOptions,
},
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ run(async () => {
}),
},
},
providerOptions: {
vertex: {
streamFunctionCallArguments: true,
},
},
includeRawChunks: true,
});

Expand Down
147 changes: 138 additions & 9 deletions packages/google/src/google-generative-ai-language-model.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5713,7 +5713,7 @@ describe('doStream', () => {
).toMatchInlineSnapshot(`undefined`);
});

it('should default streamFunctionCallArguments to true for Vertex provider even without provider option', async () => {
it('should default streamFunctionCallArguments to false for Vertex provider without provider option', async () => {
server.urls[TEST_URL_GEMINI_PRO].response = {
type: 'stream-chunks',
chunks: [
Expand Down Expand Up @@ -5759,14 +5759,10 @@ describe('doStream', () => {
],
});

expect((await server.calls[0].requestBodyJson).toolConfig)
.toMatchInlineSnapshot(`
{
"functionCallingConfig": {
"streamFunctionCallArguments": true,
},
}
`);
expect(
(await server.calls[0].requestBodyJson).toolConfig?.functionCallingConfig
?.streamFunctionCallArguments,
).toMatchInlineSnapshot(`undefined`);
});

it('should allow Vertex provider to opt out of streamFunctionCallArguments by setting it to false', async () => {
Expand Down Expand Up @@ -5826,6 +5822,139 @@ describe('doStream', () => {
).toBeUndefined();
});

it('should not send streamFunctionCallArguments for Vertex provider doGenerate (unary API)', async () => {
server.urls[TEST_URL_GEMINI_PRO].response = {
type: 'json-value',
body: {
candidates: [
{
content: { parts: [{ text: 'Hello' }], role: 'model' },
finishReason: 'STOP',
safetyRatings: SAFETY_RATINGS,
},
],
usageMetadata: {
promptTokenCount: 1,
candidatesTokenCount: 1,
totalTokenCount: 2,
},
},
};

const vertexModel = new GoogleGenerativeAILanguageModel('gemini-pro', {
provider: 'google.vertex.chat',
baseURL: 'https://generativelanguage.googleapis.com/v1beta',
headers: { 'x-goog-api-key': 'test-api-key' },
generateId: () => 'test-id',
});

await vertexModel.doGenerate({
prompt: TEST_PROMPT,
tools: [
{
type: 'function',
name: 'test-tool',
inputSchema: {
type: 'object',
properties: { value: { type: 'string' } },
required: ['value'],
additionalProperties: false,
$schema: 'http://json-schema.org/draft-07/schema#',
},
},
],
});

expect(await server.calls[0].requestBodyJson).toMatchInlineSnapshot(`
{
"contents": [
{
"parts": [
{
"text": "Hello",
},
],
"role": "user",
},
],
"generationConfig": {},
"tools": [
{
"functionDeclarations": [
{
"description": "",
"name": "test-tool",
"parameters": {
"properties": {
"value": {
"type": "string",
},
},
"required": [
"value",
],
"type": "object",
},
},
],
},
],
}
`);
});

it('should not send streamFunctionCallArguments for Vertex provider doGenerate even when explicitly set', async () => {
server.urls[TEST_URL_GEMINI_PRO].response = {
type: 'json-value',
body: {
candidates: [
{
content: { parts: [{ text: 'Hello' }], role: 'model' },
finishReason: 'STOP',
safetyRatings: SAFETY_RATINGS,
},
],
usageMetadata: {
promptTokenCount: 1,
candidatesTokenCount: 1,
totalTokenCount: 2,
},
},
};

const vertexModel = new GoogleGenerativeAILanguageModel('gemini-pro', {
provider: 'google.vertex.chat',
baseURL: 'https://generativelanguage.googleapis.com/v1beta',
headers: { 'x-goog-api-key': 'test-api-key' },
generateId: () => 'test-id',
});

await vertexModel.doGenerate({
prompt: TEST_PROMPT,
providerOptions: {
vertex: {
streamFunctionCallArguments: true,
},
},
});

expect(await server.calls[0].requestBodyJson).toMatchInlineSnapshot(`
{
"contents": [
{
"parts": [
{
"text": "Hello",
},
],
"role": "user",
},
],
"generationConfig": {},
}
`);
});

it('should only pass valid provider options', async () => {
prepareChunksFixtureResponse('google-text');

Expand Down
47 changes: 27 additions & 20 deletions packages/google/src/google-generative-ai-language-model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,22 +88,25 @@ export class GoogleGenerativeAILanguageModel implements LanguageModelV4 {
return this.config.supportedUrls?.() ?? {};
}

private async getArgs({
prompt,
maxOutputTokens,
temperature,
topP,
topK,
frequencyPenalty,
presencePenalty,
stopSequences,
responseFormat,
seed,
tools,
toolChoice,
reasoning,
providerOptions,
}: LanguageModelV4CallOptions) {
private async getArgs(
{
prompt,
maxOutputTokens,
temperature,
topP,
topK,
frequencyPenalty,
presencePenalty,
stopSequences,
responseFormat,
seed,
tools,
toolChoice,
reasoning,
providerOptions,
}: LanguageModelV4CallOptions,
{ isStreaming = false }: { isStreaming?: boolean } = {},
) {
const warnings: SharedV4Warning[] = [];

const providerOptionsName = this.config.provider.includes('vertex')
Expand Down Expand Up @@ -184,9 +187,10 @@ export class GoogleGenerativeAILanguageModel implements LanguageModelV4 {
? { ...resolvedThinking, ...googleOptions?.thinkingConfig }
: undefined;

const streamFunctionCallArguments = isVertexProvider
? (googleOptions?.streamFunctionCallArguments ?? true)
: undefined;
const streamFunctionCallArguments =
isStreaming && isVertexProvider
? (googleOptions?.streamFunctionCallArguments ?? false)
: undefined;

const toolConfig =
googleToolConfig ||
Expand Down Expand Up @@ -478,7 +482,10 @@ export class GoogleGenerativeAILanguageModel implements LanguageModelV4 {
async doStream(
options: LanguageModelV4CallOptions,
): Promise<LanguageModelV4StreamResult> {
const { args, warnings, providerOptionsName } = await this.getArgs(options);
const { args, warnings, providerOptionsName } = await this.getArgs(
options,
{ isStreaming: true },
);

const headers = combineHeaders(
await resolve(this.config.headers),
Expand Down
5 changes: 3 additions & 2 deletions packages/google/src/google-generative-ai-options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -192,9 +192,10 @@ export const googleLanguageModelOptions = lazySchema(() =>
/**
* Optional. When set to true, function call arguments will be streamed
* incrementally via partialArgs in streaming responses. Only supported
* on the Vertex AI API (not the Gemini API).
* on the Vertex AI API (not the Gemini API) and only for Gemini 3+
* models.
*
* @default true
* @default false
*
* https://docs.cloud.google.com/vertex-ai/generative-ai/docs/multimodal/function-calling#streaming-fc
*/
Expand Down
Loading