microsoft/semantic-kernel · error · ArgumentException

Role must be one of: system, user, assistant or tool. {role}

Error message

Role must be one of: system, user, assistant or tool. {role} is an invalid role.

What it means

Thrown by the MistralChatMessage JSON constructor when the supplied role is non-null but not one of the allowed values: system, user, assistant, or tool. The constructor validates the role because the Mistral chat completions API only accepts those four roles. The offending value is interpolated into the message and reported as an ArgumentException on the 'role' parameter.

Source

Thrown at dotnet/src/Connectors/Connectors.MistralAI/Client/MistralChatMessage.cs:43

    [JsonPropertyName("tool_call_id")]
    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
    public string? ToolCallId { get; set; }

    [JsonPropertyName("tool_calls")]
    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
    public IList<MistralToolCall>? ToolCalls { get; set; }

    /// <summary>
    /// Construct an instance of <see cref="MistralChatMessage"/>.
    /// </summary>
    /// <param name="role">If provided must be one of: system, user, assistant</param>
    /// <param name="content">Content of the chat message</param>
    [JsonConstructor]
    internal MistralChatMessage(string? role, object? content)
    {
        if (role is not null and not "system" and not "user" and not "assistant" and not "tool")
        {
            throw new System.ArgumentException($"Role must be one of: system, user, assistant or tool. {role} is an invalid role.", nameof(role));
        }

        this.Role = role;
        this.Content = content;
    }
}

View on GitHub (pinned to c028a0c7dc)

Solutions

  1. Map foreign roles to Mistral roles before constructing: 'model'/'chatbot' -> 'assistant', 'function' -> 'tool', 'developer'/'system' -> 'system'.
  2. Ensure deserialized chat history uses only system/user/assistant/tool roles.
  3. If building messages manually, pass role as null or one of the four valid strings.

Example fix

// before
var msg = new MistralChatMessage(role: "model", content: "hi");  // throws

// after
var msg = new MistralChatMessage(role: "assistant", content: "hi");
Defensive patterns

Strategy: type-guard

Validate before calling

static readonly HashSet<string> ValidRoles = new() { "system", "user", "assistant", "tool" };
static string NormalizeRole(string? r) => r switch
{
    null => "user",
    "model" or "chatbot" or "bot" => "assistant",
    "function" => "tool",
    "developer" => "system",
    _ => ValidRoles.Contains(r) ? r : "user"
};

Type guard

static bool IsValidMistralRole(string? r)
    => r is null || r is "system" or "user" or "assistant" or "tool";

Try / catch

try { var msg = new MistralChatMessage(role, content); }
catch (ArgumentException ex) when (ex.Message.Contains("is an invalid role"))
{ logger.LogError(ex, "Translate the role to one of system/user/assistant/tool."); throw; }

Prevention

When it happens

Trigger: Deserializing a MistralChatMessage from JSON whose role is e.g. 'function', 'developer', 'model', 'chatbot', or any custom string; or constructing a MistralChatMessage manually with an unsupported role.

Common situations: Mapping chat histories from another provider (OpenAI 'function'/'tool', Gemini 'model', custom 'bot') into Mistral messages without translating the role; schema drift where a new role string appears; typos in role strings.

Related errors


AI-assisted analysis of microsoft/semantic-kernel@c028a0c7dc (2026-08-13). Data as JSON: /api/errors/10629cf463d46637. Report an issue: GitHub.