Python: map Anthropic stop_sequence finish reason and fall back safely for unknown stop reasons - #14382
Conversation
…to None for unknown reasons stop_sequences in AnthropicChatPromptExecutionSettings is user-settable; when the API returns stop_reason='stop_sequence' the direct dict index raised KeyError (non-streaming) and broke the stream on RawMessageDeltaEvent. Add the mapping and use .get() so unknown reasons degrade to None.
There was a problem hiding this comment.
🟡 Changes recommended
The new stop_sequence mapping and the unknown-stop-reason fallback are not explicitly covered by a targeted regression test, which increases the chance of reintroducing the original failure mode.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
This PR updates the Python Anthropic chat completion connector to correctly translate Anthropic stop reasons into Semantic Kernel FinishReason, specifically handling stop-sequence termination and avoiding runtime errors when Anthropic introduces new/unknown stop reasons.
Changes:
- Add
"stop_sequence"mapping toFinishReason.STOPin the Anthropic-to-SK finish-reason map. - Switch finish-reason lookups (non-streaming and streaming) from direct indexing to
.get(...)to avoidKeyErrorand safely fall back toNone.
File summaries
| File | Description |
|---|---|
| python/semantic_kernel/connectors/ai/anthropic/services/anthropic_chat_completion.py | Adds stop_sequence mapping and makes finish-reason mapping resilient to unknown stop reasons in both streaming and non-streaming paths. |
Review details
- Files reviewed: 1/1 changed files
- Comments generated: 1
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
There was a problem hiding this comment.
MAF Automated Review — Iteration 1
Result: No findings
Scope: full PR (1 commit(s)): 373a95edf32d
Model: claude-opus-4.8
Overview
This PR adds "stop_sequence": FinishReason.STOP to the Anthropic finish-reason map and switches two lookup sites from direct [] indexing to .get(), so an unmapped stop_reason degrades to finish_reason=None instead of raising KeyError. The change is behavior-preserving for the three already-mapped keys, aligns the Anthropic connector with the Bedrock sibling (which already maps stop_sequence -> STOP and uses .get() for the same fallback), and stays within the documented FinishReason | None content contract. No production control flow — auto function-invocation loops or security guards — reads finish_reason, so the broader None surface introduces no bypass or regression. Residual concerns are Low-priority only: unmapped newer reasons such as refusal lose observability, and no regression test exercises the changed branches.
Reviewed the supplied pull-request change set across correctness, security/reliability, architecture, and failure behavior.
No publishable findings remained after source verification for this scope.
There was a problem hiding this comment.
🟢 Approval recommended
The change is small, aligns behavior with the stated intent (no more KeyError on stop_sequence/unknown stop reasons), and is covered by new targeted unit tests for both execution paths.
Review details
- Files reviewed: 2/2 changed files
- Comments generated: 0 new
- Review effort level: Lite
Motivation and Context
The Anthropic connector translates Anthropic
stop_reasonvalues into Semantic KernelFinishReasonviaANTHROPIC_TO_SEMANTIC_KERNEL_FINISH_REASON_MAP, but the map is missing"stop_sequence"and both lookup sites index into it directly:finish_reason = ANTHROPIC_TO_SEMANTIC_KERNEL_FINISH_REASON_MAP[response.stop_reason]RawMessageDeltaEvent,finish_reason = ANTHROPIC_TO_SEMANTIC_KERNEL_FINISH_REASON_MAP[str(stream_event.delta.stop_reason)]AnthropicChatPromptExecutionSettingssupportsstop_sequences. When a request sets them and Anthropic ends the turn because a stop sequence was hit (stop_reason="stop_sequence"), the non-streaming path raisesKeyError, and the streaming path raisesKeyErroron the final delta event.The Bedrock connector already handles unknown stop reasons defensively (
finish_reason_from_bedrock_to_semantic_kerneluses.get()), so this aligns the Anthropic connector with that existing pattern.Description
"stop_sequence": SemanticKernelFinishReason.STOPtoANTHROPIC_TO_SEMANTIC_KERNEL_FINISH_REASON_MAP(consistent with OpenAI semantics, where hitting a stop sequence reports finish reasonstop)..get(...), so unknown or future stop reasons degrade tofinish_reason=Noneinstead of raisingKeyError.Verification
ANTHROPIC_TO_SEMANTIC_KERNEL_FINISH_REASON_MAP["stop_sequence"]raisesKeyError."stop_sequence"maps toFinishReason.STOP;MAP.get("refusal")/MAP.get("pause_turn")returnNone; the existingend_turn/max_tokens/tool_usemappings are unchanged.pytest tests/unit/connectors/ai/anthropic— 35 passed (18 non-streaming chat completion + 9 request settings + 8 streaming).ruff checkandruff format --checkpass on the changed file.No linked issue — found while reviewing the connector code.
Contribution Checklist