The Mistral adapter calls Mistral Large, Mistral Medium, Magistral, and Codestral. It also creates embeddings.
npm i @tanstack/ai-mistralpnpm add @tanstack/ai-mistralyarn add @tanstack/ai-mistralbun add @tanstack/ai-mistralGet a key from the Mistral Console. Put it in the environment of your server:
MISTRAL_API_KEY=...mistralText and mistralEmbedding read MISTRAL_API_KEY. To pass the key in code, use createMistralText:
import { createMistralText } from "@tanstack/ai-mistral";
const adapter = createMistralText("mistral-large-latest", process.env.MISTRAL_API_KEY!);import {
chat,
chatParamsFromRequest,
toServerSentEventsResponse,
} from "@tanstack/ai";
import { mistralText } from "@tanstack/ai-mistral";
export async function POST(request: Request) {
const { messages, threadId, runId } = await chatParamsFromRequest(request);
const stream = chat({
adapter: mistralText("mistral-large-latest"),
messages,
threadId,
runId,
});
return toServerSentEventsResponse(stream);
}The client is the same for every provider. Point useChat at your route:
import { useState } from "react";
import { useChat, fetchServerSentEvents } from "@tanstack/ai-react";
export function Chat() {
const [input, setInput] = useState("");
const { messages, sendMessage } = useChat({
connection: fetchServerSentEvents("/api/chat"),
threadId: "support-chat",
});
return (
<form
onSubmit={(event) => {
event.preventDefault();
sendMessage(input);
setInput("");
}}
>
{messages.map((message) => (
<p key={message.id}>
{message.parts
.map((part) => (part.type === "text" ? part.content : ""))
.join("")}
</p>
))}
<input value={input} onChange={(event) => setInput(event.target.value)} />
</form>
);
}For Vue, Solid, Svelte, Angular, and the other frameworks, see Quick Start.
You now have a streaming Mistral chat. Tools work the same as with every adapter. Mistral has no provider tools of its own, so define your tools with toolDefinition().
Send an image with the text to a vision model: mistral-medium-latest or mistral-small-latest.
import { chat } from "@tanstack/ai";
import { mistralText } from "@tanstack/ai-mistral";
const stream = chat({
adapter: mistralText("mistral-medium-latest"),
messages: [
{
role: "user",
content: [
{ type: "text", content: "What's in this image?" },
{
type: "image",
source: {
type: "url",
value: "https://example.com/photo.jpg",
},
},
],
},
],
threadId: "thread-1",
});For base64 data, set source.type to "data" and add mimeType:
import { chat } from "@tanstack/ai";
import { mistralText } from "@tanstack/ai-mistral";
const base64String = "..."; // your base64-encoded image bytes
const stream = chat({
adapter: mistralText("mistral-medium-latest"),
messages: [
{
role: "user",
content: [
{ type: "text", content: "What's in this image?" },
{
type: "image",
source: {
type: "data",
mimeType: "image/png",
value: base64String,
},
},
],
},
],
threadId: "thread-1",
});See Multimodal Content for the full shape of a content part.
Get JSON that matches a Zod schema. The adapter uses the json_schema response format of Mistral:
import { chat } from "@tanstack/ai";
import { mistralText } from "@tanstack/ai-mistral";
import { z } from "zod";
const recipeSchema = z.object({
name: z.string(),
ingredients: z.array(z.string()),
steps: z.array(z.string()),
});
const recipe = await chat({
adapter: mistralText("mistral-large-latest"),
messages: [
{ role: "user", content: "Give me a chocolate chip cookie recipe." },
],
threadId: "thread-1",
outputSchema: recipeSchema,
});
console.log(recipe.name); // typed as z.infer<typeof recipeSchema>See Structured Outputs.
import { embed } from "@tanstack/ai";
import { mistralEmbedding } from "@tanstack/ai-mistral";
const result = await embed({
adapter: mistralEmbedding("mistral-embed"),
input: ["a red guitar", "a blue drum kit"],
});
console.log(result.embeddings[0]?.vector); // 1024 dimensionsmistral-embed always gives 1024 dimensions. codestral-embed is tuned for code, and it takes the top-level dimensions option:
import { embed } from "@tanstack/ai";
import { mistralEmbedding } from "@tanstack/ai-mistral";
const result = await embed({
adapter: mistralEmbedding("codestral-embed"),
input: "function add(a, b) { return a + b }",
dimensions: 512,
});See the Embeddings guide.
Provider options go in modelOptions, with the snake_case names of Mistral. This includes the sampling options temperature, top_p, and max_tokens:
import { chat } from "@tanstack/ai";
import { mistralText } from "@tanstack/ai-mistral";
const stream = chat({
adapter: mistralText("mistral-large-latest"),
messages: [{ role: "user", content: "Hello!" }],
threadId: "thread-1",
modelOptions: {
temperature: 0.7,
top_p: 0.9,
max_tokens: 1024,
random_seed: 42,
stop: ["END"],
safe_prompt: true,
frequency_penalty: 0.5,
presence_penalty: 0.5,
parallel_tool_calls: true,
tool_choice: "auto",
},
});Sampling options do not go at the root of chat(). See Moving Sampling Options into modelOptions.
Set reasoning on chat() to turn thinking up, down, or off:
import { chat } from "@tanstack/ai";
import { mistralText } from "@tanstack/ai-mistral";
const stream = chat({
adapter: mistralText("mistral-medium-latest"),
messages: [{ role: "user", content: "Why is the sky blue?" }],
threadId: "thread-1",
reasoning: "high",
});What goes to Mistral:
The other models take no reasoning. The Magistral models stream their thinking before the answer. See Magistral thinking events.
| Group | Models |
|---|---|
| Chat | mistral-large-latest (flagship, 128k context), mistral-medium-latest (with vision), mistral-small-latest (fast, with vision), ministral-8b-latest, ministral-3b-latest |
| Code | codestral-latest (256k context) |
| Reasoning | magistral-medium-latest, magistral-small-latest |
| Embeddings | mistral-embed, codestral-embed |
| Chat on Vertex | mistral-medium-3, mistral-small-2503, codestral-2 |
See the model comparison of Mistral for the full details. adapter.inputModalities lists the input kinds of the selected model. See Check what a model accepts.
import {
createMistralText,
type MistralTextConfig,
} from "@tanstack/ai-mistral";
const config: Omit<MistralTextConfig, "apiKey"> = {
baseURL: "https://api.mistral.ai", // Optional, this is the default
defaultHeaders: {
"X-Custom-Header": "value",
},
};
const adapter = createMistralText(
"mistral-large-latest",
process.env.MISTRAL_API_KEY!,
config,
);Send every request through a gateway, such as Cloudflare AI Gateway or a corporate proxy, with baseURL and defaultHeaders. These two option names are the same on every TanStack AI adapter, so one gateway config works for all of them.
import { createMistralText } from "@tanstack/ai-mistral";
const adapter = createMistralText("mistral-large-latest", process.env.MISTRAL_API_KEY!, {
baseURL: "https://gateway.example.com/mistral",
defaultHeaders: { "cf-aig-authorization": `Bearer ${process.env.GATEWAY_TOKEN}` },
});Mistral caches the start of your requests by itself.
See Prompt Caching.
The Magistral models (magistral-medium-latest, magistral-small-latest) stream their thinking as separate events before the answer. The adapter sends AG-UI REASONING_* chunks for the thinking and TEXT_MESSAGE_* chunks for the answer:
// ignore: narrowing the raw AG-UI stream by `chunk.type` relies on @ag-ui/core's
// discriminated-union `type` field, which kiira can't resolve in a source-only
// check. The runtime behaviour is exactly as shown.
import { chat } from "@tanstack/ai";
import { mistralText } from "@tanstack/ai-mistral";
const stream = chat({
adapter: mistralText("magistral-medium-latest"),
messages: [{ role: "user", content: "Why is the sky blue?" }],
threadId: "thread-1",
});
for await (const chunk of stream) {
if (chunk.type === "REASONING_MESSAGE_CONTENT") {
process.stdout.write(`[thinking] ${chunk.delta}`);
} else if (chunk.type === "TEXT_MESSAGE_CONTENT") {
process.stdout.write(chunk.delta);
}
}The reasoning events always close before any text or tool output starts. So you first see the full sequence: REASONING_START, REASONING_MESSAGE_START, REASONING_MESSAGE_CONTENT (one or more), REASONING_MESSAGE_END, REASONING_END.
See Thinking & Reasoning for the event spec of all providers.
Use @tanstack/ai-mistral/vertex when Mistral must run on Vertex AI. That path uses Google Cloud credentials and the publisher rawPredict endpoint.
Mistral on Vertex is regional only. Use us-central1 or europe-west4.
npm i @tanstack/ai-mistral google-auth-librarypnpm add @tanstack/ai-mistral google-auth-libraryyarn add @tanstack/ai-mistral google-auth-librarybun add @tanstack/ai-mistral google-auth-libraryimport { chat } from "@tanstack/ai";
import { mistralVertexText } from "@tanstack/ai-mistral/vertex";
const stream = chat({
adapter: mistralVertexText("mistral-medium-3", {
project: "my-project",
location: "europe-west4",
}),
messages: [{ role: "user", content: "Hello!" }],
threadId: "thread-1",
});Gemini on Vertex is in @tanstack/ai-vertex.
| Factory | Creates |
|---|---|
| mistralText(model, config?) | Text adapter. Reads MISTRAL_API_KEY |
| createMistralText(model, apiKey, config?) | Text adapter with an explicit API key |
| mistralEmbedding(model, config?) / createMistralEmbedding(model, apiKey, config?) | Embedding adapter |
| mistralVertexText(model, config) | Text adapter on Vertex. Import from @tanstack/ai-mistral/vertex |
adapter.inputModalities lists the input kinds of the selected model. It is undefined for a model that this package does not know.