Skip to content
Adapters

Mistral

The Mistral adapter calls Mistral Large, Pixtral, Magistral, and Codestral. It also creates embeddings.

1. Install

shell
npm i @tanstack/ai-mistral

2. Set the API key

Get a key from the Mistral Console. Put it in the environment of your server:

shell
MISTRAL_API_KEY=...

mistralText and mistralEmbedding read MISTRAL_API_KEY. To pass the key in code, use createMistralText:

ts
import { createMistralText } from "@tanstack/ai-mistral";

const adapter = createMistralText("mistral-large-latest", process.env.MISTRAL_API_KEY!);

3. Stream a chat from the server

ts
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);
}

4. Render the chat

The client is the same for every provider. Point useChat at your route:

tsx
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().

Images in a message

Send an image with the text to a vision model: pixtral-large-latest, pixtral-12b-2409, mistral-medium-latest, or mistral-small-latest.

ts
import { chat } from "@tanstack/ai";
import { mistralText } from "@tanstack/ai-mistral";

const stream = chat({
  adapter: mistralText("pixtral-large-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:

ts
import { chat } from "@tanstack/ai";
import { mistralText } from "@tanstack/ai-mistral";

const base64String = "..."; // your base64-encoded image bytes

const stream = chat({
  adapter: mistralText("pixtral-large-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.

Structured output

Get JSON that matches a Zod schema. The adapter uses the json_schema response format of Mistral:

ts
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.

Embeddings

ts
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 dimensions

mistral-embed always gives 1024 dimensions. codestral-embed is tuned for code, and it takes the top-level dimensions option:

ts
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.

What Mistral does not do

Models and options

Model options

Provider options go in modelOptions, with the snake_case names of Mistral. This includes the sampling options temperature, top_p, and max_tokens:

ts
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.

Reasoning

Set reasoning on chat() to turn thinking up, down, or off:

ts
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:

  • mistral-medium-latest, mistral-small-latest: reasoning_effort. They take off (sent as none) and high.
  • magistral-medium-latest, magistral-small-latest: they take off and high. high sends prompt_mode: "reasoning". off sends nothing.

The other models take no reasoning. The Magistral models stream their thinking before the answer. See Magistral thinking events.

Models

GroupModels
Chatmistral-large-latest (flagship, 128k context), mistral-medium-latest (with vision), mistral-small-latest (fast, with vision), ministral-8b-latest, ministral-3b-latest, open-mistral-nemo
Codecodestral-latest (256k context)
Visionpixtral-large-latest, pixtral-12b-2409
Reasoningmagistral-medium-latest, magistral-small-latest
Embeddingsmistral-embed, codestral-embed
Chat on Vertexmistral-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.

Advanced

Configuration

ts
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,
);

Behind a proxy

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.

ts
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}` },
});
  • serverURL is an alias of baseURL. If you set both, baseURL wins.
  • Need more than a fixed header, such as a log of each model call? This adapter supports wrapFetch.

Prompt caching

Mistral caches the start of your requests by itself.

  • chat() sends prompt_cache_key by default. The key is the threadId that you pass, so one conversation shares a cache.
  • The key goes only to api.mistral.ai, on chat() and on structured output.
  • With promptCache: 'none', no key goes out.
  • Cache reads show up in usage.promptTokensDetails.cachedTokens.

See Prompt Caching.

Magistral thinking events

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:

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

Mistral on Vertex

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.

shell
npm i @tanstack/ai-mistral google-auth-library
ts
import { 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",
});
  • project and location use the same names as @tanstack/ai-vertex. location is required.
  • mistralVertexText accepts only the Mistral chat models that Vertex lists: mistral-medium-3, mistral-small-2503, and codestral-2.
  • Mistral API aliases such as mistral-large-latest and mistral-medium-latest are not Vertex model ids. Vertex also lists mistral-ocr-2505, but that model is OCR, not chat.
  • Install google-auth-library for Application Default Credentials, or pass authClient or getAccessToken.

Gemini on Vertex is in @tanstack/ai-vertex.

API reference

FactoryCreates
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
  • config.baseURL: a custom base URL. Optional. serverURL is an alias.
  • config.defaultHeaders: headers for every request. Optional.

adapter.inputModalities lists the input kinds of the selected model. It is undefined for a model that this package does not know.