dotnet/machinelearning · error · InvalidOperationException

Please provide a message with a valid role. The valid roles

Error message

Please provide a message with a valid role. The valid roles are System, User, and Assistant.

What it means

The LLaMA 3.1 template only supports the roles System, User, and Assistant. BuildPrompt throws InvalidOperationException when a message's role is null or any other role (e.g. Tool/Function) is passed, because the LLaMA header format has no tag for such roles.

Source

Thrown at src/Microsoft.ML.GenAI.LLaMA/Llama3_1ChatTemplateBuilder.cs:30

namespace Microsoft.ML.GenAI.LLaMA;
#pragma warning disable MSML_GeneralName // This name should be PascalCased
public class Llama3_1ChatTemplateBuilder : IChatTemplateBuilder, IMEAIChatTemplateBuilder
#pragma warning restore MSML_GeneralName // This name should be PascalCased
{
    private const char Newline = '\n';

    public string BuildPrompt(IEnumerable<IMessage> messages, IEnumerable<FunctionContract>? tools = null)
    {
        var availableRoles = new[] { Role.System, Role.User, Role.Assistant };
        if (messages.Any(m => m.GetContent() is null))
        {
            throw new InvalidOperationException("Please provide a message with content.");
        }

        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://github.com/meta-llama/llama3/blob/11817d47e1ba7a4959b025eb1ca308572e0e3963/llama/generation.py#L280

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

View on GitHub (pinned to 7b76e69cf9)

Solutions

  1. Map tool/function messages to User or Assistant text messages before building the prompt.
  2. Filter out messages with unsupported roles from the sequence.
  3. Ensure each message's Role is explicitly set to Role.System, Role.User, or Role.Assistant.
  4. Translate third-party role enums to the library's Role type instead of passing raw values.

Example fix

// before
var prompt = builder.BuildPrompt(history); // history contains Role.Tool messages
// after
var mapped = history.Select(m => m.GetRole() == Role.Tool
    ? new TextMessage(Role.User, m.GetContent()!, from: "tool")
    : m);
var prompt = builder.BuildPrompt(mapped);
Defensive patterns

Strategy: validation

Validate before calling

var valid = new[] { Role.System, Role.User, Role.Assistant };
if (messages.Any(m => m.GetRole() is null || !valid.Contains(m.GetRole()!.Value)))
    throw new InvalidOperationException("Unsupported role in history for LLaMA template.");

Type guard

static bool HasValidRole(IMessage m) =>
    m.GetRole() is Role r && r is Role.System or Role.User or Role.Assistant;

Try / catch

try { prompt = builder.BuildPrompt(messages); }
catch (InvalidOperationException ex) when (ex.Message.Contains("valid role"))
{ prompt = builder.BuildPrompt(messages.Where(HasValidRole)); }

Prevention

When it happens

Trigger: Calling BuildPrompt(IEnumerable<IMessage>, ...) with messages whose GetRole() returns null or a role outside {System, User, Assistant} — typically tool/function-result messages or a custom role value.

Common situations: Including tool-call results in chat history meant for LLaMA prompting; using Role values from another framework's enum; appending system-like messages with a nonstandard role name.

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/45ce31f45d2b7960. Report an issue: GitHub.