# Copyright (c) Microsoft. All rights reserved.
from collections.abc import AsyncGenerator
from unittest.mock import AsyncMock, MagicMock
import pytest
from anthropic import AsyncAnthropic
from anthropic.lib.streaming import TextEvent
from anthropic.lib.streaming._types import InputJsonEvent
from anthropic.types import (
ContentBlockStopEvent,
InputJSONDelta,
Message,
MessageDeltaUsage,
MessageStopEvent,
RawContentBlockDeltaEvent,
RawContentBlockStartEvent,
RawMessageDeltaEvent,
RawMessageStartEvent,
TextBlock,
TextDelta,
ToolUseBlock,
Usage,
)
from anthropic.types.raw_message_delta_event import Delta
from semantic_kernel.connectors.ai.anthropic.prompt_execution_settings.anthropic_prompt_execution_settings import (
AnthropicChatPromptExecutionSettings,
)
from semantic_kernel.contents.chat_message_content import (
ChatMessageContent,
FunctionCallContent,
FunctionResultContent,
TextContent,
)
from semantic_kernel.contents.const import ContentTypes
from semantic_kernel.contents.streaming_chat_message_content import StreamingChatMessageContent, StreamingTextContent
from semantic_kernel.contents.utils.author_role import AuthorRole
from semantic_kernel.contents.utils.finish_reason import FinishReason
@pytest.fixture
def mock_tool_calls_message() -> ChatMessageContent:
return ChatMessageContent(
ai_model_id="claude-3-opus-20240229",
metadata={},
content_type="message",
role=AuthorRole.ASSISTANT,
name=None,
items=[
TextContent(
inner_content=None,
ai_model_id=None,
metadata={},
content_type="text",
text="",
encoding=None,
),
FunctionCallContent(
inner_content=None,
ai_model_id=None,
metadata={},
content_type=ContentTypes.FUNCTION_CALL_CONTENT,
id="test_function_call_content",
index=1,
name="math-Add",
function_name="Add",
plugin_name="math",
arguments={"input": 3, "amount": 3},
),
],
encoding=None,
finish_reason=FinishReason.TOOL_CALLS,
)
@pytest.fixture
def mock_parallel_tool_calls_message() -> ChatMessageContent:
return ChatMessageContent(
ai_model_id="claude-3-opus-20240229",
metadata={},
content_type="message",
role=AuthorRole.ASSISTANT,
name=None,
items=[
TextContent(
inner_content=None,
ai_model_id=None,
metadata={},
content_type="text",
text="",
encoding=None,
),
FunctionCallContent(
inner_content=None,
ai_model_id=None,
metadata={},
content_type=ContentTypes.FUNCTION_CALL_CONTENT,
id="test_function_call_content_1",
index=1,
name="math-Add",
function_name="Add",
plugin_name="math",
arguments={"input": 3, "amount": 3},
),
FunctionCallContent(
inner_content=None,
ai_model_id=None,
metadata={},
content_type=ContentTypes.FUNCTION_CALL_CONTENT,
id="test_function_call_content_2",
index=1,
name="math-Subtract",
function_name="Subtract",
plugin_name="math",
arguments={"input": 6, "amount": 3},
),
],
encoding=None,
finish_reason=FinishReason.TOOL_CALLS,
)
@pytest.fixture
def mock_streaming_tool_calls_message() -> list:
stream_events = [
RawMessageStartEvent(
message=Message(
id="test_message_id",
content=[],
model="claude-3-opus-20240229",
role="assistant",
stop_reason=None,
stop_sequence=None,
type="message",
usage=Usage(input_tokens=1720, output_tokens=2),
),
type="message_start",
),
RawContentBlockStartEvent(content_block=TextBlock(text="", type="text"), index=0, type="content_block_start"),
RawContentBlockDeltaEvent(
delta=TextDelta(text="", type="text_delta"), index=0, type="content_block_delta"
),
TextEvent(type="text", text="", snapshot=""),
RawContentBlockDeltaEvent(
delta=TextDelta(text="", type="text_delta"), index=0, type="content_block_delta"
),
TextEvent(type="text", text="", snapshot=""),
ContentBlockStopEvent(
index=0, type="content_block_stop", content_block=TextBlock(text="", type="text")
),
RawContentBlockStartEvent(
content_block=ToolUseBlock(id="test_tool_use_message_id", input={}, name="math-Add", type="tool_use"),
index=1,
type="content_block_start",
),
RawContentBlockDeltaEvent(
delta=InputJSONDelta(partial_json='{"input": 3, "amount": 3}', type="input_json_delta"),
index=1,
type="content_block_delta",
),
InputJsonEvent(type="input_json", partial_json='{"input": 3, "amount": 3}', snapshot={"input": 3, "amount": 3}),
ContentBlockStopEvent(
index=1,
type="content_block_stop",
content_block=ToolUseBlock(
id="test_tool_use_block_id", input={"input": 3, "amount": 3}, name="math-Add", type="tool_use"
),
),
RawMessageDeltaEvent(
delta=Delta(stop_reason="tool_use", stop_sequence=None),
type="message_delta",
usage=MessageDeltaUsage(output_tokens=159),
),
MessageStopEvent(
type="message_stop",
message=Message(
id="test_message_id",
content=[
TextBlock(text="", type="text"),
ToolUseBlock(
id="test_tool_use_block_id", input={"input": 3, "amount": 3}, name="math-Add", type="tool_use"
),
],
model="claude-3-opus-20240229",
role="assistant",
stop_reason="tool_use",
stop_sequence=None,
type="message",
usage=Usage(input_tokens=100, output_tokens=100),
),
),
]
async def async_generator():
for event in stream_events:
yield event
stream_mock = AsyncMock()
stream_mock.__aenter__.return_value = async_generator()
return stream_mock
@pytest.fixture
def mock_tool_call_result_message() -> ChatMessageContent:
return ChatMessageContent(
inner_content=None,
ai_model_id=None,
metadata={},
content_type="message",
role=AuthorRole.TOOL,
name=None,
items=[
FunctionResultContent(
id="test_function_call_content",
result=6,
)
],
encoding=None,
finish_reason=FinishReason.TOOL_CALLS,
)
@pytest.fixture
def mock_parallel_tool_call_result_message() -> ChatMessageContent:
return ChatMessageContent(
inner_content=None,
ai_model_id=None,
metadata={},
content_type="message",
role=AuthorRole.TOOL,
name=None,
items=[
FunctionResultContent(
id="test_function_call_content_1",
result=6,
),
FunctionResultContent(
id="test_function_call_content_2",
result=3,
),
],
encoding=None,
finish_reason=FinishReason.TOOL_CALLS,
)
@pytest.fixture
def mock_streaming_chat_message_content() -> StreamingChatMessageContent:
return StreamingChatMessageContent(
choice_index=0,
ai_model_id="claude-3-opus-20240229",
metadata={},
role=AuthorRole.ASSISTANT,
name=None,
items=[
StreamingTextContent(
inner_content=None,
ai_model_id=None,
metadata={},
content_type="text",
text="",
encoding=None,
choice_index=0,
),
FunctionCallContent(
inner_content=None,
ai_model_id=None,
metadata={},
content_type=ContentTypes.FUNCTION_CALL_CONTENT,
id="tool_id",
index=0,
name="math-Add",
function_name="Add",
plugin_name="math",
arguments='{"input": 3, "amount": 3}',
),
],
encoding=None,
finish_reason=FinishReason.TOOL_CALLS,
)
@pytest.fixture
def mock_settings() -> AnthropicChatPromptExecutionSettings:
return AnthropicChatPromptExecutionSettings()
@pytest.fixture
def mock_chat_message_response() -> Message:
return Message(
id="test_message_id",
content=[TextBlock(text="Hello, how are you?", type="text")],
model="claude-3-opus-20240229",
role="assistant",
stop_reason="end_turn",
stop_sequence=None,
type="message",
usage=Usage(input_tokens=10, output_tokens=10),
)
@pytest.fixture
def mock_streaming_message_response() -> AsyncGenerator:
raw_message_start_event = RawMessageStartEvent(
message=Message(
id="test_message_id",
content=[],
model="claude-3-opus-20240229",
role="assistant",
stop_reason=None,
stop_sequence=None,
type="message",
usage=Usage(input_tokens=41, output_tokens=3),
),
type="message_start",
)
raw_content_block_start_event = RawContentBlockStartEvent(
content_block=TextBlock(text="", type="text"),
index=0,
type="content_block_start",
)
raw_content_block_delta_event = RawContentBlockDeltaEvent(
delta=TextDelta(text="Hello! It", type="text_delta"),
index=0,
type="content_block_delta",
)
text_event = TextEvent(
type="text",
text="Hello! It",
snapshot="Hello! It",
)
content_block_stop_event = ContentBlockStopEvent(
index=0,
type="content_block_stop",
content_block=TextBlock(text="Hello! It's nice to meet you.", type="text"),
)
raw_message_delta_event = RawMessageDeltaEvent(
delta=Delta(stop_reason="end_turn", stop_sequence=None),
type="message_delta",
usage=MessageDeltaUsage(output_tokens=84),
)
message_stop_event = MessageStopEvent(
type="message_stop",
message=Message(
id="test_message_stop_id",
content=[TextBlock(text="Hello! It's nice to meet you.", type="text")],
model="claude-3-opus-20240229",
role="assistant",
stop_reason="end_turn",
stop_sequence=None,
type="message",
usage=Usage(input_tokens=41, output_tokens=84),
),
)
# Combine all mock events into a list
stream_events = [
raw_message_start_event,
raw_content_block_start_event,
raw_content_block_delta_event,
text_event,
content_block_stop_event,
raw_message_delta_event,
message_stop_event,
]
async def async_generator():
for event in stream_events:
yield event
# Create an AsyncMock for the stream
stream_mock = AsyncMock()
stream_mock.__aenter__.return_value = async_generator()
return stream_mock
@pytest.fixture
def mock_anthropic_client_completion(mock_chat_message_response: Message) -> AsyncAnthropic:
client = MagicMock(spec=AsyncAnthropic)
messages_mock = MagicMock()
messages_mock.create = AsyncMock(return_value=mock_chat_message_response)
client.messages = messages_mock
return client
@pytest.fixture
def mock_anthropic_client_completion_stream(mock_streaming_message_response: AsyncGenerator) -> AsyncAnthropic:
client = MagicMock(spec=AsyncAnthropic)
messages_mock = MagicMock()
messages_mock.stream.return_value = mock_streaming_message_response
client.messages = messages_mock
return client