microsoft/autogen · error · ArgumentNullException

choice.FinishReason

Error message

choice.FinishReason

What it means

MistralChatMessageConnector.PostProcessMessage switches on choice.FinishReason to decide between TextMessage (stop/length) and ToolCallMessage (tool_calls). A null FinishReason cannot be dispatched, so ArgumentNullException is thrown with paramName "choice.FinishReason".

Source

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

                };
            }
        });
    }

    private IMessage PostProcessMessage(ChatCompletionResponse response, IAgent from)
    {
        if (response.Choices is null)
        {
            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)

View on GitHub (pinned to 027ecf0a37)

Solutions

  1. Make sure streaming responses go through the streaming connector path (ProcessChatCompletionResponse), not the non-streaming PostProcessMessage.
  2. Log the raw choice JSON to confirm finish_reason is genuinely missing, then update AutoGen.Mistral if the API contract changed.
  3. Catch ArgumentNullException around the completion call and retry once — some null-finish-reason responses are transient.
Defensive patterns

Strategy: validation

Validate before calling

var fr = response.Choices?[0].FinishReason;
if (fr is null)
{
    // response shape not dispatchable: log raw choice JSON, do not map
}

Try / catch

try { var reply = await mistralAgent.SendAsync(msg); } catch (ArgumentNullException ex) when (ex.ParamName == "choice.FinishReason") { /* retry once; report schema drift if persistent */ }

Prevention

When it happens

Trigger: A Mistral chat completion choice whose finish_reason JSON field is absent/null — e.g. truncated error responses, streaming chunk shapes accidentally fed to the non-streaming path, or API schema changes.

Common situations: Feeding a streaming chunk payload into the non-streaming PostProcessMessage path; Mistral API version drift making finish_reason optional on some responses; intermediate/keep-alive responses with empty deltas.

Related errors


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