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

  1. Map unsupported roles onto User or Assistant before building the prompt.
  2. Filter messages to the three supported roles.
  3. 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

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


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