Skip to content
Merged
5 changes: 5 additions & 0 deletions .changeset/wise-lamps-study.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@ai-sdk/google': patch
---

Fixed handling of image response in the tool call result.
82 changes: 82 additions & 0 deletions examples/ai-core/src/generate-text/google-image-tool-results.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { google } from '@ai-sdk/google';
import { generateText, stepCountIs, tool } from 'ai';
import { z } from 'zod/v4';
import 'dotenv/config';
import * as fs from 'fs';
import * as path from 'path';

async function fileToBase64(filePath: string): Promise<string> {
const fileBuffer = await fs.promises.readFile(filePath);
return fileBuffer.toString('base64');
}

const imageAnalysisTool = tool({
description: 'Give the image ',
inputSchema: z.object({}),
execute: async ({}) => {
try {
const imagePath = path.join(__dirname, '../../data/comic-cat.png');
const base64Image = await fileToBase64(imagePath);

return {
success: true,
description: 'Image fetched successfully',
base64Image,
};
} catch (error) {
return {
success: false,
error: `Failed to fetch image: ${error instanceof Error ? error.message : 'Unknown error'}`,
};
}
},

toModelOutput(output: { base64Image?: string }) {
return {
type: 'content',
value: [
{
type: 'media',
mediaType: 'image/png',
data: output.base64Image!,
},
],
};
},
});

async function main() {
console.log(
'🔍 Testing Google model image analysis with tool-returned images...\n',
);

const result = await generateText({
model: google('gemini-2.5-flash'),
tools: {
analyzeImage: imageAnalysisTool,
},
stopWhen: stepCountIs(2),
prompt: `Whats in this image?`,
});

console.log('📋 Analysis Result: \n');
console.log('='.repeat(60));
console.log(`${JSON.stringify(result.text, null, 2)}\n`);
// console.log(JSON.stringify(result.steps, null, 2));

if (result.toolCalls && result.toolCalls.length > 0) {
console.log('🔧 Tool Calls Made: \n');
result.toolCalls.forEach((call, index) => {
console.log(`${index + 1}. ${call.toolName}:`);
console.log(` Input: ${JSON.stringify(call.input, null, 2)}`);
});
console.log();
}

console.log('📊 Usage: \n');
console.log(`Input tokens: ${result.usage.inputTokens}`);
console.log(`Output tokens: ${result.usage.outputTokens}`);
console.log(`Total tokens: ${result.usage.totalTokens}`);
}

main().catch(console.error);
Original file line number Diff line number Diff line change
Expand Up @@ -352,4 +352,62 @@ describe('assistant messages', () => {
]),
).toThrow('File data URLs in assistant messages are not supported');
});

it('should convert tool result messages with content type (multipart with images)', async () => {
const result = convertToGoogleGenerativeAIMessages([
{
role: 'tool',
content: [
{
type: 'tool-result',
toolName: 'imageGenerator',
toolCallId: 'testCallId',
output: {
type: 'content',
value: [
{
type: 'text',
text: 'Here is the generated image:',
},
{
type: 'media',
data: 'base64encodedimagedata',
mediaType: 'image/jpeg',
},
],
},
},
],
},
]);

expect(result).toEqual({
systemInstruction: undefined,
contents: [
{
role: 'user',
parts: [
{
functionResponse: {
name: 'imageGenerator',
response: {
name: 'imageGenerator',
content: 'Here is the generated image:',
},
},
},
{
inlineData: {
mimeType: 'image/jpeg',
data: 'base64encodedimagedata',
},
},
{
text: 'Tool executed successfully and returned this image as a response',
},
],
},
],
});
});
});
60 changes: 51 additions & 9 deletions packages/google/src/convert-to-google-generative-ai-messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,17 +146,59 @@ export function convertToGoogleGenerativeAIMessages(
case 'tool': {
systemMessagesAllowed = false;

contents.push({
role: 'user',
parts: content.map(part => ({
functionResponse: {
name: part.toolName,
response: {
const parts: GoogleGenerativeAIContentPart[] = [];

for (const part of content) {
const output = part.output;

if (output.type === 'content') {
for (const contentPart of output.value) {
switch (contentPart.type) {
case 'text':
parts.push({
functionResponse: {
name: part.toolName,
response: {
name: part.toolName,
content: contentPart.text,
},
},
});
break;
case 'media':
parts.push(
{
inlineData: {
mimeType: contentPart.mediaType,
data: contentPart.data,
},
},
{
text: 'Tool executed successfully and returned this image as a response',
},
);
break;
default:
parts.push({ text: JSON.stringify(contentPart) });
break;
}
}
} else {
parts.push({
functionResponse: {
name: part.toolName,
content: part.output.value,
response: {
name: part.toolName,
content: output.value,
},
},
},
})),
});
}
}

contents.push({
role: 'user',
parts,
});
break;
}
Expand Down
Loading