chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,240 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
|
||||
import os
|
||||
import sys
|
||||
from typing import Annotated
|
||||
|
||||
if sys.version_info >= (3, 12):
|
||||
from typing import override # pragma: no cover
|
||||
else:
|
||||
from typing_extensions import override # pragma: no cover
|
||||
|
||||
import pytest
|
||||
from azure.ai.inference.aio import ChatCompletionsClient
|
||||
from azure.identity import AzureCliCredential
|
||||
from openai import AsyncAzureOpenAI
|
||||
|
||||
from semantic_kernel.connectors.ai.anthropic import AnthropicChatCompletion, AnthropicChatPromptExecutionSettings
|
||||
from semantic_kernel.connectors.ai.azure_ai_inference import (
|
||||
AzureAIInferenceChatCompletion,
|
||||
AzureAIInferenceChatPromptExecutionSettings,
|
||||
)
|
||||
from semantic_kernel.connectors.ai.bedrock import BedrockChatCompletion, BedrockChatPromptExecutionSettings
|
||||
from semantic_kernel.connectors.ai.chat_completion_client_base import ChatCompletionClientBase
|
||||
from semantic_kernel.connectors.ai.google.google_ai import GoogleAIChatCompletion, GoogleAIChatPromptExecutionSettings
|
||||
from semantic_kernel.connectors.ai.mistral_ai import MistralAIChatCompletion, MistralAIChatPromptExecutionSettings
|
||||
from semantic_kernel.connectors.ai.ollama import OllamaChatCompletion, OllamaChatPromptExecutionSettings
|
||||
from semantic_kernel.connectors.ai.onnx import OnnxGenAIChatCompletion, OnnxGenAIPromptExecutionSettings, ONNXTemplate
|
||||
from semantic_kernel.connectors.ai.open_ai import (
|
||||
AzureChatCompletion,
|
||||
AzureChatPromptExecutionSettings,
|
||||
AzureOpenAISettings,
|
||||
OpenAIChatCompletion,
|
||||
OpenAIChatPromptExecutionSettings,
|
||||
)
|
||||
from semantic_kernel.connectors.ai.prompt_execution_settings import PromptExecutionSettings
|
||||
from semantic_kernel.contents.chat_history import ChatHistory
|
||||
from semantic_kernel.contents.chat_message_content import ChatMessageContent
|
||||
from semantic_kernel.contents.streaming_chat_message_content import StreamingChatMessageContent
|
||||
from semantic_kernel.core_plugins.math_plugin import MathPlugin
|
||||
from semantic_kernel.functions.kernel_function_decorator import kernel_function
|
||||
from semantic_kernel.kernel import Kernel
|
||||
from semantic_kernel.kernel_pydantic import KernelBaseModel
|
||||
from semantic_kernel.utils.authentication.entra_id_authentication import get_entra_auth_token
|
||||
from tests.integration.completions.completion_test_base import CompletionTestBase, ServiceType
|
||||
from tests.utils import is_service_setup_for_testing
|
||||
|
||||
# Make sure all services are setup for before running the tests
|
||||
# The following exceptions apply:
|
||||
# 1. OpenAI and Azure OpenAI services are always setup for testing.
|
||||
azure_openai_setup: bool = True
|
||||
# 2. Bedrock services don't use API keys and model providers are tested individually,
|
||||
# so no environment variables are required.
|
||||
mistral_ai_setup: bool = is_service_setup_for_testing(
|
||||
["MISTRALAI_API_KEY", "MISTRALAI_CHAT_MODEL_ID"], raise_if_not_set=False
|
||||
) # We don't have a MistralAI deployment
|
||||
# There is no single model in Ollama that supports both image and tool call in chat completion
|
||||
# We are splitting the Ollama test into three services: chat, image, and tool call. The chat model
|
||||
# can be any model that supports chat completion. Also, Ollama is only available on Linux runners in our pipeline.
|
||||
ollama_setup: bool = is_service_setup_for_testing(["OLLAMA_CHAT_MODEL_ID"])
|
||||
ollama_image_setup: bool = is_service_setup_for_testing(["OLLAMA_CHAT_MODEL_ID_IMAGE"])
|
||||
ollama_tool_call_setup: bool = is_service_setup_for_testing(["OLLAMA_CHAT_MODEL_ID_TOOL_CALL"])
|
||||
google_ai_setup: bool = is_service_setup_for_testing(["GOOGLE_AI_API_KEY", "GOOGLE_AI_GEMINI_MODEL_ID"])
|
||||
vertex_ai_setup: bool = is_service_setup_for_testing([
|
||||
"GOOGLE_AI_CLOUD_PROJECT_ID",
|
||||
"GOOGLE_AI_GEMINI_MODEL_ID",
|
||||
"GOOGLE_AI_CLOUD_REGION",
|
||||
])
|
||||
onnx_setup: bool = is_service_setup_for_testing(
|
||||
["ONNX_GEN_AI_CHAT_MODEL_FOLDER"], raise_if_not_set=False
|
||||
) # Tests are optional for ONNX
|
||||
anthropic_setup: bool = is_service_setup_for_testing(["ANTHROPIC_API_KEY", "ANTHROPIC_CHAT_MODEL_ID"])
|
||||
|
||||
|
||||
# A mock plugin that contains a function that returns a complex object.
|
||||
class PersonDetails(KernelBaseModel):
|
||||
id: str
|
||||
name: str
|
||||
age: int
|
||||
|
||||
|
||||
class PersonSearchPlugin:
|
||||
@kernel_function(name="SearchPerson", description="Search details of a person given their id.")
|
||||
def search_person(
|
||||
self, person_id: Annotated[str, "The person ID to search"]
|
||||
) -> Annotated[PersonDetails, "The details of the person"]:
|
||||
return PersonDetails(id=person_id, name="John Doe", age=42)
|
||||
|
||||
|
||||
class ChatCompletionTestBase(CompletionTestBase):
|
||||
"""Base class for testing completion services."""
|
||||
|
||||
@override
|
||||
@pytest.fixture(
|
||||
scope="function"
|
||||
) # This needs to be scoped to function to avoid resources getting cleaned up after each test
|
||||
def services(self) -> dict[str, tuple[ServiceType | None, type[PromptExecutionSettings] | None]]:
|
||||
azure_openai_setup = True
|
||||
credential = AzureCliCredential()
|
||||
azure_openai_settings = AzureOpenAISettings()
|
||||
endpoint = str(azure_openai_settings.endpoint)
|
||||
deployment_name = azure_openai_settings.chat_deployment_name
|
||||
ad_token = get_entra_auth_token(credential, azure_openai_settings.token_endpoint)
|
||||
if not ad_token:
|
||||
azure_openai_setup = False
|
||||
api_version = azure_openai_settings.api_version
|
||||
azure_custom_client = None
|
||||
azure_ai_inference_client = None
|
||||
if azure_openai_setup:
|
||||
azure_custom_client = AzureChatCompletion(
|
||||
async_client=AsyncAzureOpenAI(
|
||||
azure_endpoint=endpoint,
|
||||
azure_deployment=deployment_name,
|
||||
azure_ad_token=ad_token,
|
||||
api_version=api_version,
|
||||
default_headers={"Test-User-X-ID": "test"},
|
||||
),
|
||||
)
|
||||
assert deployment_name
|
||||
azure_ai_inference_client = AzureAIInferenceChatCompletion(
|
||||
ai_model_id=deployment_name,
|
||||
client=ChatCompletionsClient(
|
||||
endpoint=f"{endpoint.strip('/')}/openai/deployments/{deployment_name}",
|
||||
credential=credential, # type: ignore
|
||||
credential_scopes=["https://cognitiveservices.azure.com/.default"],
|
||||
),
|
||||
)
|
||||
|
||||
return {
|
||||
"openai": (OpenAIChatCompletion(), OpenAIChatPromptExecutionSettings),
|
||||
"azure": (
|
||||
AzureChatCompletion(credential=credential) if azure_openai_setup else None,
|
||||
AzureChatPromptExecutionSettings,
|
||||
),
|
||||
"azure_custom_client": (azure_custom_client, AzureChatPromptExecutionSettings),
|
||||
"azure_ai_inference": (azure_ai_inference_client, AzureAIInferenceChatPromptExecutionSettings),
|
||||
"anthropic": (AnthropicChatCompletion() if anthropic_setup else None, AnthropicChatPromptExecutionSettings),
|
||||
"mistral_ai": (
|
||||
MistralAIChatCompletion() if mistral_ai_setup else None,
|
||||
MistralAIChatPromptExecutionSettings,
|
||||
),
|
||||
"ollama": (OllamaChatCompletion() if ollama_setup else None, OllamaChatPromptExecutionSettings),
|
||||
"ollama_image": (
|
||||
OllamaChatCompletion(ai_model_id=os.environ["OLLAMA_CHAT_MODEL_ID_IMAGE"])
|
||||
if ollama_image_setup
|
||||
else None,
|
||||
OllamaChatPromptExecutionSettings,
|
||||
),
|
||||
"ollama_tool_call": (
|
||||
OllamaChatCompletion(ai_model_id=os.environ["OLLAMA_CHAT_MODEL_ID_TOOL_CALL"])
|
||||
if ollama_tool_call_setup
|
||||
else None,
|
||||
OllamaChatPromptExecutionSettings,
|
||||
),
|
||||
"google_ai": (GoogleAIChatCompletion() if google_ai_setup else None, GoogleAIChatPromptExecutionSettings),
|
||||
"vertex_ai": (
|
||||
GoogleAIChatCompletion(use_vertexai=True) if vertex_ai_setup else None,
|
||||
GoogleAIChatPromptExecutionSettings,
|
||||
),
|
||||
"onnx_gen_ai": (
|
||||
OnnxGenAIChatCompletion(template=ONNXTemplate.PHI3V) if onnx_setup else None,
|
||||
OnnxGenAIPromptExecutionSettings,
|
||||
),
|
||||
"bedrock_amazon_nova": (
|
||||
self._try_create_bedrock_chat_completion_client("amazon.nova-lite-v1:0"),
|
||||
BedrockChatPromptExecutionSettings,
|
||||
),
|
||||
"bedrock_ai21labs": (
|
||||
self._try_create_bedrock_chat_completion_client("ai21.jamba-1-5-mini-v1:0"),
|
||||
BedrockChatPromptExecutionSettings,
|
||||
),
|
||||
"bedrock_anthropic_claude": (
|
||||
self._try_create_bedrock_chat_completion_client("anthropic.claude-3-sonnet-20240229-v1:0"),
|
||||
BedrockChatPromptExecutionSettings,
|
||||
),
|
||||
"bedrock_cohere_command": (
|
||||
self._try_create_bedrock_chat_completion_client("cohere.command-r-v1:0"),
|
||||
BedrockChatPromptExecutionSettings,
|
||||
),
|
||||
"bedrock_meta_llama": (
|
||||
self._try_create_bedrock_chat_completion_client("meta.llama3-70b-instruct-v1:0"),
|
||||
BedrockChatPromptExecutionSettings,
|
||||
),
|
||||
"bedrock_mistralai": (
|
||||
self._try_create_bedrock_chat_completion_client("mistral.mistral-small-2402-v1:0"),
|
||||
BedrockChatPromptExecutionSettings,
|
||||
),
|
||||
}
|
||||
|
||||
def setup(self, kernel: Kernel):
|
||||
"""Setup the kernel with the completion service and function."""
|
||||
kernel.add_plugin(MathPlugin(), plugin_name="math")
|
||||
kernel.add_plugin(PersonSearchPlugin(), plugin_name="search")
|
||||
|
||||
async def get_chat_completion_response(
|
||||
self,
|
||||
kernel: Kernel,
|
||||
service: ServiceType,
|
||||
execution_settings: PromptExecutionSettings,
|
||||
chat_history: ChatHistory,
|
||||
stream: bool,
|
||||
) -> ChatMessageContent | StreamingChatMessageContent | None:
|
||||
"""Get response from the service
|
||||
|
||||
Args:
|
||||
kernel (Kernel): Kernel instance.
|
||||
service (ChatCompletionClientBase): Chat completion service.
|
||||
execution_settings (PromptExecutionSettings): Execution settings.
|
||||
input (str): Input string.
|
||||
stream (bool): Stream flag.
|
||||
"""
|
||||
assert isinstance(service, ChatCompletionClientBase)
|
||||
if not stream:
|
||||
return await service.get_chat_message_content(
|
||||
chat_history,
|
||||
execution_settings,
|
||||
kernel=kernel,
|
||||
)
|
||||
parts: list[StreamingChatMessageContent] = [
|
||||
part
|
||||
async for part in service.get_streaming_chat_message_content(
|
||||
chat_history,
|
||||
execution_settings,
|
||||
kernel=kernel,
|
||||
)
|
||||
if part
|
||||
]
|
||||
if parts:
|
||||
return sum(parts[1:], parts[0])
|
||||
raise AssertionError("No response")
|
||||
|
||||
def _try_create_bedrock_chat_completion_client(self, model_id: str) -> BedrockChatCompletion | None:
|
||||
try:
|
||||
return BedrockChatCompletion(model_id=model_id)
|
||||
except Exception as ex:
|
||||
from conftest import logger
|
||||
|
||||
logger.warning(ex)
|
||||
# Returning None so that the test that uses this service will be skipped
|
||||
return None
|
||||
Reference in New Issue
Block a user