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
- Ensure messages are immutable/not mutated between validation and prompt building.
- Sanitize roles to System/User/Assistant before calling BuildPrompt.
- 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
- Treat IMessage objects as immutable once queued.
- Avoid custom Role values when targeting Phi-3.
- Re-check roles right before building the prompt if history is shared across threads.
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
- Please provide a message with a valid role. The valid roles
- Unsupported role {message.Role}
- Please provide a message with content.
- Only text content is supported, but got {item.GetType().Name
- Invalid role.
AI-assisted analysis of dotnet/machinelearning@7b76e69cf9 (2026-09-11).
Data as JSON: /api/errors/560d2560f0fe8ff6.
Report an issue: GitHub.