Skip to content

Commit 2825757

Browse files
titouvgr2m
andauthored
feat(google): Adding new Google File search tool (#10051)
## Background Google [released](https://x.com/_philschmid/status/1986506204240347520) on the 6th of November then file search tool. - [Documentation](https://ai.google.dev/gemini-api/docs/file-search) - [Blog post](https://blog.google/technology/developers/file-search-gemini-api/) (Wasn't initially sure if this was pertinent but this the @ai-sdk/openai already has a [tool for file search](https://ai-sdk.dev/providers/ai-sdk-providers/openai#file-search-tool), i decided to propose this PR) ## Summary Add support for Gemini File Search so SDK users can ground Gemini 2.5 models on their own indexed documents. ## Manual Verification Use the official `@google/genai` SDK only for the parts where you can create a file search store and upload file to it. ```ts import { GoogleGenAI } from "@google/genai"; const GEMINI_API_KEY = process.env.GEMINI_API_KEY; const client = new GoogleGenAI({ vertexai: false, apiKey: GEMINI_API_KEY }); const fileSearchStore = await client.fileSearchStores.create({ config: { displayName: "test_file_search_store" }, }); console.log(fileSearchStore); let operation = await client.fileSearchStores.uploadToFileSearchStore({ fileSearchStoreName: fileSearchStore.name!, file: "./README.md", config: { mimeType: "text/markdown", }, }); console.log(operation); while (operation.done !== true) { await new Promise((resolve) => setTimeout(resolve, 1000)); operation = await client.operations.get({ operation: operation }); } ``` Then run this file ```ts import { google } from '@ai-sdk/google'; import { generateText } from 'ai'; import 'dotenv/config'; async function main() { const { text, sources, providerMetadata } = await generateText({ model: google('gemini-2.5-pro'), tools: { file_search: google.tools.fileSearch({ fileSearchStoreNames: [ 'fileSearchStores/testfilesearchstore-itgm4cn4ie11', ], // metadataFilter: 'author = "Robert Graves"', // topK: 8, }), }, prompt: 'Tell me more about the project readme.', }); console.log(text); console.log(sources); console.dir(providerMetadata, { depth: null }); } main().catch(console.error); ``` --------- Co-authored-by: Gregor Martynus <[email protected]>
1 parent 47d1ee6 commit 2825757

File tree

9 files changed

+198
-4
lines changed

9 files changed

+198
-4
lines changed

.changeset/shy-emus-relax.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'@ai-sdk/google': patch
3+
'@ai-sdk/google-vertex': patch
4+
---
5+
6+
Add Google File search tool

content/providers/01-ai-sdk-providers/15-google-generative-ai.mdx

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -489,6 +489,31 @@ Example response:
489489
}
490490
```
491491

492+
### File Search
493+
494+
The [File Search tool](https://ai.google.dev/gemini-api/docs/file-search) lets Gemini retrieve context from your own documents that you have indexed in File Search stores. Only Gemini 2.5 models support this feature.
495+
496+
```ts highlight="9-13"
497+
import { google } from '@ai-sdk/google';
498+
import { generateText } from 'ai';
499+
500+
const { text, sources } = await generateText({
501+
model: google('gemini-2.5-pro'),
502+
tools: {
503+
file_search: google.tools.fileSearch({
504+
fileSearchStoreNames: [
505+
'projects/my-project/locations/us/fileSearchStores/my-store',
506+
],
507+
metadataFilter: 'author = "Robert Graves"',
508+
topK: 8,
509+
}),
510+
},
511+
prompt: "Summarise the key themes of 'I, Claudius'.",
512+
});
513+
```
514+
515+
File Search responses include citations via the normal `sources` field and expose raw [grounding metadata](#google-search) in `providerMetadata.google.groundingMetadata`.
516+
492517
### URL Context
493518

494519
Google provides a provider-defined URL context tool.

packages/google-vertex/src/google-vertex-provider.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ vi.mock('@ai-sdk/google/internal', () => ({
1616
googleTools: {
1717
googleSearch: vi.fn(),
1818
urlContext: vi.fn(),
19+
fileSearch: vi.fn(),
1920
codeExecution: vi.fn(),
2021
},
2122
}));

packages/google-vertex/src/google-vertex-tools.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,6 @@ import { googleTools } from '@ai-sdk/google/internal';
33
export const googleVertexTools = {
44
googleSearch: googleTools.googleSearch,
55
urlContext: googleTools.urlContext,
6+
fileSearch: googleTools.fileSearch,
67
codeExecution: googleTools.codeExecution,
78
};

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

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -709,9 +709,15 @@ export const getGroundingMetadataSchema = () =>
709709
web: z
710710
.object({ uri: z.string(), title: z.string().nullish() })
711711
.nullish(),
712-
retrievedContext: z
713-
.object({ uri: z.string(), title: z.string().nullish() })
714-
.nullish(),
712+
retrievedContext: z.union([
713+
z
714+
.object({ uri: z.string(), title: z.string().nullish() })
715+
.nullish(),
716+
z.object({
717+
title: z.string().nullish(),
718+
text: z.string().nullish(),
719+
}),
720+
]),
715721
}),
716722
)
717723
.nullish(),

packages/google/src/google-prepare-tools.test.ts

Lines changed: 79 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { expect, it } from 'vitest';
22
import { prepareTools } from './google-prepare-tools';
3+
import { LanguageModelV3ProviderDefinedTool } from '@ai-sdk/provider';
34

45
it('should return undefined tools and tool_choice when tools are null', () => {
56
const result = prepareTools({
@@ -62,10 +63,24 @@ it('should correctly prepare provider-defined tools as array', () => {
6263
name: 'url_context',
6364
args: {},
6465
},
66+
{
67+
type: 'provider-defined',
68+
id: 'google.file_search',
69+
name: 'file_search',
70+
args: { fileSearchStoreNames: ['projects/foo/fileSearchStores/bar'] },
71+
},
6572
],
6673
modelId: 'gemini-2.5-flash',
6774
});
68-
expect(result.tools).toEqual([{ googleSearch: {} }, { urlContext: {} }]);
75+
expect(result.tools).toEqual([
76+
{ googleSearch: {} },
77+
{ urlContext: {} },
78+
{
79+
fileSearch: {
80+
fileSearchStoreNames: ['projects/foo/fileSearchStores/bar'],
81+
},
82+
},
83+
]);
6984
expect(result.toolConfig).toBeUndefined();
7085
expect(result.toolWarnings).toEqual([]);
7186
});
@@ -116,6 +131,69 @@ it('should add warnings for unsupported tools', () => {
116131
`);
117132
});
118133

134+
it('should add warnings for file search on unsupported models', () => {
135+
const tool: LanguageModelV3ProviderDefinedTool = {
136+
type: 'provider-defined' as const,
137+
id: 'google.file_search',
138+
name: 'file_search',
139+
args: { fileSearchStoreNames: ['projects/foo/fileSearchStores/bar'] },
140+
};
141+
142+
const result = prepareTools({
143+
tools: [tool],
144+
modelId: 'gemini-1.5-flash-8b',
145+
});
146+
147+
expect(result.tools).toBeUndefined();
148+
expect(result.toolWarnings).toMatchInlineSnapshot(`
149+
[
150+
{
151+
"details": "The file search tool is only supported with Gemini 2.5 models.",
152+
"tool": {
153+
"args": {
154+
"fileSearchStoreNames": [
155+
"projects/foo/fileSearchStores/bar",
156+
],
157+
},
158+
"id": "google.file_search",
159+
"name": "file_search",
160+
"type": "provider-defined",
161+
},
162+
"type": "unsupported-tool",
163+
},
164+
]
165+
`);
166+
});
167+
168+
it('should correctly prepare file search tool', () => {
169+
const result = prepareTools({
170+
tools: [
171+
{
172+
type: 'provider-defined',
173+
id: 'google.file_search',
174+
name: 'file_search',
175+
args: {
176+
fileSearchStoreNames: ['projects/foo/fileSearchStores/bar'],
177+
metadataFilter: 'author=Robert Graves',
178+
topK: 5,
179+
},
180+
},
181+
],
182+
modelId: 'gemini-2.5-pro',
183+
});
184+
185+
expect(result.tools).toEqual([
186+
{
187+
fileSearch: {
188+
fileSearchStoreNames: ['projects/foo/fileSearchStores/bar'],
189+
metadataFilter: 'author=Robert Graves',
190+
topK: 5,
191+
},
192+
},
193+
]);
194+
expect(result.toolWarnings).toEqual([]);
195+
});
196+
119197
it('should handle tool choice "auto"', () => {
120198
const result = prepareTools({
121199
tools: [

packages/google/src/google-prepare-tools.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ export function prepareTools({
5050
const isGemini2 = modelId.includes('gemini-2') || isLatest;
5151
const supportsDynamicRetrieval =
5252
modelId.includes('gemini-1.5-flash') && !modelId.includes('-8b');
53+
const supportsFileSearch = modelId.includes('gemini-2.5');
5354

5455
if (tools == null) {
5556
return { tools: undefined, toolConfig: undefined, toolWarnings };
@@ -124,6 +125,18 @@ export function prepareTools({
124125
});
125126
}
126127
break;
128+
case 'google.file_search':
129+
if (supportsFileSearch) {
130+
googleTools.push({ fileSearch: { ...tool.args } });
131+
} else {
132+
toolWarnings.push({
133+
type: 'unsupported-tool',
134+
tool,
135+
details:
136+
'The file search tool is only supported with Gemini 2.5 models.',
137+
});
138+
}
139+
break;
127140
default:
128141
toolWarnings.push({ type: 'unsupported-tool', tool });
129142
break;

packages/google/src/google-tools.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { codeExecution } from './tool/code-execution';
2+
import { fileSearch } from './tool/file-search';
23
import { googleSearch } from './tool/google-search';
34
import { urlContext } from './tool/url-context';
45

@@ -14,6 +15,17 @@ export const googleTools = {
1415
* Must have name "url_context".
1516
*/
1617
urlContext,
18+
/**
19+
* Enables Retrieval Augmented Generation (RAG) via the Gemini File Search tool.
20+
* Must have name "file_search".
21+
*
22+
* @param fileSearchStoreNames - Fully-qualified File Search store resource names.
23+
* @param metadataFilter - Optional filter expression to restrict the files that can be retrieved.
24+
* @param topK - Optional result limit for the number of chunks returned from File Search.
25+
*
26+
* @see https://ai.google.dev/gemini-api/docs/file-search
27+
*/
28+
fileSearch,
1729
/**
1830
* A tool that enables the model to generate and run Python code.
1931
* Must have name "code_execution".
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import {
2+
createProviderDefinedToolFactory,
3+
lazySchema,
4+
zodSchema,
5+
} from '@ai-sdk/provider-utils';
6+
import { z } from 'zod/v4';
7+
8+
/** Tool to retrieve knowledge from the File Search Stores. */
9+
const fileSearchArgsBaseSchema = z
10+
.object({
11+
/** The names of the file_search_stores to retrieve from.
12+
* Example: `fileSearchStores/my-file-search-store-123`
13+
*/
14+
fileSearchStoreNames: z
15+
.array(z.string())
16+
.describe(
17+
'The names of the file_search_stores to retrieve from. Example: `fileSearchStores/my-file-search-store-123`',
18+
),
19+
/** The number of file search retrieval chunks to retrieve. */
20+
topK: z
21+
.number()
22+
.int()
23+
.positive()
24+
.describe('The number of file search retrieval chunks to retrieve.')
25+
.optional(),
26+
27+
/** Metadata filter to apply to the file search retrieval documents.
28+
* See https://google.aip.dev/160 for the syntax of the filter expression.
29+
*/
30+
metadataFilter: z
31+
.string()
32+
.describe(
33+
'Metadata filter to apply to the file search retrieval documents. See https://google.aip.dev/160 for the syntax of the filter expression.',
34+
)
35+
.optional(),
36+
})
37+
.passthrough();
38+
39+
export type GoogleFileSearchToolArgs = z.infer<typeof fileSearchArgsBaseSchema>;
40+
41+
const fileSearchArgsSchema = lazySchema(() =>
42+
zodSchema(fileSearchArgsBaseSchema),
43+
);
44+
45+
export const fileSearch = createProviderDefinedToolFactory<
46+
{},
47+
GoogleFileSearchToolArgs
48+
>({
49+
id: 'google.file_search',
50+
name: 'file_search',
51+
inputSchema: fileSearchArgsSchema,
52+
});

0 commit comments

Comments
 (0)