Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/new-apples-raise.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@ai-sdk/mistral': patch
---

feat(provider/mistral): `response_format.type === 'json_schema'`
66 changes: 66 additions & 0 deletions content/providers/01-ai-sdk-providers/20-mistral.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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.',
});
```

<Note>
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.
</Note>

### Model Capabilities

| Model | Image Input | Object Generation | Tool Usage | Tool Streaming |
Expand Down
8 changes: 8 additions & 0 deletions examples/ai-core/src/generate-object/mistral.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down
21 changes: 14 additions & 7 deletions packages/mistral/src/mistral-chat-language-model.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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": [
{
Expand All @@ -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,
Expand Down
31 changes: 16 additions & 15 deletions packages/mistral/src/mistral-chat-language-model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Copy link
Contributor

@vercel vercel bot Aug 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (responseFormat?.type === 'json' && !responseFormat?.schema) {
if (responseFormat?.type === 'json' && (!responseFormat?.schema || !structuredOutputs)) {

When structuredOutputs is false but a JSON schema is provided, the model won't receive any schema information, causing it to generate unstructured JSON instead of following the schema.

View Details

Analysis

The logic for JSON instruction injection has a bug when structuredOutputs is disabled but a JSON response format with schema is requested. Currently, the code only injects JSON instructions when there's no schema (!responseFormat?.schema on line 118), but when structuredOutputs is false, the code falls back to using { type: 'json_object' } format without the schema information (line 151).

This means when:

  1. responseFormat.type === 'json'
  2. responseFormat.schema exists
  3. structuredOutputs === false

The condition on line 118 evaluates to false (because schema exists), so no JSON instructions are injected, and the condition on line 141 also evaluates to false (because structuredOutputs is false), so it uses json_object format without schema information. The model receives no guidance about the expected JSON structure, likely producing unstructured output instead of following the provided schema.


Recommendation

Update the condition on line 118 to inject JSON instructions when either there's no schema OR when there's a schema but structuredOutputs is disabled:

if (responseFormat?.type === 'json' && (!responseFormat?.schema || !structuredOutputs)) {

This ensures that JSON instructions with schema information are injected when structuredOutputs is disabled, providing the model with the necessary schema guidance through the fallback instruction method.

prompt = injectJsonInstructionIntoMessages({
messages: prompt,
schema: responseFormat.schema,
Expand All @@ -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,
Expand Down
14 changes: 14 additions & 0 deletions packages/mistral/src/mistral-chat-options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<typeof mistralProviderOptions>;
Loading