Skip to content

Mistral

Nuget package dotnet License: MIT Discord

Features 🔥

  • Fully generated C# SDK based on official Mistral OpenAPI specification using AutoSDK
  • Same day update to support new features
  • Updated and supported automatically if there are no breaking changes
  • All modern .NET features - nullability, trimming, NativeAOT, etc.
  • Support .Net Framework/.Net Standard 2.0
  • Microsoft.Extensions.AI IChatClient support

Usage

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
using Mistral;

using var client = new MistralClient(apiKey);

ChatCompletionResponse response = await client.Agents.AgentsCompletionAsync(
    agentId: "Test",
    messages: new List<OneOf<UserMessage, AssistantMessage, ToolMessage>>
    {
        new UserMessage
        {
            Content = "Hello",
        },
    });

Microsoft.Extensions.AI

The SDK implements IChatClient for seamless integration with the .NET AI ecosystem:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
using Mistral;
using Meai = Microsoft.Extensions.AI;

Meai.IChatClient chatClient = new MistralClient(apiKey);

var response = await chatClient.GetResponseAsync(
    [new Meai.ChatMessage(Meai.ChatRole.User, "Hello!")],
    new Meai.ChatOptions { ModelId = "mistral-large-latest" });

Console.WriteLine(response.Text);

Note: Use the Meai alias because the Mistral SDK has its own generated IChatClient interface.

Chat Client Five Random Words Streaming

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
using var client = new MistralClient(apiKey);

Meai.IChatClient chatClient = client;
var updates = chatClient.GetStreamingResponseAsync(
    [
        new Meai.ChatMessage(Meai.ChatRole.User, "Generate 5 random words.")
    ],
    new Meai.ChatOptions
    {
        ModelId = "mistral-small-latest",
    });

var deltas = new List<string>();
await foreach (var update in updates)
{
    if (!string.IsNullOrWhiteSpace(update.Text))
    {
        deltas.Add(update.Text);
    }
}

Chat Client Five Random Words

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
using var client = new MistralClient(apiKey);

Meai.IChatClient chatClient = client;
var response = await chatClient.GetResponseAsync(
    [
        new Meai.ChatMessage(Meai.ChatRole.User, "Generate 5 random words.")
    ],
    new Meai.ChatOptions
    {
        ModelId = "mistral-small-latest",
    });

Chat Client Get Service Returns Chat Client Metadata

1
2
3
4
using var client = CreateTestClient();
Meai.IChatClient chatClient = client;

var metadata = Meai.ChatClientExtensions.GetService<Meai.ChatClientMetadata>(chatClient);

Voxtral MEAI ISpeechToTextClient

MistralClient implements Microsoft.Extensions.AI.ISpeechToTextClient, backed by Voxtral. GetTextAsync POSTs to /v1/audio/transcriptions (default model: voxtral-mini-2507). GetStreamingTextAsync opens a WebSocket to /v1/audio/transcriptions/realtime (default model: voxtral-mini-transcribe-realtime-2602) and yields interim + final updates.

1
2
3
4
5
6
using var client = CreateTestClient();

Meai.ISpeechToTextClient speechClient = client;

var metadata = speechClient.GetService(typeof(Meai.SpeechToTextClientMetadata))
    as Meai.SpeechToTextClientMetadata;

Voxtral round-trip (TTS → Voxtral STT)

Exercises the Voxtral batch transcription endpoint end-to-end by: 1. Using Mistral TTS (/v1/audio/speech) to synthesize a short utterance. 2. Feeding the resulting audio bytes into Meai.ISpeechToTextClient.GetTextAsync (default model: voxtral-mini-2507). 3. Asserting the returned text is non-empty.

Skips when MISTRAL_API_KEY is unset or the account has no voices available.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
using var client = new MistralClient(apiKey);

VoiceListResponse voices;
try
{
    voices = await client.AudioVoices.ListAllVoicesAsync();
}
catch (ApiException ex)
{
    throw new AssertInconclusiveException(
        $"Mistral voice listing unavailable (HTTP {(int?)ex.StatusCode}); skipping live STT round-trip.",
        ex);
}

if (voices.Items is not { Count: > 0 } items)
{
    throw new AssertInconclusiveException(
        "No voices available on this Mistral account; skipping live STT round-trip.");
}

var voice = items[0];

SpeechResponse speech;
try
{
    speech = await client.AudioSpeech.SpeechAsync(new SpeechRequest
    {
        Input = "Hello from Voxtral.",
        VoiceId = voice.Id.ToString(),
        ResponseFormat = SpeechOutputFormat.Wav,
    });
}
catch (ApiException ex)
{
    throw new AssertInconclusiveException(
        $"Mistral TTS unavailable for this account (HTTP {(int?)ex.StatusCode}); skipping live STT round-trip.",
        ex);
}

var audioBytes = Convert.FromBase64String(speech.AudioData);

Meai.ISpeechToTextClient speechClient = client;
using var audioStream = new MemoryStream(audioBytes);
var response = await speechClient.GetTextAsync(audioStream);

Voxtral realtime streaming transcription

Opens a Voxtral realtime WebSocket (wss://api.mistral.ai/v1/audio/transcriptions/realtime) via Meai.ISpeechToTextClient.GetStreamingTextAsync, streams ~1 s of synthesized PCM 16-bit LE / 16 kHz silence to drive the session, and verifies at least a SessionOpen + SessionClose update pair. A TranscriptionDone update with non-empty text is asserted only when the server returns one — silent input does not reliably trigger a transcription on Voxtral.

Skips when MISTRAL_API_KEY is unset.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
using var client = new MistralClient(apiKey);

// 1 second of PCM 16-bit LE / 16 kHz silence (~16 000 samples × 2 B/sample).
var pcmSilence = new byte[16000 * 2];
using var audio = new MemoryStream(pcmSilence);

Meai.ISpeechToTextClient speechClient = client;

bool sessionOpened = false;
bool sessionClosed = false;
Meai.SpeechToTextResponseUpdate? finalUpdate = null;

using var timeoutCts = new CancellationTokenSource(TimeSpan.FromSeconds(45));
try
{
    await foreach (var update in speechClient.GetStreamingTextAsync(audio, cancellationToken: timeoutCts.Token))
    {
        if (update.Kind == Meai.SpeechToTextResponseUpdateKind.SessionOpen)
        {
            sessionOpened = true;
        }
        else if (update.Kind == Meai.SpeechToTextResponseUpdateKind.SessionClose)
        {
            sessionClosed = true;
        }
        else if (update.Kind == Meai.SpeechToTextResponseUpdateKind.TextUpdated)
        {
            finalUpdate = update;
        }
    }
}
catch (System.Net.WebSockets.WebSocketException ex)
{
    throw new AssertInconclusiveException(
        $"Voxtral realtime endpoint unavailable (WebSocket error: {ex.Message}); skipping live streaming test.",
        ex);
}

// finalUpdate may be null when silent input doesn't trigger transcription —
// that's expected and not a failure of the streaming plumbing itself.
if (finalUpdate is not null)
{
}

Voxtral chat with audio input

Exercises the audio-input branch of MistralClient.IChatClient end-to-end by: 1. Using Mistral TTS (/v1/audio/speech) to synthesize a short utterance. 2. Sending that audio to a Voxtral chat model (default voxtral-small-latest) as a Meai.DataContent with media type audio/wav, alongside a text prompt asking the model to repeat what it heard. 3. Asserting the model returns text that mentions the original utterance.

Skips when MISTRAL_API_KEY is unset, the account has no voices, the TTS endpoint refuses the request, or the Voxtral chat model isn't enabled for this account.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
using var client = new MistralClient(apiKey);

VoiceListResponse voices;
try
{
    voices = await client.AudioVoices.ListAllVoicesAsync();
}
catch (ApiException ex)
{
    throw new AssertInconclusiveException(
        $"Mistral voice listing unavailable (HTTP {(int?)ex.StatusCode}); skipping live chat-with-audio test.",
        ex);
}

if (voices.Items is not { Count: > 0 } items)
{
    throw new AssertInconclusiveException(
        "No voices available on this Mistral account; skipping live chat-with-audio test.");
}

const string utterance = "The quick brown fox jumps over the lazy dog.";

SpeechResponse speech;
try
{
    speech = await client.AudioSpeech.SpeechAsync(new SpeechRequest
    {
        Input = utterance,
        VoiceId = items[0].Id.ToString(),
        ResponseFormat = SpeechOutputFormat.Wav,
    });
}
catch (ApiException ex)
{
    throw new AssertInconclusiveException(
        $"Mistral TTS unavailable for this account (HTTP {(int?)ex.StatusCode}); skipping live chat-with-audio test.",
        ex);
}

var audioBytes = Convert.FromBase64String(speech.AudioData);

Meai.IChatClient chatClient = client;
Meai.ChatResponse response;
try
{
    response = await chatClient.GetResponseAsync(
        [
            new Meai.ChatMessage(Meai.ChatRole.User,
            [
                new Meai.TextContent("Repeat exactly what the speaker said in the audio, with no extra commentary."),
                new Meai.DataContent(audioBytes, mediaType: "audio/wav"),
            ]),
        ],
        new Meai.ChatOptions
        {
            ModelId = VoxtralChatModelId,
        });
}
catch (ApiException ex) when (ex.StatusCode is System.Net.HttpStatusCode.NotFound
                                  or System.Net.HttpStatusCode.Forbidden
                                  or System.Net.HttpStatusCode.BadRequest)
{
    throw new AssertInconclusiveException(
        $"Voxtral chat model '{VoxtralChatModelId}' not enabled for this account (HTTP {(int?)ex.StatusCode}); skipping live chat-with-audio test.",
        ex);
}

Chat Client Get Service Returns Null For Unknown Key

1
2
3
4
using var client = CreateTestClient();
Meai.IChatClient chatClient = client;

var result = Meai.ChatClientExtensions.GetService<Meai.ChatClientMetadata>(chatClient, serviceKey: "unknown");

Chat Client Get Service Returns Self

1
2
3
4
using var client = CreateTestClient();
Meai.IChatClient chatClient = client;

var self = Meai.ChatClientExtensions.GetService<MistralClient>(chatClient);

Chat Client Tool Calling Multi Turn

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
using var client = new MistralClient(apiKey);

var getWeatherTool = Meai.AIFunctionFactory.Create(
    (string location) => $"The weather in {location} is 72°F and sunny.",
    name: "get_weather",
    description: "Gets the current weather for a given location.");

Meai.IChatClient chatClient = client;
var messages = new List<Meai.ChatMessage>
{
    new(Meai.ChatRole.User, "What is the weather in Paris?"),
};

// First turn: model requests tool call
var response = await chatClient.GetResponseAsync(
    messages,
    new Meai.ChatOptions
    {
        ModelId = "mistral-small-latest",
        Tools = [getWeatherTool],
    });

var functionCall = response.Messages
    .SelectMany(m => m.Contents)
    .OfType<Meai.FunctionCallContent>()
    .FirstOrDefault();

// Add assistant message with function call and tool result
messages.AddRange(response.Messages);
var toolResult = await getWeatherTool.InvokeAsync(
    functionCall!.Arguments is { } args ? new Meai.AIFunctionArguments(args) : null);
messages.Add(new Meai.ChatMessage(Meai.ChatRole.Tool,
[
    new Meai.FunctionResultContent(functionCall.CallId, toolResult),
]));

// Second turn: model should produce a final text response
var finalResponse = await chatClient.GetResponseAsync(
    messages,
    new Meai.ChatOptions
    {
        ModelId = "mistral-small-latest",
        Tools = [getWeatherTool],
    });

Chat Client Tool Calling Single Turn

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
using var client = new MistralClient(apiKey);

var getWeatherTool = Meai.AIFunctionFactory.Create(
    (string location) => $"The weather in {location} is 72°F and sunny.",
    name: "get_weather",
    description: "Gets the current weather for a given location.");

Meai.IChatClient chatClient = client;
var response = await chatClient.GetResponseAsync(
    [
        new Meai.ChatMessage(Meai.ChatRole.User, "What is the weather in Paris?")
    ],
    new Meai.ChatOptions
    {
        ModelId = "mistral-small-latest",
        Tools = [getWeatherTool],
    });

var functionCall = response.Messages
    .SelectMany(m => m.Contents)
    .OfType<Meai.FunctionCallContent>()
    .FirstOrDefault();

Chat Client Tool Calling Streaming

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
using var client = new MistralClient(apiKey);

var getWeatherTool = Meai.AIFunctionFactory.Create(
    (string location) => $"The weather in {location} is 72°F and sunny.",
    name: "get_weather",
    description: "Gets the current weather for a given location.");

Meai.IChatClient chatClient = client;
var updates = chatClient.GetStreamingResponseAsync(
    [
        new Meai.ChatMessage(Meai.ChatRole.User, "What is the weather in Paris?")
    ],
    new Meai.ChatOptions
    {
        ModelId = "mistral-small-latest",
        Tools = [getWeatherTool],
    });

var functionCalls = new List<Meai.FunctionCallContent>();
await foreach (var update in updates)
{
    functionCalls.AddRange(update.Contents.OfType<Meai.FunctionCallContent>());
}

Test

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
using var client = new MistralClient(apiKey);

ChatCompletionResponse response = await client.Chat.ChatCompletionAsync(
    model: "mistral-small-latest",
    messages: new List<MessagesItem>
    {
        new UserMessage
        {
            Content = "Hello",
        },
    });

Support

Priority place for bugs: https://github.com/tryAGI/Mistral/issues
Priority place for ideas and general questions: https://github.com/tryAGI/Mistral/discussions
Discord: https://discord.gg/Ca2xhfBf3v

Acknowledgments

JetBrains logo

This project is supported by JetBrains through the Open Source Support Program.