Skip to content

Commit db913bd

Browse files
authored
fix(google): add thought signature to gemini 3 pro image parts (#10462)
## Background `generateText()` / `streamText()` doesn't pass thought signature for image parts in response messages. That breaks multi-turn chats with `gemini-3-pro-image-preview` ## Summary - Gemini now supports non-PNG images in assistant messages - Add thought signature to image parts in `response.messages` - spec update: `LanguageModelV3File#providerMetadata` (optional field) ## Manual Verification ```ts import { google } from '@ai-sdk/google'; import { generateText } from 'ai'; import 'dotenv/config'; import { presentImages } from '../lib/present-image'; async function main() { const step1 = await generateText({ model: google('gemini-3-pro-image-preview'), prompt: 'Create an image of Los Angeles where all car infrastructure has been replaced with bike infrastructure, trains, pedestrian zones, and parks. The image should be photorealistic and vibrant.', }); await presentImages(step1.files); const step2 = await generateText({ model: google('gemini-3-pro-image-preview'), messages: [ ...step1.response.messages, { role: 'user', content: 'Now create a variation of the image, but in the style of a watercolor painting.', }, ] }); await presentImages(step2.files); } main().catch(console.error); ``` ## Related Issues Fixes #10441 Closes #7979
1 parent cd7a224 commit db913bd

File tree

8 files changed

+56
-20
lines changed

8 files changed

+56
-20
lines changed

.changeset/popular-hounds-boil.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
'@ai-sdk/provider': patch
3+
'@ai-sdk/google': patch
4+
'ai': patch
5+
---
6+
7+
fix(google): add thought signature to gemini 3 pro image parts
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { google } from '@ai-sdk/google';
2+
import { generateText } from 'ai';
3+
4+
import { presentImages } from '../lib/present-image';
5+
import { run } from '../lib/run';
6+
7+
import 'dotenv/config';
8+
9+
run(async () => {
10+
const step1 = await generateText({
11+
model: google('gemini-3-pro-image-preview'),
12+
prompt:
13+
'Create an image of Los Angeles where all car infrastructure has been replaced with bike infrastructure, trains, pedestrian zones, and parks. The image should be photorealistic and vibrant.',
14+
});
15+
16+
await presentImages(step1.files);
17+
18+
const step2 = await generateText({
19+
model: google('gemini-3-pro-image-preview'),
20+
messages: [
21+
...step1.response.messages,
22+
{
23+
role: 'user',
24+
content:
25+
'Now create a variation of the image, but in the style of a watercolor painting.',
26+
},
27+
],
28+
});
29+
30+
await presentImages(step2.files);
31+
});

packages/ai/src/generate-text/generate-text.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -964,6 +964,9 @@ function asContent<TOOLS extends ToolSet>({
964964
return {
965965
type: 'file' as const,
966966
file: new DefaultGeneratedFile(part),
967+
...(part.providerMetadata != null
968+
? { providerMetadata: part.providerMetadata }
969+
: {}),
967970
};
968971
}
969972

packages/google/src/convert-to-google-generative-ai-messages.test.ts

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -323,19 +323,6 @@ describe('assistant messages', () => {
323323
});
324324
});
325325

326-
it('should throw error for non-PNG images in assistant messages', async () => {
327-
expect(() =>
328-
convertToGoogleGenerativeAIMessages([
329-
{
330-
role: 'assistant',
331-
content: [
332-
{ type: 'file', data: 'AAECAw==', mediaType: 'image/jpeg' },
333-
],
334-
},
335-
]),
336-
).toThrow('Only PNG images are supported in assistant messages');
337-
});
338-
339326
it('should throw error for URL file data in assistant messages', async () => {
340327
expect(() =>
341328
convertToGoogleGenerativeAIMessages([

packages/google/src/convert-to-google-generative-ai-messages.ts

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -107,13 +107,6 @@ export function convertToGoogleGenerativeAIMessages(
107107
}
108108

109109
case 'file': {
110-
if (part.mediaType !== 'image/png') {
111-
throw new UnsupportedFunctionalityError({
112-
functionality:
113-
'Only PNG images are supported in assistant messages',
114-
});
115-
}
116-
117110
if (part.data instanceof URL) {
118111
throw new UnsupportedFunctionalityError({
119112
functionality:
@@ -126,6 +119,7 @@ export function convertToGoogleGenerativeAIMessages(
126119
mimeType: part.mediaType,
127120
data: convertToBase64(part.data),
128121
},
122+
thoughtSignature,
129123
};
130124
}
131125

packages/google/src/google-generative-ai-language-model.test.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1411,6 +1411,7 @@ describe('doGenerate', () => {
14111411
{
14121412
"data": "base64encodedimagedata",
14131413
"mediaType": "image/jpeg",
1414+
"providerMetadata": undefined,
14141415
"type": "file",
14151416
},
14161417
{
@@ -1421,6 +1422,7 @@ describe('doGenerate', () => {
14211422
{
14221423
"data": "anotherbase64encodedimagedata",
14231424
"mediaType": "image/png",
1425+
"providerMetadata": undefined,
14241426
"type": "file",
14251427
},
14261428
]
@@ -1473,11 +1475,13 @@ describe('doGenerate', () => {
14731475
{
14741476
"data": "imagedata1",
14751477
"mediaType": "image/jpeg",
1478+
"providerMetadata": undefined,
14761479
"type": "file",
14771480
},
14781481
{
14791482
"data": "imagedata2",
14801483
"mediaType": "image/png",
1484+
"providerMetadata": undefined,
14811485
"type": "file",
14821486
},
14831487
]
@@ -1592,11 +1596,13 @@ describe('doGenerate', () => {
15921596
{
15931597
"data": "validimagedata",
15941598
"mediaType": "image/jpeg",
1599+
"providerMetadata": undefined,
15951600
"type": "file",
15961601
},
15971602
{
15981603
"data": "pdfdata",
15991604
"mediaType": "application/pdf",
1605+
"providerMetadata": undefined,
16001606
"type": "file",
16011607
},
16021608
]

packages/google/src/google-generative-ai-language-model.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,9 @@ export class GoogleGenerativeAILanguageModel implements LanguageModelV3 {
270270
type: 'file' as const,
271271
data: part.inlineData.data,
272272
mediaType: part.inlineData.mimeType,
273+
providerMetadata: part.thoughtSignature
274+
? { google: { thoughtSignature: part.thoughtSignature } }
275+
: undefined,
273276
});
274277
}
275278
}
@@ -807,6 +810,7 @@ const getContentSchema = () =>
807810
mimeType: z.string(),
808811
data: z.string(),
809812
}),
813+
thoughtSignature: z.string().nullish(),
810814
}),
811815
z.object({
812816
executableCode: z

packages/provider/src/language-model/v3/language-model-v3-file.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { SharedV3ProviderMetadata } from '../../shared';
2+
13
/**
24
A file that has been generated by the model.
35
Generated files as base64 encoded strings or binary data.
@@ -22,4 +24,6 @@ as base64 encoded strings. If the API returns binary data, the file data should
2224
be returned as binary data.
2325
*/
2426
data: string | Uint8Array;
27+
28+
providerMetadata?: SharedV3ProviderMetadata;
2529
};

0 commit comments

Comments
 (0)