dotnet/machinelearning · error · NotSupportedException
Unsupported role {message.Role}
Error message
Unsupported role {message.Role} What it means
In the ChatMessage overload of BuildPrompt, each item's prompt is selected by the message's AuthorRole. If the role is not System, User, or Assistant, the discard arm throws NotSupportedException('Unsupported role ...'). The trailing newline interpolates the actual role value into the message.
Source
Thrown at src/Microsoft.ML.GenAI.Phi/Phi3/Phi3ChatTemplateBuilder.cs:80
{
// build prompt from chat history
var sb = new StringBuilder();
foreach (var message in chatHistory)
{
foreach (var item in message.Items)
{
if (item is not TextContent textContent)
{
throw new NotSupportedException($"Only text content is supported, but got {item.GetType().Name}");
}
var prompt = message.Role switch
{
_ when message.Role == AuthorRole.System => $"<|system|>{Newline}{textContent}<|end|>{Newline}",
_ when message.Role == AuthorRole.User => $"<|user|>{Newline}{textContent}<|end|>{Newline}",
_ when message.Role == AuthorRole.Assistant => $"<|assistant|>{Newline}{textContent}<|end|>{Newline}",
_ => throw new NotSupportedException($"Unsupported role {message.Role}")
};
sb.Append(prompt);
}
}
sb.Append("<|assistant|>");
return sb.ToString();
}
public string BuildPrompt(IEnumerable<ChatMessage> messages, ChatOptions? options = null, bool appendAssistantTag = true)
{
var availableRoles = new[] { ChatRole.System, ChatRole.User, ChatRole.Assistant };
if (messages.Any(m => m.Text is null))
{
throw new InvalidOperationException("Please provide a message with content.");
}View on GitHub (pinned to 7b76e69cf9)
Solutions
- Map unsupported roles onto User or Assistant before building the prompt.
- Filter messages to the three supported roles.
- Serialize tool results as assistant/user text messages.
Example fix
// before var prompt = builder.BuildPrompt(chatHistory); // contains Tool role // after var normalized = chatHistory.Select(m => m.Role == AuthorRole.Tool ? new ChatMessage(AuthorRole.User, m.Content) : m); var prompt = builder.BuildPrompt(normalized);
Defensive patterns
Strategy: validation
Validate before calling
var supported = new[] { AuthorRole.System, AuthorRole.User, AuthorRole.Assistant };
if (chatHistory.Any(m => !supported.Contains(m.Role)))
throw new InvalidOperationException($"Unsupported role in history: {chatHistory.First(m => !supported.Contains(m.Role)).Role}"); Type guard
bool HasSupportedRole(ChatMessage m) => m.Role == AuthorRole.System || m.Role == AuthorRole.User || m.Role == AuthorRole.Assistant;
Try / catch
try { var prompt = builder.BuildPrompt(chatHistory); } catch (NotSupportedException ex) when (ex.Message.StartsWith("Unsupported role")) { chatHistory = chatHistory.Where(HasSupportedRole).ToList(); prompt = builder.BuildPrompt(chatHistory); } Prevention
- Map tool/custom roles to User or Assistant at history-ingestion time.
- Restrict role creation to the three supported AuthorRole values.
- Log and drop unsupported-role messages instead of passing them through.
When it happens
Trigger: Calling BuildPrompt(IEnumerable<ChatMessage>) with a message whose AuthorRole is Tool, Function, or any custom role string outside the three supported Phi-3 roles.
Common situations: Tool-result messages in the chat history; custom roles defined by the host application; histories exported from frameworks with richer role vocabularies.
Related errors
- Please provide a message with a valid role. The valid roles
- Invalid 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/d4e729a7256ff551.
Report an issue: GitHub.