microsoft/semantic-kernel · error · InvalidOperationException

Message role must be either Assistant or User.

Error message

Message role must be either Assistant or User.

What it means

Thrown when building the Bedrock agent conversation history, this error rejects any ChatMessageContent whose Role is neither AuthorRole.Assistant nor AuthorRole.User. The Bedrock Agent runtime conversation API only models a two-party dialogue (assistant vs user), so system, tool, or custom-role messages cannot be mapped. The check is applied to every message in the history except the last one (the current user turn).

Source

Thrown at dotnet/src/Agents/Bedrock/BedrockAgentChannel.cs:227

        SessionState sessionState = new();

        // We don't take the last message as it needs to be sent separately in another parameter.
        if (this._history.Count > 1)
        {
            sessionState.ConversationHistory = new()
            {
                Messages = []
            };

            foreach (var message in this._history.Take(this._history.Count - 1))
            {
                if (message.Content is null)
                {
                    throw new InvalidOperationException("Message content cannot be null.");
                }
                if (message.Role != AuthorRole.Assistant && message.Role != AuthorRole.User)
                {
                    throw new InvalidOperationException("Message role must be either Assistant or User.");
                }

                sessionState.ConversationHistory.Messages.Add(new()
                {
                    Role = message.Role == AuthorRole.Assistant
                        ? Amazon.BedrockAgentRuntime.ConversationRole.Assistant
                        : Amazon.BedrockAgentRuntime.ConversationRole.User,
                    Content = [
                        new Amazon.BedrockAgentRuntime.Model.ContentBlock()
                        {
                            Text = message.Content,
                        },
                    ],
                });
            }
        }

        return sessionState;

View on GitHub (pinned to c028a0c7dc)

Solutions

  1. Filter the history before passing it to the Bedrock agent: remove or convert any message whose Role is not AuthorRole.User or AuthorRole.Assistant.
  2. Move system instructions into the Bedrock agent's instruction/agent configuration rather than into the conversation history.
  3. If a Tool role message exists, ensure function results are routed through the Bedrock session-state ReturnControl path rather than added as history messages.
  4. Verify the thread history only contains alternating User/Assistant turns prior to the current invocation.

Example fix

// before
history.Add(new ChatMessageContent(AuthorRole.System, "You are a helpful agent."));
agent.InvokeAsync(history, thread);

// after — put instructions in the agent definition, not the history
var agent = await bedrockAgentClient.CreateOrUpdateAgentAsync(
    new CreateAgentRequest { ... AgentResourceRoleArn = ..., Instruction = "You are a helpful agent." });
agent.InvokeAsync(history, thread); // history contains only User/Assistant messages
Defensive patterns

Strategy: validation

Validate before calling

// Before invoking the Bedrock agent, filter history to allowed roles
using Microsoft.SemanticKernel.ChatCompletion;

ChatHistory filtered = new();
foreach (var m in history)
{
    if (m.Role == AuthorRole.User || m.Role == AuthorRole.Assistant)
        filtered.Add(m);
    // else: drop System/Tool/custom-role messages
}
// pass `filtered` to the Bedrock agent invocation

Type guard

static bool IsBedrockSupportedRole(ChatMessageContent m) =>
    m.Role == AuthorRole.User || m.Role == AuthorRole.Assistant;

Try / catch

try { await foreach (var r in agent.InvokeAsync(filtered, thread, ct)) { ... } }
catch (InvalidOperationException ex) when (ex.Message.Contains("Message role"))
{
    logger.LogError("Unsupported message role in history. Filter to User/Assistant only.");
    throw;
}

Prevention

When it happens

Trigger: Calling InvokeAsync/InvokeStreamingAsync on a BedrockAgent with an AgentThread whose history contains a System message, a Tool message, or any message with a non-standard AuthorRole. Also triggered when a ChatHistory is shared between a different connector (that accepted System messages) and a Bedrock agent.

Common situations: Reusing a ChatHistory built for OpenAI/Azure connectors where System messages are standard; prepending a system prompt to the thread; tool/function result messages that carry AuthorRole.Tool; messages created with new AuthorRole("system").

Related errors


AI-assisted analysis of microsoft/semantic-kernel@c028a0c7dc (2026-08-13). Data as JSON: /api/errors/9b100a7b3f272a79. Report an issue: GitHub.