dotnet/machinelearning · error · NotSupportedException

Unsupported role {message.Role}

Error message

Unsupported role {message.Role}

What it means

The LLaMA 3.1 template only renders messages authored as System, User, or Assistant. BuildPrompt throws NotSupportedException when a ChatMessage's Role is anything else (e.g. Tool), because the header format has no corresponding tag.

Source

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

        sb.Append("<|begin_of_text|>");
        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 text = textContent.Text?.Trim() ?? string.Empty;

                var prompt = message.Role switch
                {
                    _ when message.Role == AuthorRole.System => $"<|start_header_id|>system<|end_header_id|>{Newline}{text}<|eot_id|>{Newline}",
                    _ when message.Role == AuthorRole.User => $"<|start_header_id|>user<|end_header_id|>{Newline}{text}<|eot_id|>{Newline}",
                    _ when message.Role == AuthorRole.Assistant => $"<|start_header_id|>assistant<|end_header_id|>{Newline}{text}<|eot_id|>{Newline}",
                    _ => throw new NotSupportedException($"Unsupported role {message.Role}")
                };

                sb.Append(prompt);
            }
        }

        sb.Append($"<|start_header_id|>assistant<|end_header_id|>{Newline}");

        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. Convert Tool-role messages to User or Assistant text messages before prompt building.
  2. Remove Tool-role messages from the history passed to BuildPrompt.
  3. Explicitly set AuthorRole on every message to System, User, or Assistant.
  4. Merge tool output into the preceding assistant or a new user message.

Example fix

// before
var prompt = builder.BuildPrompt(history); // contains AuthorRole.Tool
// after
var mapped = history.Select(m => m.Role == AuthorRole.Tool
    ? new ChatMessage(AuthorRole.User, m.Content)
    : m);
var prompt = builder.BuildPrompt(mapped);
Defensive patterns

Strategy: validation

Validate before calling

var valid = new[] { AuthorRole.System, AuthorRole.User, AuthorRole.Assistant };
if (chatHistory.Any(m => !valid.Contains(m.Role)))
    throw new InvalidOperationException("Unsupported AuthorRole in history for LLaMA template.");

Type guard

static bool HasSupportedRole(ChatMessage m) =>
    m.Role == AuthorRole.System || m.Role == AuthorRole.User || m.Role == AuthorRole.Assistant;

Try / catch

try { prompt = builder.BuildPrompt(chatHistory); }
catch (NotSupportedException ex) when (ex.Message.StartsWith("Unsupported role"))
{ prompt = builder.BuildPrompt(chatHistory.Where(HasSupportedRole)); }

Prevention

When it happens

Trigger: Calling BuildPrompt(IEnumerable<ChatMessage>, ...) with a ChatMessage whose AuthorRole is Tool/Function or any custom role — typical when function-calling results are kept verbatim in chat history.

Common situations: Semantic Kernel function-calling pipelines adding Tool-role messages; converting histories from other providers with extra roles; custom AuthorRole values.

Related errors


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