@ai-sdk/google-vertex
Version:
644 lines (624 loc) • 18.8 kB
text/typescript
import {
getGoogleVertexRequestConfig as requestConfig,
type VertexRequestConfig,
} from './google-vertex-model-catalog-config';
import type {
ModelEntry,
ProviderOptionDefinition,
ProviderOptionDefinitions,
} from '@ai-sdk/provider';
import {
getGoogleLanguageModelEntry,
googleProviderOptions,
} from '@ai-sdk/google/internal';
import type { GoogleVertexEmbeddingModelOptions } from './google-vertex-embedding-model-options';
import type { GoogleVertexTranscriptionModelOptions } from './google-vertex-transcription-model-options';
import type { GoogleVertexTranscriptionModelGeminiOptions } from './gemini-transcription/google-vertex-gemini-transcription-model-options';
import type { GoogleVertexSpeechModelOptions } from './google-vertex-speech-model-options';
const documentedImageDetails: Record<
string,
{ imageGeneration: boolean; aspectRatios: readonly string[] }
> = {
'gemini-nano-banana-2.1': {
imageGeneration: true,
aspectRatios: [
'1:1',
'2:3',
'3:2',
'3:4',
'4:3',
'4:5',
'5:4',
'9:16',
'16:9',
'21:9',
'1:8',
'8:1',
'1:4',
'4:1',
],
},
'gemini-3.1-flash-image-preview': {
imageGeneration: true,
aspectRatios: [
'1:1',
'2:3',
'3:2',
'3:4',
'4:3',
'4:5',
'5:4',
'9:16',
'16:9',
'21:9',
],
},
'gemini-3-pro-image-preview': {
imageGeneration: true,
aspectRatios: [
'1:1',
'2:3',
'3:2',
'3:4',
'4:3',
'4:5',
'5:4',
'9:16',
'16:9',
'21:9',
],
},
'gemini-2.5-flash-image': {
imageGeneration: true,
aspectRatios: [
'1:1',
'2:3',
'3:2',
'3:4',
'4:3',
'4:5',
'5:4',
'9:16',
'16:9',
'21:9',
],
},
};
const documentedTranscriptionDetails: Record<
string,
{ wordTimestampDescription: string; languageDetectionDescription: string }
> = {
chirp_2: {
wordTimestampDescription:
'Available with a potential quality and speed tradeoff',
languageDetectionDescription: "Auto detection with `['auto']`",
},
chirp_3: {
wordTimestampDescription:
'Available with a potential transcription quality tradeoff',
languageDetectionDescription: "Auto detection with `['auto']`",
},
telephony: {
wordTimestampDescription: 'Available',
languageDetectionDescription:
'Explicit supported language codes, with alternative language detection support',
},
};
export const googleVertexProviderOptions = {
language: googleProviderOptions.language,
image: Object.fromEntries(
Object.entries(googleProviderOptions.language).filter(
([key]) => key !== 'responseModalities',
),
),
embedding: {
outputDimensionality: { description: 'Reduced output dimensions.' },
taskType: { description: 'Task for which embeddings are optimized.' },
title: { description: 'Document title; requires RETRIEVAL_DOCUMENT.' },
autoTruncate: {
description: 'Truncate inputs exceeding the model context.',
},
} satisfies Record<
keyof GoogleVertexEmbeddingModelOptions,
ProviderOptionDefinition
>,
speech: {
speechMetadata: { description: 'Directions for Gemini 3.8 speech.' },
turns: { description: 'Structured transcript for Gemini 3.8 speech.' },
multiSpeakerVoiceConfig: {
description: 'Voice configuration for dialogue speakers.',
},
} satisfies Record<
keyof GoogleVertexSpeechModelOptions,
ProviderOptionDefinition
>,
transcription: {
languageCodes: {
description: 'Language hints or automatic language detection.',
},
enableAutomaticPunctuation: {
description: 'Include punctuation in Cloud Speech transcripts.',
},
enableWordTimeOffsets: {
description: 'Include Cloud Speech word timestamps.',
},
region: { description: 'Cloud Speech region.' },
customVocabulary: {
description: 'Vocabulary hints for Gemini transcription.',
},
wordTimestamp: { description: 'Include Gemini word timestamps.' },
diarization: { description: 'Enable Gemini speaker diarization.' },
mode: { description: 'Verbatim or smart Gemini transcription formatting.' },
} satisfies Record<
| keyof GoogleVertexTranscriptionModelOptions
| keyof GoogleVertexTranscriptionModelGeminiOptions,
ProviderOptionDefinition
>,
video: {
pollIntervalMs: { description: 'Polling interval.' },
pollTimeoutMs: { description: 'Polling timeout.' },
personGeneration: { description: 'Policy for generating people.' },
negativePrompt: { description: 'Content to avoid.' },
generateAudio: { description: 'Generate synchronized audio.' },
gcsOutputDirectory: { description: 'Cloud Storage output directory.' },
referenceImages: { description: 'Reference images for video generation.' },
},
} as const satisfies ProviderOptionDefinitions;
type VertexEntry = ModelEntry<VertexRequestConfig>;
function languageEntry(modelId: string): VertexEntry {
const entry = getGoogleLanguageModelEntry(modelId);
return {
capabilities: { language: entry.capabilities.language },
providerOptions: {
language: { ...entry.providerOptions.language, serviceTier: false },
},
requestConfig: { language: requestConfig(modelId).language },
};
}
export function getGoogleVertexImageModelEntry(modelId: string): VertexEntry {
const known = imageModelIds.some(id => id === modelId);
return {
capabilities: {
image: {
sizes: {
description: 'Use aspectRatio; the size option is unsupported.',
},
fileInput: known ? true : 'unknown',
maskInput: known ? false : 'unknown',
maxImagesPerCall: 1,
...documentedImageDetails[modelId],
},
},
providerOptions: {
image: Object.fromEntries(
Object.keys(googleVertexProviderOptions.image).map(key => [
key,
key === 'serviceTier'
? false
: {
condition:
'Support follows the Gemini language model and image configuration.',
},
]),
),
},
requestConfig: { image: requestConfig(modelId).image },
};
}
function embeddingEntry(modelId: string): VertexEntry {
const gemini = modelId.startsWith('gemini-embedding-');
return {
capabilities: {
embedding: {
defaultDimensions: gemini ? 3072 : 768,
customDimensions: 'unknown',
...{
maxEmbeddingsPerCall:
requestConfig(modelId).embedding!.maxEmbeddingsPerCall,
supportsParallelCalls: true,
multimodal:
requestConfig(modelId).embedding!.endpoint === 'embedContent',
},
},
},
providerOptions: {
embedding: {
outputDimensionality: 'unknown',
taskType: 'unknown',
title: { condition: 'Requires RETRIEVAL_DOCUMENT.' },
autoTruncate: true,
},
},
requestConfig: { embedding: requestConfig(modelId).embedding },
};
}
function speechEntry(modelId: string): VertexEntry {
const chirp = requestConfig(modelId).speech?.api === 'cloud-tts';
return {
capabilities: {
speech: { instructions: !chirp, ...{ multiSpeaker: !chirp } },
},
providerOptions: {
speech: {
multiSpeakerVoiceConfig: chirp ? false : true,
speechMetadata: chirp
? false
: { condition: 'Requires Gemini 3.8 TTS.' },
turns: chirp ? false : { condition: 'Requires Gemini 3.8 TTS.' },
},
},
requestConfig: { speech: requestConfig(modelId).speech },
};
}
function transcriptionEntry(modelId: string): VertexEntry {
const config = requestConfig(modelId).transcription!;
const gemini = config.api === 'gemini';
return {
capabilities: {
transcription: {
transcription: !config.live,
streaming: config.live,
duration: gemini ? false : true,
segments: config.live
? false
: { condition: 'Requires word timestamps.' },
language: true,
...documentedTranscriptionDetails[modelId],
},
},
providerOptions: {
transcription: {
languageCodes: true,
enableAutomaticPunctuation: !gemini,
enableWordTimeOffsets: !gemini,
region: !gemini,
customVocabulary: gemini,
wordTimestamp: gemini,
diarization: gemini,
mode: gemini,
},
},
requestConfig: { transcription: config },
};
}
function videoEntry(modelId: string): VertexEntry {
const veo3 = modelId.startsWith('veo-3');
return {
capabilities: {
video: {
inputs: {
text: true,
image: true,
firstFrame: true,
lastFrame: {
condition: 'Requires a Veo model supporting frame interpolation.',
},
referenceImage: {
condition: 'Requires a Veo model supporting reference images.',
},
referenceVideo: false,
},
generateAudio: veo3,
aspectRatio: true,
resolution: true,
duration: true,
fps: false,
seed: true,
maxVideosPerCall: 4,
},
},
providerOptions: {
video: {
pollIntervalMs: true,
pollTimeoutMs: true,
personGeneration: true,
negativePrompt: true,
generateAudio: veo3,
gcsOutputDirectory: true,
referenceImages: {
condition: 'Requires a Veo model supporting reference images.',
},
},
},
};
}
const languageModelIds = [
'gemini-nano-banana-2.1',
'gemini-4-argon',
'gemini-4-carbon',
'gemini-4-hydrogen',
'gemini-3.8-flash',
'gemini-3.7-flash',
'gemini-3.6-flash',
'gemini-3.5-flash',
'gemini-3.5-flash-lite',
'gemini-2.5-pro',
'gemini-2.5-flash',
'gemini-2.5-flash-lite',
'gemini-2.0-flash-lite',
'gemini-2.0-flash',
'gemini-2.0-flash-001',
'gemini-1.5-flash',
'gemini-1.5-flash-001',
'gemini-1.5-flash-002',
'gemini-1.5-pro',
'gemini-1.5-pro-001',
'gemini-1.5-pro-002',
'gemini-1.0-pro-001',
'gemini-1.0-pro-vision-001',
'gemini-1.0-pro',
'gemini-1.0-pro-002',
'gemini-2.0-flash-lite-preview-02-05',
'gemini-2.5-flash-preview-09-2025',
'gemini-3-pro-preview',
'gemini-3-pro-image-preview',
'gemini-3-flash-preview',
'gemini-3.1-pro-preview',
'gemini-3.1-flash-image-preview',
'gemini-3.1-flash-lite-preview',
'gemini-2.0-pro-exp-02-05',
'gemini-2.0-flash-exp',
] as const;
const imageModelIds = [
'gemini-2.5-flash-image',
'gemini-3-pro-image-preview',
'gemini-3.1-flash-image-preview',
'gemini-nano-banana-2.1',
] as const;
const embeddingModelIds = [
'textembedding-gecko',
'textembedding-gecko@001',
'textembedding-gecko@003',
'textembedding-gecko-multilingual',
'textembedding-gecko-multilingual@001',
'text-multilingual-embedding-002',
'text-embedding-004',
'text-embedding-005',
'gemini-embedding-001',
'gemini-embedding-2',
'gemini-embedding-2-preview',
] as const;
const videoModelIds = [
'veo-2.0-generate-preview',
'veo-2.0-generate-exp',
'veo-2.0-generate-001',
'veo-3.0-generate-001',
'veo-3.0-fast-generate-001',
'veo-3.0-generate-preview',
'veo-3.0-fast-generate-preview',
'veo-3.1-generate-001',
'veo-3.1-fast-generate-001',
'veo-3.1-generate-preview',
'veo-3.1-fast-generate-preview',
] as const;
const speechModelIds = [
'gemini-2.5-flash-tts',
'gemini-2.5-pro-tts',
'gemini-2.5-flash-lite-preview-tts',
'gemini-3.1-flash-tts-preview',
'chirp-3-hd',
] as const;
const transcriptionModelIds = ['chirp_2', 'chirp_3', 'telephony'] as const;
const geminiTranscriptionModelIds = [
'gemini-3.5-transcribe',
'gemini-3.5-transcribe-live',
] as const;
const entries: [string, VertexEntry][] = [
...languageModelIds.map((id): [string, VertexEntry] => [
id,
languageEntry(id),
]),
...imageModelIds.map((id): [string, VertexEntry] => [
id,
getGoogleVertexImageModelEntry(id),
]),
...embeddingModelIds.map((id): [string, VertexEntry] => [
id,
embeddingEntry(id),
]),
...videoModelIds.map((id): [string, VertexEntry] => [id, videoEntry(id)]),
...speechModelIds.map((id): [string, VertexEntry] => [id, speechEntry(id)]),
...transcriptionModelIds.map((id): [string, VertexEntry] => [
id,
transcriptionEntry(id),
]),
...geminiTranscriptionModelIds.map((id): [string, VertexEntry] => [
id,
transcriptionEntry(id),
]),
];
/** A model may expose more than one operation (for example Gemini image models). */
export const googleVertexModelCatalog: Readonly<Record<string, VertexEntry>> =
entries.reduce<Record<string, VertexEntry>>((catalog, [id, entry]) => {
const previous = catalog[id];
catalog[id] = {
capabilities: { ...previous?.capabilities, ...entry.capabilities },
providerOptions: {
...previous?.providerOptions,
...entry.providerOptions,
},
requestConfig: { ...previous?.requestConfig, ...entry.requestConfig },
};
return catalog;
}, {});
const vertexAnthropicModels = {
'claude-fable-5-1': {
image: true,
objectGeneration: true,
tools: true,
streaming: true,
computerUse: true,
},
'claude-fable-5-2': {
image: true,
objectGeneration: true,
tools: true,
streaming: true,
computerUse: true,
},
'claude-3-7-sonnet@20250219': {
image: true,
objectGeneration: true,
tools: true,
streaming: true,
computerUse: true,
},
'claude-3-5-sonnet-v2@20241022': {
image: true,
objectGeneration: true,
tools: true,
streaming: true,
computerUse: true,
},
'claude-3-5-sonnet@20240620': {
image: true,
objectGeneration: true,
tools: true,
streaming: true,
computerUse: false,
},
'claude-3-5-haiku@20241022': {
image: false,
objectGeneration: true,
tools: true,
streaming: true,
computerUse: false,
},
'claude-3-sonnet@20240229': {
image: true,
objectGeneration: true,
tools: true,
streaming: true,
computerUse: false,
},
'claude-3-haiku@20240307': {
image: true,
objectGeneration: true,
tools: true,
streaming: true,
computerUse: false,
},
'claude-3-opus@20240229': {
image: true,
objectGeneration: true,
tools: true,
streaming: true,
computerUse: false,
},
// Known IDs without declared capabilities retain the custom-model fallback.
'claude-fable-5': undefined,
'claude-haiku-5-5': undefined,
'claude-opus-4-1@20250805': undefined,
'claude-opus-4-5@20251101': undefined,
'claude-opus-4-6': undefined,
'claude-opus-4-7': undefined,
'claude-opus-4-8': undefined,
'claude-opus-4@20250514': undefined,
'claude-opus-5': undefined,
'claude-opus-5-5': undefined,
'claude-sonnet-4-5@20250929': undefined,
'claude-sonnet-4-6': undefined,
'claude-sonnet-4@20250514': undefined,
'claude-sonnet-5': undefined,
'claude-sonnet-5-5': undefined,
};
export const googleVertexAnthropicModelCatalog = Object.fromEntries(
Object.entries(vertexAnthropicModels).flatMap(([id, support]) => {
if (support == null) return [];
const entry = {
capabilities: {
language: {
inputs: { image: support.image, audio: false },
objectGeneration: support.objectGeneration,
tools: {
usage: support.tools,
streaming: support.streaming,
computerUse: support.computerUse,
},
reasoning: { support: 'unknown' },
sampling: {
temperature: 'unknown',
topP: 'unknown',
logprobs: false,
},
},
},
} as const;
return [[id, entry satisfies ModelEntry]];
}),
);
const vertexXaiModels = {
'xai/grok-4.20-reasoning': { reasoning: true },
'xai/grok-4.20-non-reasoning': { reasoning: false },
'xai/grok-4.1-fast-reasoning': { reasoning: true },
'xai/grok-4.1-fast-non-reasoning': { reasoning: false },
};
export const googleVertexXaiModelCatalog = Object.fromEntries(
Object.entries(vertexXaiModels).map(([id, support]) => {
const entry = {
capabilities: {
language: {
inputs: { image: 'unknown', audio: 'unknown' },
objectGeneration: 'unknown',
tools: { usage: 'unknown', streaming: 'unknown' },
reasoning: { support: support.reasoning },
sampling: {
temperature: 'unknown',
topP: 'unknown',
logprobs: 'unknown',
},
},
},
} as const;
return [id, entry satisfies ModelEntry];
}),
);
const vertexMaasModels = {
'deepseek-ai/deepseek-r1-0528-maas': { publisher: 'DeepSeek' },
'deepseek-ai/deepseek-v3.1-maas': { publisher: 'DeepSeek' },
'deepseek-ai/deepseek-v3.2-maas': { publisher: 'DeepSeek' },
'openai/gpt-oss-120b-maas': { publisher: 'OpenAI' },
'openai/gpt-oss-20b-maas': { publisher: 'OpenAI' },
'meta/llama-4-maverick-17b-128e-instruct-maas': { publisher: 'Meta' },
'meta/llama-4-scout-17b-16e-instruct-maas': { publisher: 'Meta' },
'minimax/minimax-m2-maas': { publisher: 'MiniMax' },
'qwen/qwen3-coder-480b-a35b-instruct-maas': { publisher: 'Qwen' },
'qwen/qwen3-next-80b-a3b-instruct-maas': { publisher: 'Qwen' },
'qwen/qwen3-next-80b-a3b-thinking-maas': { publisher: 'Qwen' },
'moonshotai/kimi-k2-thinking-maas': { publisher: 'Moonshot' },
};
export const googleVertexMaasModelCatalog = Object.fromEntries(
Object.entries(vertexMaasModels).map(([id, support]) => {
const entry = {
capabilities: {
language: {
inputs: { image: 'unknown', audio: 'unknown' },
objectGeneration: 'unknown',
tools: { usage: 'unknown', streaming: 'unknown' },
reasoning: { support: 'unknown' },
sampling: {
temperature: 'unknown',
topP: 'unknown',
logprobs: 'unknown',
},
publisher: support.publisher,
},
},
requestConfig: requestConfig(id),
} as const;
return [id, entry satisfies ModelEntry<VertexRequestConfig>];
}),
);
/** Known model IDs, derived from the declarations used to build the catalog. */
export type GoogleVertexModelIds = {
language: (typeof languageModelIds)[number];
image: (typeof imageModelIds)[number];
embedding: (typeof embeddingModelIds)[number];
video: (typeof videoModelIds)[number];
speech: (typeof speechModelIds)[number];
transcription: (typeof transcriptionModelIds)[number];
geminiTranscription: (typeof geminiTranscriptionModelIds)[number];
anthropic: keyof typeof vertexAnthropicModels;
xai: keyof typeof vertexXaiModels;
maas: keyof typeof vertexMaasModels;
};