microsoft/semantic-kernel · error · InvalidOperationException

Chat history can't contain only system messages.

Error message

Chat history can't contain only system messages.

What it means

Thrown by GeminiChatCompletionClient.ValidateChatHistory when every message in the ChatHistory has AuthorRole.System. Gemini requires at least one non-system message (a user or assistant turn) to generate a response. An all-system history has no actual conversation content for the model to respond to.

Source

Thrown at dotnet/src/Connectors/Connectors.Google/Core/Gemini/Clients/GeminiChatCompletionClient.cs:768

    private static bool IsRequestableTool(IEnumerable<GeminiTool.FunctionDeclaration> functions, GeminiFunctionToolCall ftc)
        => functions.Any(geminiFunction =>
            string.Equals(geminiFunction.Name, ftc.FullyQualifiedName, StringComparison.OrdinalIgnoreCase));

    private static bool CheckAutoInvokeCondition(Kernel? kernel, GeminiPromptExecutionSettings geminiExecutionSettings)
    {
        bool autoInvoke = kernel is not null
                          && geminiExecutionSettings.ToolCallBehavior?.MaximumAutoInvokeAttempts > 0
                          && s_inflightAutoInvokes.Value < MaxInflightAutoInvokes;
        ValidateAutoInvoke(autoInvoke, geminiExecutionSettings.CandidateCount ?? 1);
        return autoInvoke;
    }

    private static void ValidateChatHistory(ChatHistory chatHistory)
    {
        Verify.NotNullOrEmpty(chatHistory);
        if (chatHistory.All(message => message.Role == AuthorRole.System))
        {
            throw new InvalidOperationException("Chat history can't contain only system messages.");
        }
    }

    private async IAsyncEnumerable<GeminiChatMessageContent> ProcessChatResponseStreamAsync(
        Stream responseStream,
        [EnumeratorCancellation] CancellationToken ct)
    {
        await foreach (var response in this.ParseResponseStreamAsync(responseStream, ct: ct).ConfigureAwait(false))
        {
            foreach (var messageContent in this.ProcessChatResponse(response))
            {
                yield return messageContent;
            }
        }
    }

    private async IAsyncEnumerable<GeminiResponse> ParseResponseStreamAsync(
        Stream responseStream,

View on GitHub (pinned to c028a0c7dc)

Solutions

  1. Add at least one user or assistant message to the chat history before calling the Gemini client.
  2. Move system instructions to GeminiPromptExecutionSettings.SystemInstruction instead of using AuthorRole.System messages.
  3. Validate the history before the call: if (history.All(m => m.Role == AuthorRole.System)) throw new InvalidOperationException("Add a user message.");

Example fix

// before — only system messages
history.AddSystemMessage("You are a translator.");
await client.GetChatMessageContentsAsync(history);

// after — add a user message
history.AddSystemMessage("You are a translator.");
history.AddUserMessage("Translate 'hello' to French.");
await client.GetChatMessageContentsAsync(history);
Defensive patterns

Strategy: validation

Validate before calling

if (chatHistory.All(m => m.Role == AuthorRole.System))
{
    throw new InvalidOperationException(
        "Chat history must contain at least one non-system message for Gemini.");
}
// or auto-fix by adding a placeholder user message
if (chatHistory.Count > 0 && chatHistory.All(m => m.Role == AuthorRole.System))
{
    chatHistory.AddUserMessage("(continue)");
}

Type guard

static bool HasNonSystemMessage(ChatHistory history) =>
    history.Any(m => m.Role != AuthorRole.System);

Try / catch

try { var result = await client.GetChatMessageContentsAsync(history); }
catch (InvalidOperationException ex) when (ex.Message.Contains("only system messages"))
{
    logger.LogWarning("Chat history had only system messages; adding user prompt.");
    history.AddUserMessage(userInput);
    result = await client.GetChatMessageContentsAsync(history);
}

Prevention

When it happens

Trigger: Calling GetChatMessageContentsAsync (or the streaming variant) with a ChatHistory where chatHistory.All(m => m.Role == AuthorRole.System) returns true. This means zero non-system messages exist. An empty history is caught earlier by Verify.NotNullOrEmpty.

Common situations: Building a chat history programmatically and only adding system instructions. Copying a history from another connector that stores everything as system messages. Forgetting to append the actual user query after setting up the system prompt.

Related errors


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