@ai-sdk/google
Version:
341 lines (318 loc) • 9.77 kB
text/typescript
import {
UnsupportedFunctionalityError,
type LanguageModelV4CallOptions,
type SharedV4Warning,
} from '@ai-sdk/provider';
import {
convertJSONSchemaToOpenAPISchema,
isRecursiveJSONSchemaReferenceError,
} from './convert-json-schema-to-openapi-schema';
import type { GoogleModelId } from './google-language-model-options';
import { getGoogleModelCapabilities } from './google-model-capabilities';
type FunctionTool = Extract<
NonNullable<LanguageModelV4CallOptions['tools']>[number],
{ type: 'function' }
>;
type GoogleFunctionDeclaration = {
name: string;
description: string;
parameters?: unknown;
parametersJsonSchema?: unknown;
};
export function prepareTools({
tools,
toolChoice,
modelId,
isVertexProvider = false,
}: {
tools: LanguageModelV4CallOptions['tools'];
toolChoice?: LanguageModelV4CallOptions['toolChoice'];
modelId: GoogleModelId;
isVertexProvider?: boolean;
}): {
tools:
| Array<
| {
functionDeclarations: GoogleFunctionDeclaration[];
}
| Record<string, any>
>
| undefined;
toolConfig:
| undefined
| {
functionCallingConfig?: {
mode: 'AUTO' | 'NONE' | 'ANY' | 'VALIDATED';
allowedFunctionNames?: string[];
streamFunctionCallArguments?: boolean;
};
includeServerSideToolInvocations?: boolean;
};
toolWarnings: SharedV4Warning[];
} {
// when the tools array is empty, change it to undefined to prevent errors:
tools = tools?.length ? tools : undefined;
const toolWarnings: SharedV4Warning[] = [];
const { supportsGemini2Tools, supportsFileSearch, usesGemini3Features } =
getGoogleModelCapabilities(modelId);
if (tools == null) {
return { tools: undefined, toolConfig: undefined, toolWarnings };
}
// Check for mixed tool types and add warnings
const hasFunctionTools = tools.some(tool => tool.type === 'function');
const hasProviderTools = tools.some(tool => tool.type === 'provider');
if (hasFunctionTools && hasProviderTools && !usesGemini3Features) {
toolWarnings.push({
type: 'unsupported',
feature: `combination of function and provider-defined tools`,
});
}
if (hasProviderTools) {
const googleTools: any[] = [];
const ProviderTools = tools.filter(tool => tool.type === 'provider');
ProviderTools.forEach(tool => {
switch (tool.id) {
case 'google.google_search':
if (supportsGemini2Tools) {
googleTools.push({ googleSearch: { ...tool.args } });
} else {
toolWarnings.push({
type: 'unsupported',
feature: `provider-defined tool ${tool.id}`,
details: 'Google Search requires Gemini 2.0 or newer.',
});
}
break;
case 'google.enterprise_web_search':
if (supportsGemini2Tools) {
googleTools.push({ enterpriseWebSearch: {} });
} else {
toolWarnings.push({
type: 'unsupported',
feature: `provider-defined tool ${tool.id}`,
details: 'Enterprise Web Search requires Gemini 2.0 or newer.',
});
}
break;
case 'google.url_context':
if (supportsGemini2Tools) {
googleTools.push({ urlContext: {} });
} else {
toolWarnings.push({
type: 'unsupported',
feature: `provider-defined tool ${tool.id}`,
details:
'The URL context tool is not supported with other Gemini models than Gemini 2.',
});
}
break;
case 'google.code_execution':
if (supportsGemini2Tools) {
googleTools.push({ codeExecution: {} });
} else {
toolWarnings.push({
type: 'unsupported',
feature: `provider-defined tool ${tool.id}`,
details:
'The code execution tool is not supported with other Gemini models than Gemini 2.',
});
}
break;
case 'google.file_search':
if (supportsFileSearch) {
googleTools.push({ fileSearch: { ...tool.args } });
} else {
toolWarnings.push({
type: 'unsupported',
feature: `provider-defined tool ${tool.id}`,
details:
'The file search tool is only supported with Gemini 2.5 models and Gemini 3 models.',
});
}
break;
case 'google.vertex_rag_store':
if (supportsGemini2Tools) {
googleTools.push({
retrieval: {
vertex_rag_store: {
rag_resources: {
rag_corpus: tool.args.ragCorpus,
},
similarity_top_k: tool.args.topK as number | undefined,
},
},
});
} else {
toolWarnings.push({
type: 'unsupported',
feature: `provider-defined tool ${tool.id}`,
details:
'The RAG store tool is not supported with other Gemini models than Gemini 2.',
});
}
break;
case 'google.google_maps':
if (supportsGemini2Tools) {
googleTools.push({ googleMaps: {} });
} else {
toolWarnings.push({
type: 'unsupported',
feature: `provider-defined tool ${tool.id}`,
details:
'The Google Maps grounding tool is not supported with Gemini models other than Gemini 2 or newer.',
});
}
break;
default:
toolWarnings.push({
type: 'unsupported',
feature: `provider-defined tool ${tool.id}`,
});
break;
}
});
if (hasFunctionTools && usesGemini3Features && googleTools.length > 0) {
const functionDeclarations: GoogleFunctionDeclaration[] = [];
for (const tool of tools) {
if (tool.type === 'function') {
functionDeclarations.push(prepareFunctionDeclaration(tool));
}
}
const combinedToolConfig: {
functionCallingConfig: {
mode: 'VALIDATED' | 'ANY' | 'NONE';
allowedFunctionNames?: string[];
};
includeServerSideToolInvocations?: true;
} = {
functionCallingConfig: { mode: 'VALIDATED' },
...(!isVertexProvider && {
includeServerSideToolInvocations: true,
}),
};
if (toolChoice != null) {
switch (toolChoice.type) {
case 'auto':
break;
case 'none':
combinedToolConfig.functionCallingConfig = { mode: 'NONE' };
break;
case 'required':
combinedToolConfig.functionCallingConfig = { mode: 'ANY' };
break;
case 'tool':
combinedToolConfig.functionCallingConfig = {
mode: 'ANY',
allowedFunctionNames: [toolChoice.toolName],
};
break;
}
}
return {
tools: [...googleTools, { functionDeclarations }],
toolConfig: combinedToolConfig,
toolWarnings,
};
}
return {
tools: googleTools.length > 0 ? googleTools : undefined,
toolConfig: undefined,
toolWarnings,
};
}
const functionDeclarations = [];
let hasStrictTools = false;
for (const tool of tools) {
switch (tool.type) {
case 'function':
functionDeclarations.push(prepareFunctionDeclaration(tool));
if (tool.strict === true) {
hasStrictTools = true;
}
break;
default:
toolWarnings.push({
type: 'unsupported',
feature: `function tool ${tool.name}`,
});
break;
}
}
if (toolChoice == null) {
return {
tools: [{ functionDeclarations }],
toolConfig: hasStrictTools
? { functionCallingConfig: { mode: 'VALIDATED' } }
: undefined,
toolWarnings,
};
}
const type = toolChoice.type;
switch (type) {
case 'auto':
return {
tools: [{ functionDeclarations }],
toolConfig: {
functionCallingConfig: {
mode: hasStrictTools ? 'VALIDATED' : 'AUTO',
},
},
toolWarnings,
};
case 'none':
return {
tools: [{ functionDeclarations }],
toolConfig: { functionCallingConfig: { mode: 'NONE' } },
toolWarnings,
};
case 'required':
return {
tools: [{ functionDeclarations }],
toolConfig: {
functionCallingConfig: {
mode: 'ANY',
},
},
toolWarnings,
};
case 'tool':
return {
tools: [{ functionDeclarations }],
toolConfig: {
functionCallingConfig: {
mode: 'ANY',
allowedFunctionNames: [toolChoice.toolName],
},
},
toolWarnings,
};
default: {
const _exhaustiveCheck: never = type;
throw new UnsupportedFunctionalityError({
functionality: `tool choice type: ${_exhaustiveCheck}`,
});
}
}
}
function prepareFunctionDeclaration(
tool: FunctionTool,
): GoogleFunctionDeclaration {
const declaration = {
name: tool.name,
description: tool.description ?? '',
};
try {
return {
...declaration,
parameters: convertJSONSchemaToOpenAPISchema(tool.inputSchema),
};
} catch (error) {
if (!isRecursiveJSONSchemaReferenceError(error)) {
throw error;
}
return {
...declaration,
parametersJsonSchema: tool.inputSchema,
};
}
}