347 lines
12 KiB
Python
347 lines
12 KiB
Python
# Copyright (c) Microsoft. All rights reserved.
|
|
|
|
import sys
|
|
from functools import partial
|
|
from typing import Any
|
|
|
|
import pytest
|
|
|
|
if sys.version_info >= (3, 12):
|
|
from typing import override # pragma: no cover
|
|
else:
|
|
from typing_extensions import override # pragma: no cover
|
|
|
|
from semantic_kernel import Kernel
|
|
from semantic_kernel.connectors.ai import PromptExecutionSettings
|
|
from semantic_kernel.contents import AuthorRole, ChatHistory, ChatMessageContent, TextContent
|
|
from semantic_kernel.kernel_pydantic import KernelBaseModel
|
|
from tests.integration.completions.chat_completion_test_base import (
|
|
ChatCompletionTestBase,
|
|
anthropic_setup,
|
|
mistral_ai_setup,
|
|
ollama_setup,
|
|
onnx_setup,
|
|
vertex_ai_setup,
|
|
)
|
|
from tests.integration.completions.completion_test_base import ServiceType
|
|
from tests.utils import retry
|
|
|
|
|
|
class Step(KernelBaseModel):
|
|
explanation: str
|
|
output: str
|
|
|
|
|
|
class Reasoning(KernelBaseModel):
|
|
steps: list[Step]
|
|
final_answer: str
|
|
|
|
|
|
pytestmark = pytest.mark.parametrize(
|
|
"service_id, execution_settings_kwargs, inputs, kwargs",
|
|
[
|
|
# region OpenAI
|
|
pytest.param(
|
|
"openai",
|
|
{},
|
|
[
|
|
ChatMessageContent(role=AuthorRole.USER, items=[TextContent(text="Hello")]),
|
|
ChatMessageContent(role=AuthorRole.USER, items=[TextContent(text="How are you today?")]),
|
|
],
|
|
{},
|
|
id="openai_text_input",
|
|
),
|
|
pytest.param(
|
|
"openai",
|
|
{"response_format": Reasoning},
|
|
[
|
|
ChatMessageContent(role=AuthorRole.USER, items=[TextContent(text="Hello")]),
|
|
ChatMessageContent(role=AuthorRole.USER, items=[TextContent(text="How are you today?")]),
|
|
],
|
|
{},
|
|
id="openai_json_schema_response_format",
|
|
),
|
|
# endregion
|
|
# region Azure
|
|
pytest.param(
|
|
"azure",
|
|
{},
|
|
[
|
|
ChatMessageContent(role=AuthorRole.USER, items=[TextContent(text="Hello")]),
|
|
ChatMessageContent(role=AuthorRole.USER, items=[TextContent(text="How are you today?")]),
|
|
],
|
|
{},
|
|
id="azure_text_input",
|
|
),
|
|
pytest.param(
|
|
"azure_custom_client",
|
|
{},
|
|
[
|
|
ChatMessageContent(role=AuthorRole.USER, items=[TextContent(text="Hello")]),
|
|
ChatMessageContent(role=AuthorRole.USER, items=[TextContent(text="How are you today?")]),
|
|
],
|
|
{},
|
|
id="azure_custom_client",
|
|
),
|
|
# endregion
|
|
# region Azure AI Inference
|
|
pytest.param(
|
|
"azure_ai_inference",
|
|
{},
|
|
[
|
|
ChatMessageContent(role=AuthorRole.USER, items=[TextContent(text="Hello")]),
|
|
ChatMessageContent(role=AuthorRole.USER, items=[TextContent(text="How are you today?")]),
|
|
],
|
|
{},
|
|
id="azure_ai_inference_text_input",
|
|
),
|
|
# endregion
|
|
# region Anthropic
|
|
pytest.param(
|
|
"anthropic",
|
|
{},
|
|
[
|
|
ChatMessageContent(role=AuthorRole.USER, items=[TextContent(text="Hello")]),
|
|
ChatMessageContent(role=AuthorRole.USER, items=[TextContent(text="How are you today?")]),
|
|
],
|
|
{},
|
|
marks=pytest.mark.skipif(not anthropic_setup, reason="Anthropic Environment Variables not set"),
|
|
id="anthropic_text_input",
|
|
),
|
|
# endregion
|
|
# region Mistral AI
|
|
pytest.param(
|
|
"mistral_ai",
|
|
{},
|
|
[
|
|
ChatMessageContent(role=AuthorRole.USER, items=[TextContent(text="Hello")]),
|
|
ChatMessageContent(role=AuthorRole.USER, items=[TextContent(text="How are you today?")]),
|
|
],
|
|
{},
|
|
marks=pytest.mark.skipif(not mistral_ai_setup, reason="Mistral AI Environment Variables not set"),
|
|
id="mistral_ai_text_input",
|
|
),
|
|
# endregion
|
|
# region Ollama
|
|
pytest.param(
|
|
"ollama",
|
|
{},
|
|
[
|
|
ChatMessageContent(role=AuthorRole.USER, items=[TextContent(text="Hello")]),
|
|
ChatMessageContent(role=AuthorRole.USER, items=[TextContent(text="How are you today?")]),
|
|
],
|
|
{},
|
|
marks=(
|
|
pytest.mark.skipif(not ollama_setup, reason="Need local Ollama setup"),
|
|
pytest.mark.ollama,
|
|
),
|
|
id="ollama_text_input",
|
|
),
|
|
# endregion
|
|
# region Onnx Gen AI
|
|
pytest.param(
|
|
"onnx_gen_ai",
|
|
{},
|
|
[
|
|
ChatMessageContent(role=AuthorRole.USER, items=[TextContent(text="Hello")]),
|
|
ChatMessageContent(role=AuthorRole.USER, items=[TextContent(text="How are you today?")]),
|
|
],
|
|
{},
|
|
marks=(
|
|
pytest.mark.skipif(not onnx_setup, reason="Need a Onnx Model setup"),
|
|
pytest.mark.onnx,
|
|
),
|
|
id="onnx_gen_ai",
|
|
),
|
|
# endregion
|
|
# region Google AI
|
|
pytest.param(
|
|
"google_ai",
|
|
{"top_p": 0.9, "temperature": 0.7},
|
|
[
|
|
ChatMessageContent(role=AuthorRole.USER, items=[TextContent(text="Hello")]),
|
|
ChatMessageContent(role=AuthorRole.USER, items=[TextContent(text="How are you today?")]),
|
|
],
|
|
{},
|
|
marks=[
|
|
pytest.mark.skip(reason="Skipping due to occasional throttling from Google AI."),
|
|
# pytest.mark.skipif(not google_ai_setup, reason="Need Google AI setup"),
|
|
],
|
|
id="google_ai_text_input",
|
|
),
|
|
# endregion
|
|
# region Vertex AI
|
|
pytest.param(
|
|
"vertex_ai",
|
|
{"top_p": 0.9, "temperature": 0.7},
|
|
[
|
|
ChatMessageContent(role=AuthorRole.USER, items=[TextContent(text="Hello")]),
|
|
ChatMessageContent(role=AuthorRole.USER, items=[TextContent(text="How are you today?")]),
|
|
],
|
|
{},
|
|
marks=pytest.mark.skipif(not vertex_ai_setup, reason="Vertex AI Environment Variables not set"),
|
|
id="vertex_ai_text_input",
|
|
),
|
|
# endregion
|
|
# region Bedrock
|
|
pytest.param(
|
|
"bedrock_amazon_nova",
|
|
{},
|
|
[
|
|
ChatMessageContent(role=AuthorRole.USER, items=[TextContent(text="Hello")]),
|
|
ChatMessageContent(role=AuthorRole.USER, items=[TextContent(text="How are you today?")]),
|
|
],
|
|
{},
|
|
id="bedrock_amazon_nova_text_input",
|
|
),
|
|
pytest.param(
|
|
"bedrock_ai21labs",
|
|
{},
|
|
[
|
|
ChatMessageContent(role=AuthorRole.USER, items=[TextContent(text="Hello")]),
|
|
ChatMessageContent(role=AuthorRole.USER, items=[TextContent(text="How are you today?")]),
|
|
],
|
|
{},
|
|
marks=pytest.mark.skip(reason="Skipping due to occasional throttling from Bedrock."),
|
|
id="bedrock_ai21labs_text_input",
|
|
),
|
|
pytest.param(
|
|
"bedrock_anthropic_claude",
|
|
{},
|
|
[
|
|
ChatMessageContent(role=AuthorRole.USER, items=[TextContent(text="Hello")]),
|
|
ChatMessageContent(role=AuthorRole.USER, items=[TextContent(text="How are you today?")]),
|
|
],
|
|
{},
|
|
marks=pytest.mark.skip(reason="Skipping due to occasional throttling from Bedrock."),
|
|
id="bedrock_anthropic_claude_text_input",
|
|
),
|
|
pytest.param(
|
|
"bedrock_cohere_command",
|
|
{},
|
|
[
|
|
ChatMessageContent(role=AuthorRole.USER, items=[TextContent(text="Hello")]),
|
|
ChatMessageContent(role=AuthorRole.USER, items=[TextContent(text="How are you today?")]),
|
|
],
|
|
{},
|
|
marks=pytest.mark.skip(reason="Skipping due to occasional throttling from Bedrock."),
|
|
id="bedrock_cohere_command_text_input",
|
|
),
|
|
pytest.param(
|
|
"bedrock_meta_llama",
|
|
{},
|
|
[
|
|
ChatMessageContent(role=AuthorRole.USER, items=[TextContent(text="Hello")]),
|
|
ChatMessageContent(role=AuthorRole.USER, items=[TextContent(text="How are you today?")]),
|
|
],
|
|
{},
|
|
marks=pytest.mark.skip(reason="Skipping due to occasional throttling from Bedrock."),
|
|
id="bedrock_meta_llama_text_input",
|
|
),
|
|
pytest.param(
|
|
"bedrock_mistralai",
|
|
{},
|
|
[
|
|
ChatMessageContent(role=AuthorRole.USER, items=[TextContent(text="Hello")]),
|
|
ChatMessageContent(role=AuthorRole.USER, items=[TextContent(text="How are you today?")]),
|
|
],
|
|
{},
|
|
marks=pytest.mark.skip(reason="Skipping due to occasional throttling from Bedrock."),
|
|
id="bedrock_mistralai_text_input",
|
|
),
|
|
# endregion
|
|
],
|
|
)
|
|
|
|
|
|
class TestChatCompletion(ChatCompletionTestBase):
|
|
"""Test Chat Completions.
|
|
|
|
This only tests if the services can return text completions given text inputs.
|
|
"""
|
|
|
|
@override
|
|
async def test_completion(
|
|
self,
|
|
kernel: Kernel,
|
|
service_id: str,
|
|
services: dict[str, tuple[ServiceType, type[PromptExecutionSettings]]],
|
|
execution_settings_kwargs: dict[str, Any],
|
|
inputs: list[ChatMessageContent],
|
|
kwargs: dict[str, Any],
|
|
):
|
|
await self._test_helper(
|
|
kernel,
|
|
service_id,
|
|
services,
|
|
execution_settings_kwargs,
|
|
inputs,
|
|
False,
|
|
)
|
|
|
|
@override
|
|
async def test_streaming_completion(
|
|
self,
|
|
kernel: Kernel,
|
|
service_id: str,
|
|
services: dict[str, tuple[ServiceType, type[PromptExecutionSettings]]],
|
|
execution_settings_kwargs: dict[str, Any],
|
|
inputs: list[ChatMessageContent],
|
|
kwargs: dict[str, Any],
|
|
):
|
|
await self._test_helper(
|
|
kernel,
|
|
service_id,
|
|
services,
|
|
execution_settings_kwargs,
|
|
inputs,
|
|
True,
|
|
)
|
|
|
|
@override
|
|
def evaluate(self, test_target: Any, **kwargs):
|
|
inputs = kwargs.get("inputs")
|
|
assert isinstance(inputs, list)
|
|
assert len(test_target) == len(inputs) * 2
|
|
for i in range(len(inputs)):
|
|
message = test_target[i * 2 + 1]
|
|
assert message.items, "No items in message"
|
|
assert len(message.items) == 1, "Unexpected number of items in message"
|
|
assert isinstance(message.items[0], TextContent), "Unexpected message item type"
|
|
assert message.items[0].text, "Empty message text"
|
|
|
|
async def _test_helper(
|
|
self,
|
|
kernel: Kernel,
|
|
service_id: str,
|
|
services: dict[str, tuple[ServiceType, type[PromptExecutionSettings]]],
|
|
execution_settings_kwargs: dict[str, Any],
|
|
inputs: list[ChatMessageContent],
|
|
stream: bool,
|
|
):
|
|
self.setup(kernel)
|
|
service, settings_type = services[service_id]
|
|
if service is None:
|
|
pytest.skip(f"Service {service_id} not set up")
|
|
|
|
history = ChatHistory()
|
|
for message in inputs:
|
|
history.add_message(message)
|
|
|
|
cmc: ChatMessageContent | None = await retry(
|
|
partial(
|
|
self.get_chat_completion_response,
|
|
kernel=kernel,
|
|
service=service,
|
|
execution_settings=settings_type(**execution_settings_kwargs),
|
|
chat_history=history,
|
|
stream=stream,
|
|
),
|
|
retries=5,
|
|
name="get_chat_completion_response",
|
|
)
|
|
if cmc:
|
|
history.add_message(cmc)
|
|
|
|
self.evaluate(history.messages, inputs=inputs)
|