Skip to content

Python: map Anthropic stop_sequence finish reason and fall back safely for unknown stop reasons - #14382

Open
Shxiao (Shxiao101) wants to merge 2 commits into
microsoft:mainfrom
Shxiao101:Shxiao101/fix/anthropic-stop-reason-map
Open

Python: map Anthropic stop_sequence finish reason and fall back safely for unknown stop reasons#14382
Shxiao (Shxiao101) wants to merge 2 commits into
microsoft:mainfrom
Shxiao101:Shxiao101/fix/anthropic-stop-reason-map

Conversation

@Shxiao101

Copy link
Copy Markdown

Motivation and Context

The Anthropic connector translates Anthropic stop_reason values into Semantic Kernel FinishReason via ANTHROPIC_TO_SEMANTIC_KERNEL_FINISH_REASON_MAP, but the map is missing "stop_sequence" and both lookup sites index into it directly:

  • Non-streaming: finish_reason = ANTHROPIC_TO_SEMANTIC_KERNEL_FINISH_REASON_MAP[response.stop_reason]
  • Streaming: on every RawMessageDeltaEvent, finish_reason = ANTHROPIC_TO_SEMANTIC_KERNEL_FINISH_REASON_MAP[str(stream_event.delta.stop_reason)]

AnthropicChatPromptExecutionSettings supports stop_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 raises KeyError, and the streaming path raises KeyError on the final delta event.

The Bedrock connector already handles unknown stop reasons defensively (finish_reason_from_bedrock_to_semantic_kernel uses .get()), so this aligns the Anthropic connector with that existing pattern.

Description

  • Add "stop_sequence": SemanticKernelFinishReason.STOP to ANTHROPIC_TO_SEMANTIC_KERNEL_FINISH_REASON_MAP (consistent with OpenAI semantics, where hitting a stop sequence reports finish reason stop).
  • Switch both lookup sites to .get(...), so unknown or future stop reasons degrade to finish_reason=None instead of raising KeyError.

Verification

  • Before: ANTHROPIC_TO_SEMANTIC_KERNEL_FINISH_REASON_MAP["stop_sequence"] raises KeyError.
  • After: "stop_sequence" maps to FinishReason.STOP; MAP.get("refusal") / MAP.get("pause_turn") return None; the existing end_turn / max_tokens / tool_use mappings are unchanged.
  • pytest tests/unit/connectors/ai/anthropic — 35 passed (18 non-streaming chat completion + 9 request settings + 8 streaming).
  • ruff check and ruff format --check pass on the changed file.
  • No new tests added in this PR; the existing Anthropic chat completion suite exercises both changed lookup paths (non-streaming and streaming). Happy to add a regression test if preferred.

No linked issue — found while reviewing the connector code.

Contribution Checklist

…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.
Copilot AI lite review requested due to automatic review settings September 6, 2026 13:00
@Shxiao101
Shxiao (Shxiao101) requested a review from a team as a code owner September 6, 2026 13:00

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 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 to FinishReason.STOP in the Anthropic-to-SK finish-reason map.
  • Switch finish-reason lookups (non-streaming and streaming) from direct indexing to .get(...) to avoid KeyError and safely fall back to None.
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.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants