diff --git a/.changeset/new-apples-raise.md b/.changeset/new-apples-raise.md new file mode 100644 index 000000000000..a7a765686d7e --- /dev/null +++ b/.changeset/new-apples-raise.md @@ -0,0 +1,5 @@ +--- +'@ai-sdk/mistral': patch +--- + +feat(provider/mistral): `response_format.type === 'json_schema'` diff --git a/content/providers/01-ai-sdk-providers/20-mistral.mdx b/content/providers/01-ai-sdk-providers/20-mistral.mdx index e272f499d349..2c1b1f5b68fb 100644 --- a/content/providers/01-ai-sdk-providers/20-mistral.mdx +++ b/content/providers/01-ai-sdk-providers/20-mistral.mdx @@ -111,6 +111,18 @@ The following optional provider options are available for Mistral models: Maximum number of pages to process in a document. +- **strictJsonSchema** _boolean_ + + Whether to use strict JSON schema validation for structured outputs. Only applies when a schema is provided and only sets the [`strict` flag](https://docs.mistral.ai/api/#tag/chat/operation/chat_completion_v1_chat_completions_post) in addition to using [Custom Structured Outputs](https://docs.mistral.ai/capabilities/structured-output/custom_structured_output/), which is used by default if a schema is provided. + + Defaults to `false`. + +- **structuredOutputs** _boolean_ + + Whether to use [structured outputs](#structured-outputs). When enabled, tool calls and object generation will be strict and follow the provided schema. + + Defaults to `true`. + ### Document OCR Mistral chat models support document OCR for PDF files. @@ -200,6 +212,60 @@ const { text } = await generateText({ Mistral language models can also be used in the `streamText`, `generateObject`, and `streamObject` functions (see [AI SDK Core](/docs/ai-sdk-core)). +#### Structured Outputs + +Mistral chat models support structured outputs using JSON Schema. You can use `generateObject` or `streamObject` +with Zod, Valibot, or raw JSON Schema. The SDK sends your schema via Mistral's `response_format: { type: 'json_schema' }`. + +```ts +import { mistral } from '@ai-sdk/mistral'; +import { generateObject } from 'ai'; +import { z } from 'zod/v3'; + +const result = await generateObject({ + model: mistral('mistral-large-latest'), + schema: z.object({ + recipe: z.object({ + name: z.string(), + ingredients: z.array(z.string()), + instructions: z.array(z.string()), + }), + }), + prompt: 'Generate a simple pasta recipe.', +}); + +console.log(JSON.stringify(result.object, null, 2)); +``` + +You can enable strict JSON Schema validation using a provider option: + +```ts highlight="7-11" +import { mistral } from '@ai-sdk/mistral'; +import { generateObject } from 'ai'; +import { z } from 'zod/v3'; + +const result = await generateObject({ + model: mistral('mistral-large-latest'), + providerOptions: { + mistral: { + strictJsonSchema: true, // reject outputs that don't strictly match the schema + }, + }, + schema: z.object({ + title: z.string(), + items: z.array(z.object({ id: z.string(), qty: z.number().int().min(1) })), + }), + prompt: 'Generate a small shopping list.', +}); +``` + + + When using structured outputs, the SDK no longer injects an extra "answer with + JSON" instruction. It relies on Mistral's native `json_schema`/`json_object` + response formats instead. You can customize the schema name/description via + the standard structured-output APIs. + + ### Model Capabilities | Model | Image Input | Object Generation | Tool Usage | Tool Streaming | diff --git a/examples/ai-core/src/generate-object/mistral.ts b/examples/ai-core/src/generate-object/mistral.ts index 186ce5718824..5dbf095fc1ad 100644 --- a/examples/ai-core/src/generate-object/mistral.ts +++ b/examples/ai-core/src/generate-object/mistral.ts @@ -19,6 +19,14 @@ async function main() { }), }), prompt: 'Generate a lasagna recipe.', + providerOptions: { + mistral: { + // `open-mistral-7b` model has problems with the `$schema` property + // in the JSON schema unless `strict` is set to true + // See https://github.com/vercel/ai/pull/8130#issuecomment-3213138032 + strictJsonSchema: true, + }, + }, }); console.log(JSON.stringify(result.object.recipe, null, 2)); diff --git a/packages/mistral/src/mistral-chat-language-model.test.ts b/packages/mistral/src/mistral-chat-language-model.test.ts index 4a9fb19fa55a..28a21c11be31 100644 --- a/packages/mistral/src/mistral-chat-language-model.test.ts +++ b/packages/mistral/src/mistral-chat-language-model.test.ts @@ -571,12 +571,6 @@ describe('doGenerate', () => { "document_page_limit": undefined, "max_tokens": undefined, "messages": [ - { - "content": "JSON schema: - {"type":"object","properties":{"name":{"type":"string"}}} - You MUST answer with a JSON object that matches the JSON schema above.", - "role": "system", - }, { "content": [ { @@ -590,7 +584,20 @@ describe('doGenerate', () => { "model": "mistral-small-latest", "random_seed": undefined, "response_format": { - "type": "json_object", + "json_schema": { + "description": undefined, + "name": "response", + "schema": { + "properties": { + "name": { + "type": "string", + }, + }, + "type": "object", + }, + "strict": false, + }, + "type": "json_schema", }, "safe_prompt": undefined, "temperature": undefined, diff --git a/packages/mistral/src/mistral-chat-language-model.ts b/packages/mistral/src/mistral-chat-language-model.ts index 8af1cb4b3aa8..3d5f0148f6a4 100644 --- a/packages/mistral/src/mistral-chat-language-model.ts +++ b/packages/mistral/src/mistral-chat-language-model.ts @@ -110,22 +110,12 @@ export class MistralChatLanguageModel implements LanguageModelV2 { }); } - // TODO remove when we have JSON schema support (see OpenAI implementation) - if ( - responseFormat != null && - responseFormat.type === 'json' && - responseFormat.schema != null - ) { - warnings.push({ - type: 'unsupported-setting', - setting: 'responseFormat', - details: 'JSON response format schema is not supported', - }); - } + const structuredOutputs = options.structuredOutputs ?? true; + const strictJsonSchema = options.strictJsonSchema ?? false; // For Mistral we need to need to instruct the model to return a JSON object. // https://docs.mistral.ai/capabilities/structured-output/structured_output_overview/ - if (responseFormat?.type === 'json') { + if (responseFormat?.type === 'json' && !responseFormat?.schema) { prompt = injectJsonInstructionIntoMessages({ messages: prompt, schema: responseFormat.schema, @@ -146,9 +136,20 @@ export class MistralChatLanguageModel implements LanguageModelV2 { random_seed: seed, // response format: - // TODO add JSON schema support (see OpenAI implementation) response_format: - responseFormat?.type === 'json' ? { type: 'json_object' } : undefined, + responseFormat?.type === 'json' + ? structuredOutputs && responseFormat?.schema != null + ? { + type: 'json_schema', + json_schema: { + schema: responseFormat.schema, + strict: strictJsonSchema, + name: responseFormat.name ?? 'response', + description: responseFormat.description, + }, + } + : { type: 'json_object' } + : undefined, // mistral-specific provider options: document_image_limit: options.documentImageLimit, diff --git a/packages/mistral/src/mistral-chat-options.ts b/packages/mistral/src/mistral-chat-options.ts index 6f5af4280d1b..58396171b319 100644 --- a/packages/mistral/src/mistral-chat-options.ts +++ b/packages/mistral/src/mistral-chat-options.ts @@ -34,6 +34,20 @@ Defaults to `false`. documentImageLimit: z.number().optional(), documentPageLimit: z.number().optional(), + + /** + * Whether to use structured outputs. + * + * @default true + */ + structuredOutputs: z.boolean().optional(), + + /** + * Whether to use strict JSON schema validation. + * + * @default false + */ + strictJsonSchema: z.boolean().optional(), }); export type MistralProviderOptions = z.infer;