{"record":{"id":"54f3ed89923e28ef","repo":"microsoft/autogen","slug":"unexpected-finish-reason-choice-finish-reason","errorCode":null,"errorMessage":"Unexpected finish reason: {choice.finish_reason}","messagePattern":"Unexpected finish reason: (.+?)","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"python/packages/autogen-ext/src/autogen_ext/models/azure/_azure_ai_client.py","lineNumber":533,"sourceCode":"                # Emit the start event.\n                logger.info(\n                    LLMStreamStartEvent(\n                        messages=[m.as_dict() for m in azure_messages],\n                    )\n                )\n            assert isinstance(chunk, StreamingChatCompletionsUpdate)\n            choice = chunk.choices[0] if len(chunk.choices) > 0 else None\n            if choice and choice.finish_reason is not None:\n                if isinstance(choice.finish_reason, CompletionsFinishReason):\n                    finish_reason = cast(FinishReasons, choice.finish_reason.value)\n                    # Handle special case for TOOL_CALLS finish reason\n                    if choice.finish_reason is CompletionsFinishReason.TOOL_CALLS:\n                        finish_reason = \"function_calls\"\n                else:\n                    if choice.finish_reason in [\"stop\", \"length\", \"function_calls\", \"content_filter\", \"unknown\"]:\n                        finish_reason = choice.finish_reason  # type: ignore\n                    else:\n                        raise ValueError(f\"Unexpected finish reason: {choice.finish_reason}\")\n\n            # We first try to load the content\n            if choice and choice.delta.content is not None:\n                content_deltas.append(choice.delta.content)\n                yield choice.delta.content\n            # Otherwise, we try to load the tool calls\n            if choice and choice.delta.tool_calls is not None:\n                for tool_call_chunk in choice.delta.tool_calls:\n                    # print(tool_call_chunk)\n                    if \"index\" in tool_call_chunk:\n                        idx = tool_call_chunk[\"index\"]\n                    else:\n                        idx = tool_call_chunk.id\n                    if idx not in full_tool_calls:\n                        full_tool_calls[idx] = FunctionCall(id=\"\", arguments=\"\", name=\"\")\n\n                    full_tool_calls[idx].id += tool_call_chunk.id\n                    full_tool_calls[idx].name += tool_call_chunk.function.name","sourceCodeStart":515,"sourceCodeEnd":551,"githubUrl":"https://github.com/microsoft/autogen/blob/027ecf0a379bcc1d09956d46d12d44a3ad9cee14/python/packages/autogen-ext/src/autogen_ext/models/azure/_azure_ai_client.py#L515-L551","documentation":"Raised while consuming a create_stream() response in the Azure AI client: when a streaming chunk carries a finish_reason that is not a CompletionsFinishReason enum and is not one of the strings 'stop', 'length', 'function_calls', 'content_filter', 'unknown', the client raises ValueError('Unexpected finish reason: ...'). This is the fallback branch of the finish-reason mapping — the service reported a terminal state this client version cannot interpret.","triggerScenarios":"Azure AI Foundry / GitHub Models starts returning a new finish_reason value (string) that predates support in the installed autogen-ext; a proxy or gateway (custom endpoint) emits non-standard finish reasons like 'tool_calls' (instead of the mapped 'function_calls').","commonSituations":"Version skew: service-side rollout of new stop reasons (e.g. reasoning-model reasons) before autogen-ext update; routing the client through an OpenAI-compatible proxy that uses OpenAI vocabularly ('tool_calls') instead of the Azure inference SDK enum.","solutions":["Upgrade autogen-ext to a release mapping the new finish reason","Point the client at the genuine Azure AI Foundry / GitHub Models endpoint rather than an OpenAI-compatible proxy","If stuck on this version, catch the ValueError around stream consumption and retry without streaming (create() uses a different code path)"],"exampleFix":"# before\nasync for chunk in client.create_stream(msgs):\n    print(chunk)\n\n# after (defensive: fall back to non-streaming on unknown finish reason)\ntry:\n    async for chunk in client.create_stream(msgs):\n        print(chunk)\nexcept ValueError as e:\n    if \"Unexpected finish reason\" in str(e):\n        result = await client.create(msgs)\n    else:\n        raise","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try:\n    async for chunk in client.create_stream(msgs):\n        process(chunk)\nexcept ValueError as e:\n    if \"Unexpected finish reason\" in str(e):\n        result = await client.create(msgs)  # non-streaming fallback\n    else:\n        raise","preventionTips":["Keep autogen-ext current with the Azure inference SDK","Point the client at real Azure AI Foundry / GitHub Models endpoints, not OpenAI-compat proxies","Monitor tracebacks for new finish_reason strings and update promptly"],"tags":["azure","streaming","finish-reason","version-skew"],"backgroundTag":null,"analyzedSha":"027ecf0a379bcc1d09956d46d12d44a3ad9cee14","analyzedAt":"2026-08-15T03:38:00.719Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}