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
- Map tool/function messages to User or Assistant text messages before building the prompt.
- Filter out messages with unsupported roles from the sequence.
- Ensure each message's Role is explicitly set to Role.System, Role.User, or Role.Assistant.
- 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
- Map Tool/Function roles to User or Assistant before prompt building.
- Always set Role explicitly when constructing messages.
- Never pass raw role strings from other frameworks; convert to the library's Role type.
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
- Please provide a message with content.
- Invalid role.
- Unsupported role {message.Role}
- Expected either {0} or {1} to be provided
- {nameof(numberOfCVFolds)} must be at least 2
AI-assisted analysis of dotnet/machinelearning@7b76e69cf9 (2026-09-11).
Data as JSON: /api/errors/45ce31f45d2b7960.
Report an issue: GitHub.