Skip to content

Commit 53dcf39

Browse files
Python: #6501 Increase anthropic chat completion test coverage (#8388)
### Motivation and Context Increase Anthropic Chat Completion code coverage to 100% (#6501) ### Description Match the mocked API responses to realistic examples. ### Contribution Checklist - [x] The code builds clean without any errors or warnings - [x] The PR follows the [SK Contribution Guidelines](https://github.com/microsoft/semantic-kernel/blob/main/CONTRIBUTING.md) and the [pre-submission formatting script](https://github.com/microsoft/semantic-kernel/blob/main/CONTRIBUTING.md#development-scripts) raises no violations - [x] All unit tests pass, and I have added new tests where possible - [x] I didn't break anyone 😄
1 parent 06f3471 commit 53dcf39

File tree

1 file changed

+116
-19
lines changed

1 file changed

+116
-19
lines changed

python/tests/unit/connectors/anthropic/services/test_anthropic_chat_completion.py

+116-19
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,21 @@
33

44
import pytest
55
from anthropic import AsyncAnthropic
6+
from anthropic.lib.streaming import TextEvent
7+
from anthropic.types import (
8+
ContentBlockStopEvent,
9+
Message,
10+
MessageDeltaUsage,
11+
MessageStopEvent,
12+
RawContentBlockDeltaEvent,
13+
RawContentBlockStartEvent,
14+
RawMessageDeltaEvent,
15+
RawMessageStartEvent,
16+
TextBlock,
17+
TextDelta,
18+
Usage,
19+
)
20+
from anthropic.types.raw_message_delta_event import Delta
621

722
from semantic_kernel.connectors.ai.anthropic.prompt_execution_settings.anthropic_prompt_execution_settings import (
823
AnthropicChatPromptExecutionSettings,
@@ -12,6 +27,7 @@
1227
from semantic_kernel.connectors.ai.open_ai.prompt_execution_settings.open_ai_prompt_execution_settings import (
1328
OpenAIChatPromptExecutionSettings,
1429
)
30+
from semantic_kernel.contents.chat_history import ChatHistory
1531
from semantic_kernel.contents.chat_message_content import ChatMessageContent
1632
from semantic_kernel.exceptions import ServiceInitializationError, ServiceResponseException
1733
from semantic_kernel.functions.kernel_arguments import KernelArguments
@@ -26,43 +42,120 @@ def mock_settings() -> AnthropicChatPromptExecutionSettings:
2642
@pytest.fixture
2743
def mock_anthropic_client_completion() -> AsyncAnthropic:
2844
client = MagicMock(spec=AsyncAnthropic)
45+
2946
chat_completion_response = AsyncMock()
30-
31-
content = [MagicMock(finish_reason="stop", message=MagicMock(role="assistant", content="Test"))]
32-
chat_completion_response.content = content
47+
chat_completion_response.content = [TextBlock(text="Hello! It's nice to meet you.", type="text")]
48+
chat_completion_response.id = "test_id"
49+
chat_completion_response.model = "claude-3-opus-20240229"
50+
chat_completion_response.role = "assistant"
51+
chat_completion_response.stop_reason = "end_turn"
52+
chat_completion_response.stop_sequence = None
53+
chat_completion_response.type = "message"
54+
chat_completion_response.usage = Usage(input_tokens=114, output_tokens=75)
3355

3456
# Create a MagicMock for the messages attribute
3557
messages_mock = MagicMock()
36-
messages_mock.create = chat_completion_response
58+
messages_mock.create = AsyncMock(return_value=chat_completion_response)
3759

3860
# Assign the messages_mock to the client.messages attribute
3961
client.messages = messages_mock
62+
4063
return client
4164

4265

4366
@pytest.fixture
4467
def mock_anthropic_client_completion_stream() -> AsyncAnthropic:
4568
client = MagicMock(spec=AsyncAnthropic)
46-
chat_completion_response = MagicMock()
4769

48-
content = [
49-
MagicMock(finish_reason="stop", delta=MagicMock(role="assistant", content="Test")),
50-
MagicMock(finish_reason="stop", delta=MagicMock(role="assistant", content="Test", tool_calls=None)),
70+
# Create MagicMock instances for each event with the spec set to the appropriate class
71+
mock_raw_message_start_event = MagicMock(spec=RawMessageStartEvent)
72+
mock_raw_message_start_event.message = MagicMock(spec=Message)
73+
mock_raw_message_start_event.message.id = "test_message_id"
74+
mock_raw_message_start_event.message.content = []
75+
mock_raw_message_start_event.message.model = "claude-3-opus-20240229"
76+
mock_raw_message_start_event.message.role = "assistant"
77+
mock_raw_message_start_event.message.stop_reason = None
78+
mock_raw_message_start_event.message.stop_sequence = None
79+
mock_raw_message_start_event.message.type = "message"
80+
mock_raw_message_start_event.message.usage = MagicMock(spec=Usage)
81+
mock_raw_message_start_event.message.usage.input_tokens = 41
82+
mock_raw_message_start_event.message.usage.output_tokens = 3
83+
mock_raw_message_start_event.type = "message_start"
84+
85+
mock_raw_content_block_start_event = MagicMock(spec=RawContentBlockStartEvent)
86+
mock_raw_content_block_start_event.content_block = MagicMock(spec=TextBlock)
87+
mock_raw_content_block_start_event.content_block.text = ""
88+
mock_raw_content_block_start_event.content_block.type = "text"
89+
mock_raw_content_block_start_event.index = 0
90+
mock_raw_content_block_start_event.type = "content_block_start"
91+
92+
mock_raw_content_block_delta_event = MagicMock(spec=RawContentBlockDeltaEvent)
93+
mock_raw_content_block_delta_event.delta = MagicMock(spec=TextDelta)
94+
mock_raw_content_block_delta_event.delta.text = "Hello! It"
95+
mock_raw_content_block_delta_event.delta.type = "text_delta"
96+
mock_raw_content_block_delta_event.index = 0
97+
mock_raw_content_block_delta_event.type = "content_block_delta"
98+
99+
mock_text_event = MagicMock(spec=TextEvent)
100+
mock_text_event.type = "text"
101+
mock_text_event.text = "Hello! It"
102+
mock_text_event.snapshot = "Hello! It"
103+
104+
mock_content_block_stop_event = MagicMock(spec=ContentBlockStopEvent)
105+
mock_content_block_stop_event.index = 0
106+
mock_content_block_stop_event.type = "content_block_stop"
107+
mock_content_block_stop_event.content_block = MagicMock(spec=TextBlock)
108+
mock_content_block_stop_event.content_block.text = "Hello! It's nice to meet you."
109+
mock_content_block_stop_event.content_block.type = "text"
110+
111+
mock_raw_message_delta_event = MagicMock(spec=RawMessageDeltaEvent)
112+
mock_raw_message_delta_event.delta = MagicMock(spec=Delta)
113+
mock_raw_message_delta_event.delta.stop_reason = "end_turn"
114+
mock_raw_message_delta_event.delta.stop_sequence = None
115+
mock_raw_message_delta_event.type = "message_delta"
116+
mock_raw_message_delta_event.usage = MagicMock(spec=MessageDeltaUsage)
117+
mock_raw_message_delta_event.usage.output_tokens = 84
118+
119+
mock_message_stop_event = MagicMock(spec=MessageStopEvent)
120+
mock_message_stop_event.type = "message_stop"
121+
mock_message_stop_event.message = MagicMock(spec=Message)
122+
mock_message_stop_event.message.id = "test_message_stop_id"
123+
mock_message_stop_event.message.content = [MagicMock(spec=TextBlock)]
124+
mock_message_stop_event.message.content[0].text = "Hello! It's nice to meet you."
125+
mock_message_stop_event.message.content[0].type = "text"
126+
mock_message_stop_event.message.model = "claude-3-opus-20240229"
127+
mock_message_stop_event.message.role = "assistant"
128+
mock_message_stop_event.message.stop_reason = "end_turn"
129+
mock_message_stop_event.message.stop_sequence = None
130+
mock_message_stop_event.message.type = "message"
131+
mock_message_stop_event.message.usage = MagicMock(spec=Usage)
132+
mock_message_stop_event.message.usage.input_tokens = 41
133+
mock_message_stop_event.message.usage.output_tokens = 84
134+
135+
# Combine all mock events into a list
136+
stream_events = [
137+
mock_raw_message_start_event,
138+
mock_raw_content_block_start_event,
139+
mock_raw_content_block_delta_event,
140+
mock_text_event,
141+
mock_content_block_stop_event,
142+
mock_raw_message_delta_event,
143+
mock_message_stop_event,
51144
]
52-
chat_completion_response.content = content
53145

54-
chat_completion_response_empty = MagicMock()
55-
chat_completion_response_empty.content = []
146+
async def async_generator():
147+
for event in stream_events:
148+
yield event
149+
150+
# Create an AsyncMock for the stream
151+
stream_mock = AsyncMock()
152+
stream_mock.__aenter__.return_value = async_generator()
56153

57-
# Create a MagicMock for the messages attribute
58154
messages_mock = MagicMock()
59-
messages_mock.stream = chat_completion_response
60-
61-
generator_mock = MagicMock()
62-
generator_mock.__aiter__.return_value = [chat_completion_response_empty, chat_completion_response]
63-
155+
messages_mock.stream.return_value = stream_mock
156+
64157
client.messages = messages_mock
65-
158+
66159
return client
67160

68161

@@ -72,7 +165,10 @@ async def test_complete_chat_contents(
72165
mock_settings: AnthropicChatPromptExecutionSettings,
73166
mock_anthropic_client_completion: AsyncAnthropic,
74167
):
75-
chat_history = MagicMock()
168+
chat_history = ChatHistory()
169+
chat_history.add_user_message("test_user_message")
170+
chat_history.add_assistant_message("test_assistant_message")
171+
76172
arguments = KernelArguments()
77173
chat_completion_base = AnthropicChatCompletion(
78174
ai_model_id="test_model_id", service_id="test", api_key="", async_client=mock_anthropic_client_completion
@@ -81,6 +177,7 @@ async def test_complete_chat_contents(
81177
content: list[ChatMessageContent] = await chat_completion_base.get_chat_message_contents(
82178
chat_history=chat_history, settings=mock_settings, kernel=kernel, arguments=arguments
83179
)
180+
84181
assert content is not None
85182

86183

0 commit comments

Comments
 (0)