UNPKG

@ai-sdk/mistral

Version:
492 lines (463 loc) • 15.7 kB
import type { ModelCapabilities, ModelCapability, ModelEntry, ProviderOptionDefinition, ProviderOptionDefinitions, } from '@ai-sdk/provider'; import type { MistralChatModelId, MistralLanguageModelChatOptions, } from './mistral-chat-language-model-options'; import type { MistralEmbeddingModelOptions } from './mistral-embedding-model-options'; import type { MistralSpeechModelOptions } from './mistral-speech-model-options'; import type { MistralTranscriptionModelOptions } from './mistral-transcription-model-options'; export const mistralProviderOptions = { language: { safePrompt: { description: 'Inject a safety prompt. Chat API only.' }, documentImageLimit: { description: 'Maximum document images. Chat API only.', }, documentPageLimit: { description: 'Maximum document pages. Chat API only.', }, structuredOutputs: { description: 'Whether to send a JSON response schema.', }, strictJsonSchema: { description: 'Whether to use strict JSON schema validation.', }, parallelToolCalls: { description: 'Allow parallel function calls. Chat API only.', }, promptCacheKey: { description: 'Cache affinity key. Chat API only.' }, reasoningEffort: { description: 'Reasoning effort for configurable reasoning models.', }, } satisfies Record< keyof MistralLanguageModelChatOptions, ProviderOptionDefinition >, embedding: { metadata: { description: 'Metadata attached to the embedding request.' }, outputDimension: { description: 'Output embedding dimensions.' }, outputDtype: { description: 'Output embedding data type.' }, } satisfies Record< keyof MistralEmbeddingModelOptions, ProviderOptionDefinition >, transcription: { language: { description: 'Language of the audio.' }, returnLanguage: { description: 'Return the detected language.' }, temperature: { description: 'Sampling temperature.' }, timestampGranularities: { description: 'Timestamp granularities to return.', }, diarize: { description: 'Identify speakers in the transcription.' }, contextBias: { description: 'Vocabulary and context to guide transcription.', }, targetStreamingDelayMs: { description: 'Target transcription delay in milliseconds. Realtime only.', }, speakerReferences: { description: 'Named audio clips for known speakers. Realtime only.', }, turnDetection: { description: 'Turn detection settings. Realtime only.', }, } satisfies Record< keyof MistralTranscriptionModelOptions, ProviderOptionDefinition >, speech: { refAudio: { description: 'Reference audio for voice cloning, overriding voice.', }, expressiveness: { description: 'Speech expressiveness, from 1 to 5.' }, phonemeHints: { description: 'Pronunciation hints as IPA phonemes.' }, } satisfies Record<keyof MistralSpeechModelOptions, ProviderOptionDefinition>, } as const satisfies ProviderOptionDefinitions; type ReasoningEffort = NonNullable< MistralLanguageModelChatOptions['reasoningEffort'] >; type ReasoningCapabilities = { support: boolean; efforts: readonly ReasoningEffort[]; }; type LanguageCapabilities = NonNullable<ModelCapabilities['language']> & { reasoning: ReasoningCapabilities; endpoints: { conversation: { reasoning: ReasoningCapabilities; }; }; }; export type MistralLanguageModelEntry = { capabilities: { language: LanguageCapabilities }; providerOptions: { language: Record<keyof MistralLanguageModelChatOptions, ModelCapability>; }; }; type TranscriptionCapabilities = NonNullable< ModelCapabilities['transcription'] > & { languageWithTimestamps: boolean; timestamps: ModelCapability; diarization: ModelCapability; contextBias: ModelCapability; }; type SpeechCapabilities = NonNullable<ModelCapabilities['speech']> & { savedVoices: ModelCapability; referenceAudio: ModelCapability; nonStreaming: boolean; outputFormats: readonly string[]; }; type MistralModelEntry = ModelEntry & { providerOptions?: { [Section in keyof typeof mistralProviderOptions]?: Record< keyof (typeof mistralProviderOptions)[Section], ModelCapability >; }; capabilities: ModelCapabilities & { language?: LanguageCapabilities; transcription?: TranscriptionCapabilities; speech?: SpeechCapabilities; }; }; // Preserve the provider page's documented model set and image-input exceptions. const languageModelDefinitions = { 'labs-leanstral-1-5': true, 'magistral-medium-latest': true, 'magistral-small-latest': true, 'ministral-14b-latest': true, 'ministral-8b-latest': true, 'mistral-large-latest': true, 'mistral-medium-latest': true, 'mistral-small-latest': true, 'voxtral-small-latest': false, 'zai-glm-5-2': false, // Known IDs without declared capabilities retain the custom-model fallback. 'codestral-2508': undefined, 'codestral-latest': undefined, 'glm-5-2': undefined, 'labs-leanstral-1-5-1': undefined, 'ministral-14b-2512': undefined, 'ministral-3b-2512': undefined, 'ministral-3b-latest': undefined, 'ministral-8b-2512': undefined, 'mistral-code-fim-latest': undefined, 'mistral-code-latest': undefined, 'mistral-large-2512': undefined, 'mistral-large-4': undefined, 'mistral-large-4-0': undefined, 'mistral-medium': undefined, 'mistral-medium-2604': undefined, 'mistral-medium-3': undefined, 'mistral-medium-3-5': undefined, 'mistral-medium-3.5': undefined, 'mistral-small-2603': undefined, 'mistral-vibe-cli-fast': undefined, 'mistral-vibe-cli-latest': undefined, 'mistral-vibe-cli-with-tools': undefined, 'voxtral-small-2507': undefined, 'zai-glm-5': undefined, 'zai-glm-5-3': undefined, 'zai-glm-5-3-flash': undefined, 'zai-glm-latest': undefined, } satisfies Record<string, boolean | undefined>; // Known non-reasoning models from https://api.mistral.ai/v1/models // (2026-10-06). Unknown IDs inherit current reasoning configuration support. const modelsWithoutReasoningEffort = new Set<MistralChatModelId>([ 'codestral-2508', 'codestral-latest', 'ministral-14b-2512', 'ministral-14b-latest', 'ministral-3b-2512', 'ministral-3b-latest', 'ministral-8b-2512', 'ministral-8b-latest', 'mistral-code-fim-latest', 'mistral-code-latest', 'mistral-large-latest', 'mistral-large-2512', 'voxtral-small-2507', 'voxtral-small-latest', ]); // GLM-5.3 models accept only low, high, and max reasoning effort and cannot // disable reasoning. Omitting reasoning_effort uses max. // https://docs.mistral.ai/studio/conversations/reasoning (2026-10-26) const modelsWithLowHighMaxReasoningEffort = new Set<MistralChatModelId>([ 'zai-glm-5', 'zai-glm-5-3', 'zai-glm-5-3-flash', 'zai-glm-latest', ]); // Mistral Large 4 Chat models accept low, medium, and high reasoning effort // and cannot disable reasoning (see #24350). const modelsWithLowMediumHighReasoningEffort = new Set<MistralChatModelId>([ 'mistral-large-4', 'mistral-large-4-0', ]); // https://api.mistral.ai/v1/models (2026-10-06) const reasoningEffortModelIds = new Set<MistralChatModelId>([ 'glm-5-2', 'labs-leanstral-1-5', 'labs-leanstral-1-5-1', 'magistral-medium-latest', 'magistral-small-latest', 'mistral-large-4', 'mistral-large-4-0', 'mistral-medium', 'mistral-medium-2604', 'mistral-medium-3', 'mistral-medium-3-5', 'mistral-medium-3.5', 'mistral-medium-latest', 'mistral-small-2603', 'mistral-small-latest', 'mistral-vibe-cli-fast', 'mistral-vibe-cli-latest', 'mistral-vibe-cli-with-tools', 'zai-glm-5-2', ]); function reasoningCapabilities( efforts: readonly ReasoningEffort[], ): ReasoningCapabilities { return { support: efforts.length > 0, efforts }; } function createLanguageModelEntry( modelId: string, image?: boolean, ): MistralLanguageModelEntry { const reasoning = reasoningCapabilities( modelsWithoutReasoningEffort.has(modelId) ? [] : modelsWithLowHighMaxReasoningEffort.has(modelId) ? ['low', 'high', 'max'] : modelsWithLowMediumHighReasoningEffort.has(modelId) ? ['low', 'medium', 'high'] : ['none', 'high'], ); const conversationReasoning = reasoningCapabilities( reasoningEffortModelIds.has(modelId) ? ['none', 'high'] : [], ); const chatOnly = { condition: 'Only supported on the Chat API.' }; return { capabilities: { language: { inputs: { image: image ?? 'unknown', audio: 'unknown' }, objectGeneration: image != null ? true : 'unknown', tools: { usage: image != null ? true : 'unknown', streaming: image != null ? true : 'unknown', }, reasoning, sampling: { temperature: 'unknown', topP: 'unknown', logprobs: false }, endpoints: { conversation: { reasoning: conversationReasoning } }, }, }, providerOptions: { language: declareProviderOptions( mistralProviderOptions.language, image != null ? true : 'unknown', { safePrompt: chatOnly, documentImageLimit: chatOnly, documentPageLimit: chatOnly, parallelToolCalls: chatOnly, promptCacheKey: chatOnly, reasoningEffort: reasoning.support && !conversationReasoning.support ? chatOnly : reasoning.support, }, ), }, }; } function createTranscriptionModelEntry(modelId: string, declared: boolean) { // Only these legacy IDs reject pinned language combined with timestamps. // Future and custom IDs keep the existing permissive request behavior. const languageWithTimestamps = ![ 'voxtral-mini-2507', 'voxtral-mini-2602', 'voxtral-mini-latest', ].includes(modelId); const support = declared ? true : 'unknown'; const capabilities: TranscriptionCapabilities = { transcription: support, streaming: false, duration: support, segments: support, language: support, languageWithTimestamps, timestamps: support, diarization: support, contextBias: support, }; return { capabilities: { transcription: capabilities }, providerOptions: { transcription: declareProviderOptions( mistralProviderOptions.transcription, support, { targetStreamingDelayMs: false, speakerReferences: false, turnDetection: false, returnLanguage: { condition: 'Only supported when language is not pinned.', }, timestampGranularities: languageWithTimestamps ? support : { condition: 'Only supported when language is not pinned.', }, }, ), }, }; } function createSpeechModelEntry(declared: boolean) { const support = declared ? true : 'unknown'; const capabilities: SpeechCapabilities = { instructions: false, savedVoices: support, referenceAudio: support, nonStreaming: true, outputFormats: ['pcm', 'wav', 'mp3', 'flac', 'opus'], }; return { capabilities: { speech: capabilities }, providerOptions: { speech: declareProviderOptions( mistralProviderOptions.speech, support, {}, ), }, }; } function createEmbeddingModelEntry( defaultDimensions: number, customDimensions: boolean, ) { return { capabilities: { embedding: { defaultDimensions, customDimensions } }, providerOptions: { embedding: declareProviderOptions( mistralProviderOptions.embedding, true, { outputDimension: customDimensions, outputDtype: customDimensions, }, ), }, }; } const mistralEmbeddingModelEntries = { 'mistral-embed': createEmbeddingModelEntry(1024, false), // https://docs.mistral.ai/studio/knowledge-rag/embeddings/code_embeddings 'codestral-embed': createEmbeddingModelEntry(1536, true), // Known IDs without declared capabilities retain the custom-model fallback. 'codestral-embed-2505': undefined, 'mistral-embed-2312': undefined, }; const mistralSpeechModelEntries = { 'voxtral-mini-tts-3-0': createSpeechModelEntry(true), 'voxtral-mini-tts-2603': createSpeechModelEntry(true), // Known IDs without declared capabilities retain the custom-model fallback. 'voxtral-mini-tts-3': undefined, 'voxtral-mini-tts-latest': undefined, }; const mistralTranscriptionModelEntries = { 'voxtral-mini-transcribe-3-0': createTranscriptionModelEntry( 'voxtral-mini-transcribe-3-0', true, ), 'voxtral-small-transcribe-3-0': createTranscriptionModelEntry( 'voxtral-small-transcribe-3-0', true, ), 'voxtral-mini-latest': createTranscriptionModelEntry( 'voxtral-mini-latest', true, ), // Known IDs without declared capabilities retain the custom-model fallback. 'voxtral-mini-2602': undefined, 'voxtral-mini-transcribe-3': undefined, 'voxtral-mini-transcribe-latest': undefined, 'voxtral-mini-transcribe-realtime-3': undefined, 'voxtral-mini-transcribe-realtime-3-0': undefined, 'voxtral-small-transcribe-3': undefined, 'voxtral-small-transcribe-latest': undefined, }; export const mistralModelCatalog: Readonly<Record<string, MistralModelEntry>> = Object.fromEntries( Object.entries({ ...Object.fromEntries( Object.entries(languageModelDefinitions) .filter(([, definition]) => definition != null) .map(([modelId, image]) => [ modelId, createLanguageModelEntry(modelId, image), ]), ), ...mistralTranscriptionModelEntries, ...mistralSpeechModelEntries, ...mistralEmbeddingModelEntries, }).flatMap(([modelId, entry]) => entry != null ? [[modelId, entry] as const] : [], ), ); function findModelEntry(modelId: string) { return Object.prototype.hasOwnProperty.call(mistralModelCatalog, modelId) ? mistralModelCatalog[modelId] : undefined; } export function getMistralLanguageModelEntry( modelId: string, ): MistralLanguageModelEntry { const entry = findModelEntry(modelId); return entry?.capabilities.language != null && entry.providerOptions?.language != null ? { capabilities: { language: entry.capabilities.language }, providerOptions: { language: entry.providerOptions.language }, } : createLanguageModelEntry(modelId); } export function getMistralTranscriptionModelEntry(modelId: string) { const capabilities = findModelEntry(modelId)?.capabilities.transcription; return capabilities != null ? { capabilities: { transcription: capabilities } } : createTranscriptionModelEntry(modelId, false); } export function getMistralSpeechModelEntry(modelId: string) { const capabilities = findModelEntry(modelId)?.capabilities.speech; return capabilities != null ? { capabilities: { speech: capabilities } } : createSpeechModelEntry(false); } function declareProviderOptions<Option extends string>( definitions: Readonly<Record<Option, ProviderOptionDefinition>>, support: ModelCapability, exceptions: Partial<Record<Option, ModelCapability>>, ): Record<Option, ModelCapability> { return Object.fromEntries( (Object.keys(definitions) as Option[]).map(option => [ option, exceptions[option] ?? support, ]), ) as Record<Option, ModelCapability>; } /** Known model IDs, derived from the declarations used to build the catalog. */ export type MistralModelIds = { language: keyof typeof languageModelDefinitions; embedding: keyof typeof mistralEmbeddingModelEntries; speech: keyof typeof mistralSpeechModelEntries; transcription: keyof typeof mistralTranscriptionModelEntries; };