microsoft/autogen · error · ArgumentNullException

choice.Message.ToolCalls

Error message

choice.Message.ToolCalls

What it means

Thrown when a Mistral completion has finish_reason == 'tool_calls' but choice.Message.ToolCalls is null. The connector requires at least the ToolCalls collection to exist before mapping to ToolCallMessage; a null list means the response contract was violated. Thrown via the null-coalescing throw expression at MistralChatMessageConnector.cs:160.

Source

Thrown at dotnet/src/AutoGen.Mistral/Middleware/MistralChatMessageConnector.cs:160

        {
            throw new ArgumentNullException("response.Choices");
        }

        if (response.Choices?.Count != 1)
        {
            throw new NotSupportedException("response.Choices.Count != 1");
        }

        var choice = response.Choices[0];
        var finishReason = choice.FinishReason ?? throw new ArgumentNullException("choice.FinishReason");

        if (finishReason == Choice.FinishReasonEnum.Stop || finishReason == Choice.FinishReasonEnum.Length)
        {
            return new TextMessage(Role.Assistant, choice.Message?.Content ?? throw new ArgumentNullException("choice.Message.Content"), from: from.Name);
        }
        else if (finishReason == Choice.FinishReasonEnum.ToolCalls)
        {
            var functionContents = choice.Message?.ToolCalls ?? throw new ArgumentNullException("choice.Message.ToolCalls");
            var toolCalls = functionContents.Select(f => new ToolCall(f.Function.Name, f.Function.Arguments) { ToolCallId = f.Id }).ToList();
            return new ToolCallMessage(toolCalls, from: from.Name);
        }
        else
        {
            throw new NotSupportedException($"FinishReason {finishReason} is not supported");
        }
    }

    private IMessage? ProcessChatCompletionResponse(IMessage<ChatCompletionResponse> message, IAgent agent)
    {
        var response = message.Content;
        if (response.VarObject != "chat.completion.chunk")
        {
            throw new NotSupportedException($"VarObject {response.VarObject} is not supported");
        }
        if (response.Choices is null)
        {

View on GitHub (pinned to 027ecf0a37)

Solutions

  1. Log the raw response JSON and verify the tool_calls field name and shape match the SDK's FunctionContent model.
  2. If writing tests/mocks, always populate ToolCalls (even an empty list) whenever FinishReason is ToolCalls.
  3. If the live API shape changed, update the response model classes in AutoGen.Mistral accordingly.
  4. Upgrade to the latest AutoGen.Mistral package.

Example fix

// before
var functionContents = choice.Message?.ToolCalls ?? throw new ArgumentNullException("choice.Message.ToolCalls");

// after
var functionContents = choice.Message?.ToolCalls ?? new List<ToolCallContent>(); // then handle empty below
if (functionContents.Count == 0) throw new InvalidOperationException("finish_reason=tool_calls but tool_calls list is empty");
Defensive patterns

Strategy: validation

Validate before calling

if (choice.FinishReason == Choice.FinishReasonEnum.ToolCalls
    && (choice.Message?.ToolCalls is null || choice.Message.ToolCalls.Count == 0))
{
    throw new InvalidOperationException("finish_reason=tool_calls but no tool calls present; check API/model version");
}

Type guard

static bool HasToolCalls(Choice choice) =>
    choice?.Message?.ToolCalls is { Count: > 0 };

Try / catch

catch (ArgumentNullException ex) when (ex.Message.Contains("choice.Message.ToolCalls"))
{
    logger.LogError(ex, "Malformed tool-call completion; inspect raw response");
    throw; // data integrity issue: do not silently continue
}

Prevention

When it happens

Trigger: Non-streaming Mistral call where finish_reason is 'tool_calls' yet the message payload contains no tool_calls array (or it deserializes to null because of a renamed field). Also occurs with hand-crafted/mocked ChatCompletionResponse objects that set FinishReason but forget ToolCalls.

Common situations: Unit tests with hand-built response fixtures missing ToolCalls; Mistral API schema drift (field renamed or restructured); a truncated response from a proxy or gateway that strips tool_calls.

Related errors


AI-assisted analysis of microsoft/autogen@027ecf0a37 (2026-08-15). Data as JSON: /api/errors/6380422b17058df8. Report an issue: GitHub.