dotnet/machinelearning · error · InvalidOperationException

Please provide at most one system message.

Error message

Please provide at most one system message.

What it means

Mistral_7B_0_3ChatTemplateBuilder.BuildPrompt throws InvalidOperationException because the Mistral 7B v0.3 chat template only allows a single system message in the conversation. Multiple system messages cannot be rendered into the template.

Source

Thrown at src/Microsoft.ML.GenAI.Mistral/Mistral_7B_0_3ChatTemplateBuilder.cs:32

namespace Microsoft.ML.GenAI.Mistral;

/// <summary>
/// the chat template builder for Mistral 7B v0.3
/// </summary>
#pragma warning disable MSML_GeneralName // This name should be PascalCased
public class Mistral_7B_0_3ChatTemplateBuilder : IChatTemplateBuilder
#pragma warning restore MSML_GeneralName // This name should be PascalCased
{
    private const string Newline = "\r\n";

    public static Mistral_7B_0_3ChatTemplateBuilder Instance { get; } = new Mistral_7B_0_3ChatTemplateBuilder();

    public string BuildPrompt(IEnumerable<IMessage> messages, IEnumerable<FunctionContract>? tools = null)
    {
        // can only contain at most one system message
        if (messages.Where(m => m.GetRole() == Role.System).Count() > 1)
        {
            throw new InvalidOperationException("Please provide at most one system message.");
        }

        var systemMessage = messages.FirstOrDefault(m => m.GetRole() == Role.System)?.GetContent();

        // split the messages into two sequences by the last user message
        // e.g [user, assistant, user, assistant, user] -> [[user, assistant, user, assistant], [user]]

        var firstSequence = messages.Take(messages.ToList().FindLastIndex(m => m.GetRole() == Role.User));
        var secondSequence = messages.Skip(messages.ToList().FindLastIndex(m => m.GetRole() == Role.User));

        var sb = new StringBuilder();
        foreach (var message in firstSequence)
        {
            // skip system
            if (message.GetRole() == Role.System)
            {
                continue;
            }

View on GitHub (pinned to 7b76e69cf9)

Solutions

  1. Merge all system content into a single system message before calling BuildPrompt.
  2. Downgrade subsequent system-role messages to user role (with a clear preamble) instead of Role.System.
  3. Sanitize/normalize the incoming chat history so only the first system message keeps the System role.

Example fix

// before
messages.Add(new Message(Role.System, "Be concise.")); // second system message
var prompt = builder.BuildPrompt(messages);
// after
var merged = string.Join("\n", systemMsgs.Select(m => m.GetContent()));
var prompt = builder.BuildPrompt(new[] { new Message(Role.System, merged) }.Concat(rest));
Defensive patterns

Strategy: validation

Validate before calling

int systemCount = messages.Count(m => m.GetRole() == Role.System);
if (systemCount > 1) throw new ArgumentException("Mistral template allows at most one system message.");

Try / catch

try { prompt = builder.BuildPrompt(messages, tools); } catch (InvalidOperationException ex) when (ex.Message.Contains("at most one system message")) { /* merge system messages and retry */ }

Prevention

When it happens

Trigger: Calling BuildPrompt with an IEnumerable<IMessage> containing two or more messages whose role is Role.System.

Common situations: Merging chat histories from multiple sources that each added their own system prompt; multi-agent pipelines appending a system message per turn; a tool layer injecting system-level instructions on top of the app's system prompt.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


AI-assisted analysis of dotnet/machinelearning@7b76e69cf9 (2026-09-11). Data as JSON: /api/errors/07ff4fb95a1f2d76. Report an issue: GitHub.