diff --git a/python/semantic_kernel/connectors/ai/anthropic/services/anthropic_chat_completion.py b/python/semantic_kernel/connectors/ai/anthropic/services/anthropic_chat_completion.py index ba1fb679b9aa..a73e03373951 100644 --- a/python/semantic_kernel/connectors/ai/anthropic/services/anthropic_chat_completion.py +++ b/python/semantic_kernel/connectors/ai/anthropic/services/anthropic_chat_completion.py @@ -65,8 +65,11 @@ # map finish reasons from Anthropic to Semantic Kernel ANTHROPIC_TO_SEMANTIC_KERNEL_FINISH_REASON_MAP = { "end_turn": SemanticKernelFinishReason.STOP, + "stop_sequence": SemanticKernelFinishReason.STOP, "max_tokens": SemanticKernelFinishReason.LENGTH, + "model_context_window_exceeded": SemanticKernelFinishReason.LENGTH, "tool_use": SemanticKernelFinishReason.TOOL_CALLS, + "refusal": SemanticKernelFinishReason.CONTENT_FILTER, } logger: logging.Logger = logging.getLogger(__name__) @@ -274,7 +277,7 @@ def _create_chat_message_content( finish_reason = None if response.stop_reason: - finish_reason = ANTHROPIC_TO_SEMANTIC_KERNEL_FINISH_REASON_MAP[response.stop_reason] + finish_reason = ANTHROPIC_TO_SEMANTIC_KERNEL_FINISH_REASON_MAP.get(response.stop_reason) return ChatMessageContent( inner_content=response, @@ -312,7 +315,7 @@ def _create_streaming_chat_message_content( ) ) elif isinstance(stream_event, RawMessageDeltaEvent): - finish_reason = ANTHROPIC_TO_SEMANTIC_KERNEL_FINISH_REASON_MAP[str(stream_event.delta.stop_reason)] + finish_reason = ANTHROPIC_TO_SEMANTIC_KERNEL_FINISH_REASON_MAP.get(str(stream_event.delta.stop_reason)) usage = _get_completion_usage(stream_event.usage) start_usage = metadata.get("usage") if metadata else None if getattr(stream_event.usage, "input_tokens", None) is None and isinstance(start_usage, CompletionUsage): diff --git a/python/tests/unit/connectors/ai/anthropic/services/test_anthropic_chat_completion.py b/python/tests/unit/connectors/ai/anthropic/services/test_anthropic_chat_completion.py index 14576c778eea..cb9d41045682 100644 --- a/python/tests/unit/connectors/ai/anthropic/services/test_anthropic_chat_completion.py +++ b/python/tests/unit/connectors/ai/anthropic/services/test_anthropic_chat_completion.py @@ -22,6 +22,7 @@ from semantic_kernel.contents.chat_message_content import ChatMessageContent, FunctionCallContent, TextContent from semantic_kernel.contents.streaming_chat_message_content import StreamingChatMessageContent from semantic_kernel.contents.utils.author_role import AuthorRole +from semantic_kernel.contents.utils.finish_reason import FinishReason from semantic_kernel.exceptions import ( ServiceInitializationError, ServiceInvalidExecutionSettingsError, @@ -666,3 +667,40 @@ async def async_generator(): gen_ai_attributes.INPUT_TOKENS: 130, gen_ai_attributes.OUTPUT_TOKENS: 7, } + + +@pytest.mark.parametrize( + ("stop_reason", "finish_reason"), + [ + ("end_turn", FinishReason.STOP), + ("stop_sequence", FinishReason.STOP), + ("max_tokens", FinishReason.LENGTH), + ("model_context_window_exceeded", FinishReason.LENGTH), + ("tool_use", FinishReason.TOOL_CALLS), + ("refusal", FinishReason.CONTENT_FILTER), + ("pause_turn", None), + ], +) +def test_stop_reason_maps_to_finish_reason(anthropic_unit_test_env, stop_reason, finish_reason) -> None: + chat_completion = AnthropicChatCompletion() + message = Message( + id="msg_1", + type="message", + role="assistant", + model="claude-sonnet-4-5", + content=[TextBlock(type="text", text="Hello")], + stop_reason=stop_reason, + stop_sequence=None, + usage=Usage(input_tokens=1, output_tokens=1), + ) + delta_event = RawMessageDeltaEvent( + type="message_delta", + delta=Delta(stop_reason=stop_reason, stop_sequence=None), + usage=MessageDeltaUsage(output_tokens=1), + ) + + content = chat_completion._create_chat_message_content(message, {}) + streaming_content = chat_completion._create_streaming_chat_message_content(delta_event) + + assert content.finish_reason == finish_reason + assert streaming_content.finish_reason == finish_reason