dotnet/machinelearning · error · InvalidOperationException

Invalid role.

Error message

Invalid role.

What it means

As a final defensive switch arm in the IMessage overload of BuildPrompt, any message whose role is not System, User, or Assistant hits the discard case and throws InvalidOperationException('Invalid role.'). This normally cannot be reached because the role check earlier in the method already filters messages.

Source

Thrown at src/Microsoft.ML.GenAI.Phi/Phi3/Phi3ChatTemplateBuilder.cs:51

        if (messages.Any(m => m.GetRole() is null || availableRoles.Contains(m.GetRole()!.Value) == false))
        {
            throw new InvalidOperationException("Please provide a message with a valid role. The valid roles are System, User, and Assistant.");
        }

        // construct template based on instruction from
        // https://huggingface.co/microsoft/Phi-3-mini-128k-instruct#chat-format

        var sb = new StringBuilder();
        foreach (var message in messages)
        {
            var role = message.GetRole()!.Value;
            var content = message.GetContent()!;
            sb.Append(message switch
            {
                _ when message.GetRole() == Role.System => $"<|system|>{Newline}{content}<|end|>{Newline}",
                _ when message.GetRole() == Role.User => $"<|user|>{Newline}{content}<|end|>{Newline}",
                _ when message.GetRole() == Role.Assistant => $"<|assistant|>{Newline}{content}<|end|>{Newline}",
                _ => throw new InvalidOperationException("Invalid role.")
            });
        }

        sb.Append("<|assistant|>");
        var input = sb.ToString();

        return input;
    }

    public string BuildPrompt(ChatHistory chatHistory)
    {
        // build prompt from chat history
        var sb = new StringBuilder();

        foreach (var message in chatHistory)
        {
            foreach (var item in message.Items)
            {

View on GitHub (pinned to 7b76e69cf9)

Solutions

  1. Ensure messages are immutable/not mutated between validation and prompt building.
  2. Sanitize roles to System/User/Assistant before calling BuildPrompt.
  3. If you can hit this consistently, report it — the earlier validation should have caught the bad role.
Defensive patterns

Strategy: validation

Validate before calling

// Reuse the same role check the builder performs internally
if (messages.Any(m => m.GetRole() is not (Role.System or Role.User or Role.Assistant)))
    throw new InvalidOperationException("Unsupported role detected.");

Type guard

bool IsPhi3Role(IMessage m) => m.GetRole() is Role.System or Role.User or Role.Assistant;

Try / catch

try { var prompt = builder.BuildPrompt(messages); } catch (InvalidOperationException ex) when (ex.Message == "Invalid role.") { logger.LogError(ex, "Role changed between validation and rendering"); throw; }

Prevention

When it happens

Trigger: Only reachable if the earlier role-validation Any(...) check passed but the per-message switch still encounters an unexpected role — practically it guards against internal inconsistency or race-modified message state.

Common situations: Rare in practice; may indicate custom Role values that compare inconsistently, or mutable messages whose role changed between validation and rendering.

Understand the failure class

Background: Invalid enum value errors: "Unknown type", "Invalid scope", "must be one of" — when a string is not on the library's allowed list — this error's family across 23 libraries.

Related errors


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