UNPKG

@ai-sdk/google

Version:
361 lines (334 loc) • 12.1 kB
import type { ModelCapabilities, ModelCapability, ModelEntry, ProviderOptionDefinition, ProviderOptionDefinitions, } from '@ai-sdk/provider'; import type { GoogleLanguageModelOptions } from './google-language-model-options'; const gemini1ModelPattern = /(^|[/.])gemini-1(?:[.-]|$)/i; const gemini2ModelPattern = /(^|[/.])gemini-2(?:[.-]|$)/i; const gemini25ModelPattern = /(^|[/.])gemini-2\.5(?:[.-]|$)/i; const geminiModelPattern = /(^|[/.])gemini-/i; type GoogleLanguageProviderOption = keyof GoogleLanguageModelOptions; /** Options accepted under providerOptions.google (or googleVertex on Vertex). */ export const googleProviderOptions = { language: { responseModalities: { description: 'Response modalities to generate.' }, thinkingConfig: { description: 'Thinking budget, level, and thought output.', }, cachedContent: { description: 'Cached content to use as request context.' }, structuredOutputs: { description: 'Whether to send the JSON response schema.', }, safetySettings: { description: 'Safety settings for the request.' }, threshold: { description: 'Default threshold for configurable safety categories.', }, audioTimestamp: { description: 'Enable timestamp understanding for audio.', }, labels: { description: 'Billing labels. Vertex AI only.' }, mediaResolution: { description: 'Resolution used to process media inputs.', }, imageConfig: { description: 'Image generation configuration.' }, retrievalConfig: { description: 'Retrieval configuration for tools.' }, streamFunctionCallArguments: { description: 'Stream function arguments. Vertex AI only.', }, serviceTier: { description: 'Service tier. Gemini API only.' }, continuationToken: { description: 'Resume a response using its continuation token.', }, sharedRequestType: { description: 'Shared PayGo tier. Vertex AI only.' }, requestType: { description: 'Request routing type. Vertex AI only.' }, } satisfies Record<GoogleLanguageProviderOption, ProviderOptionDefinition>, } as const satisfies ProviderOptionDefinitions; type GoogleLanguageCapabilities = NonNullable<ModelCapabilities['language']> & { tools: NonNullable<ModelCapabilities['language']>['tools'] & { /** Existing request gate for Gemini 2 provider-defined tools. */ gemini2: boolean; googleSearch: ModelCapability; urlContext: ModelCapability; fileSearch: boolean; mixed: boolean; functionResponseParts: boolean; }; reasoning: NonNullable<ModelCapabilities['language']>['reasoning'] & { minBudget: number; maxBudget: number; }; sampling: NonNullable<ModelCapabilities['language']>['sampling'] & { topK: ModelCapability; penalties: ModelCapability; }; structuredOutput: { withFunctionTools: boolean; }; }; type GoogleLanguageRequestConfig = { systemInstruction: 'system' | 'user'; thinkingFormat: 'level' | 'budget'; reasoningMaxOutputTokens: number; jsonResponseTool: boolean; }; export type GoogleLanguageModelEntry = ModelEntry<{ language: GoogleLanguageRequestConfig; }> & { capabilities: { language: GoogleLanguageCapabilities }; providerOptions: { language: Record<GoogleLanguageProviderOption, ModelCapability>; }; requestConfig: { language: GoogleLanguageRequestConfig }; }; type DocumentedCapabilities = { image: boolean; objectGeneration: boolean; tools: boolean; streaming: boolean; googleSearch: boolean; urlContext: boolean; }; // Preserve the existing documentation's model set and per-model exceptions. const standard = { image: true, objectGeneration: true, tools: true, streaming: true, googleSearch: true, urlContext: true, } as const; const languageModelDefinitions = { 'gemini-nano-banana-2.1': { image: true, objectGeneration: false, tools: false, streaming: false, googleSearch: true, urlContext: false, }, 'gemini-4-argon': standard, 'gemini-4-carbon': standard, 'gemini-4-hydrogen': standard, 'gemini-3.8-flash': standard, 'gemini-3.7-flash': standard, 'gemini-3.6-flash': standard, 'gemini-3.5-flash': standard, 'gemini-3.5-flash-lite': standard, 'gemini-3.1-pro-preview': standard, 'gemini-3.1-flash-image-preview': standard, 'gemini-3.1-flash-lite-preview': standard, 'gemini-3-pro-preview': standard, 'gemini-3-pro-image-preview': standard, 'gemini-3-flash-preview': standard, 'gemini-2.5-pro': standard, 'gemini-2.5-flash': standard, 'gemini-2.5-flash-lite': standard, 'gemini-2.5-flash-lite-preview-06-17': standard, 'gemini-2.0-flash': standard, // Known IDs without declared capabilities retain the custom-model fallback. aqa: undefined, 'deep-research-max-preview-04-2026': undefined, 'deep-research-preview-04-2026': undefined, 'deep-research-pro-preview-12-2025': undefined, 'gemini-2.0-flash-001': undefined, 'gemini-2.0-flash-lite': undefined, 'gemini-2.0-flash-lite-001': undefined, 'gemini-2.5-computer-use-preview-10-2025': undefined, 'gemini-2.5-flash-image': undefined, 'gemini-2.5-flash-native-audio-latest': undefined, 'gemini-2.5-flash-native-audio-preview-09-2025': undefined, 'gemini-2.5-flash-native-audio-preview-12-2025': undefined, 'gemini-2.5-flash-preview-tts': undefined, 'gemini-2.5-pro-preview-tts': undefined, 'gemini-3.1-flash-tts-preview': undefined, 'gemini-3.1-pro-preview-customtools': undefined, 'gemini-flash-latest': undefined, 'gemini-flash-lite-latest': undefined, 'gemini-pro-latest': undefined, 'gemini-robotics-er-1.5-preview': undefined, 'gemma-3-12b-it': undefined, 'gemma-3-1b-it': undefined, 'gemma-3-27b-it': undefined, 'gemma-3-4b-it': undefined, 'gemma-3n-e2b-it': undefined, 'gemma-3n-e4b-it': undefined, 'nano-banana-pro-preview': undefined, } satisfies Record<string, DocumentedCapabilities | undefined>; export const googleModelCatalog: Readonly< Record<string, GoogleLanguageModelEntry> > = Object.fromEntries( Object.entries(languageModelDefinitions) .filter(([, definition]) => definition != null) .map(([modelId, documented]) => [ modelId, createGoogleLanguageModelEntry(modelId, documented), ]), ); export function getGoogleLanguageModelEntry( modelId: string, ): GoogleLanguageModelEntry { return Object.prototype.hasOwnProperty.call(googleModelCatalog, modelId) ? googleModelCatalog[modelId] : createGoogleLanguageModelEntry(modelId); } function createGoogleLanguageModelEntry( modelId: string, documented?: DocumentedCapabilities, ): GoogleLanguageModelEntry { const runtime = resolveRuntimeDefinition(modelId); const declared = documented != null; const id = modelId.toLowerCase(); // Preserve the original case-sensitive exception in the thinking resolver. const thinkingFormat = runtime.usesGemini3Features && !modelId.includes('gemini-3-pro-image') ? 'level' : 'budget'; const isHydrogen = /(^|[/.])gemini-4-hydrogen(?:-|$)/i.test(modelId); const minLevel = isHydrogen ? 'low' : getMinimumThinkingLevelForGemini3Model(modelId); const language: GoogleLanguageCapabilities = { inputs: { image: documented?.image ?? 'unknown', audio: 'unknown' }, objectGeneration: documented?.objectGeneration ?? 'unknown', tools: { usage: documented?.tools ?? 'unknown', streaming: documented?.streaming ?? 'unknown', gemini2: runtime.supportsGemini2Tools, googleSearch: documented?.googleSearch ?? 'unknown', urlContext: documented?.urlContext ?? 'unknown', fileSearch: runtime.supportsFileSearch, mixed: runtime.usesGemini3Features, functionResponseParts: runtime.usesGemini3Features, }, reasoning: { support: 'unknown', efforts: thinkingFormat === 'level' ? [ ...(minLevel === 'minimal' ? ['minimal'] : []), 'low', 'medium', 'high', ...(isHydrogen ? ['xhigh'] : []), ] : undefined, minBudget: id.includes('2.5-pro') ? 128 : 0, maxBudget: id.includes('2.5-pro') || id.includes('gemini-3-pro-image') ? 32768 : 24576, }, sampling: { temperature: 'unknown', topP: 'unknown', topK: 'unknown', logprobs: false, penalties: gemini25ModelPattern.test(modelId) ? { condition: 'Only supported on the Vertex AI API.' } : true, }, structuredOutput: { withFunctionTools: runtime.usesGemini3Features, }, }; return { capabilities: { language }, providerOptions: { language: declareProviderOptions(declared ? true : 'unknown', { labels: { condition: 'Only supported on the Vertex AI API.' }, sharedRequestType: { condition: 'Only supported on the Vertex AI API.', }, requestType: { condition: 'Only supported on the Vertex AI API.' }, serviceTier: { condition: 'Only supported on the Gemini API.' }, streamFunctionCallArguments: { condition: 'Only supported for streaming requests on the Vertex AI API with Gemini 3 or newer.', }, }), }, requestConfig: { language: { systemInstruction: id.startsWith('gemma-') ? 'user' : 'system', thinkingFormat, reasoningMaxOutputTokens: 65536, jsonResponseTool: /(^|\/)gemini-/i.test(modelId), }, }, }; } function declareProviderOptions( support: ModelCapability, exceptions: Partial<Record<GoogleLanguageProviderOption, ModelCapability>>, ): Record<GoogleLanguageProviderOption, ModelCapability> { return Object.fromEntries( ( Object.keys( googleProviderOptions.language, ) as GoogleLanguageProviderOption[] ).map(option => [option, exceptions[option] ?? support]), ) as Record<GoogleLanguageProviderOption, ModelCapability>; } function isKnownPreGemini2Model(modelId: string): boolean { return ( gemini1ModelPattern.test(modelId) || /(^|[/.])gemini-pro(?:-vision)?$/i.test(modelId) || /(^|[/.])gemini-robotics-er-1\.5(?:[.-]|$)/i.test(modelId) ); } /** * Classifies Gemini capabilities by excluding known older generations. * * Google model IDs are open-ended, so unrecognized Gemini IDs and aliases * intentionally inherit the newest supported behavior. Add exceptions here * only when a model is known to require a legacy request shape. */ function resolveRuntimeDefinition(modelId: string) { const isGeminiModel = geminiModelPattern.test(modelId); const isGemini2Model = gemini2ModelPattern.test(modelId); const isKnownPreGemini2 = isKnownPreGemini2Model(modelId); const isKnownOlderModel = isKnownPreGemini2 || isGemini2Model; const usesGemini3Features = isGeminiModel && !isKnownOlderModel; return { supportsGemini2Tools: (isGeminiModel && !isKnownPreGemini2) || modelId.toLowerCase().includes('nano-banana'), supportsFileSearch: gemini25ModelPattern.test(modelId) || usesGemini3Features, usesGemini3Features, }; } function getMinimumThinkingLevelForGemini3Model( modelId: string, ): 'minimal' | 'low' { const modelName = /(?:^|[/.])(gemini-[^/]*)$/i .exec(modelId)?.[1] .toLowerCase(); if (modelName === 'gemini-flash-latest') { return 'low'; } const proVersionMatch = /^gemini-(\d+)(?:\.\d+)?-pro(?:$|-)/.exec( modelName ?? '', ); if (Number(proVersionMatch?.[1]) >= 3) { return 'low'; } const versionMatch = /^gemini-(\d+)\.(\d+)-flash(?:$|-(?!lite(?:-|$)))/.exec( modelName ?? '', ); if (versionMatch == null) { return 'minimal'; } const majorVersion = Number(versionMatch[1]); const minorVersion = Number(versionMatch[2]); return majorVersion > 3 || (majorVersion === 3 && minorVersion >= 7) ? 'low' : 'minimal'; } /** Known model IDs, derived from the declarations used to build the catalog. */ export type GoogleModelIds = { language: keyof typeof languageModelDefinitions; };