Files
wehub-resource-sync b957a53def
CodeQL / Analyze (csharp) (push) Waiting to run
CodeQL / Analyze (python) (push) Waiting to run
chore: import upstream snapshot with attribution
2026-07-13 13:21:23 +08:00

346 lines
14 KiB
Python

# Copyright (c) Microsoft. All rights reserved.
import sys
from functools import partial
from importlib import util
from typing import Any
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 semantic_kernel import Kernel
from semantic_kernel.connectors.ai.bedrock import BedrockTextCompletion, BedrockTextPromptExecutionSettings
from semantic_kernel.connectors.ai.google.google_ai import GoogleAITextCompletion, GoogleAITextPromptExecutionSettings
from semantic_kernel.connectors.ai.hugging_face import HuggingFacePromptExecutionSettings, HuggingFaceTextCompletion
from semantic_kernel.connectors.ai.ollama import OllamaTextCompletion, OllamaTextPromptExecutionSettings
from semantic_kernel.connectors.ai.onnx import OnnxGenAIPromptExecutionSettings, OnnxGenAITextCompletion
from semantic_kernel.connectors.ai.open_ai import OpenAITextCompletion, OpenAITextPromptExecutionSettings
from semantic_kernel.connectors.ai.prompt_execution_settings import PromptExecutionSettings
from semantic_kernel.connectors.ai.text_completion_client_base import TextCompletionClientBase
from semantic_kernel.contents import StreamingTextContent, TextContent
from tests.integration.completions.completion_test_base import CompletionTestBase, ServiceType
from tests.utils import is_service_setup_for_testing, is_test_running_on_supported_platforms, retry
hugging_face_setup = util.find_spec("torch") is not None
ollama_setup: bool = is_service_setup_for_testing(["OLLAMA_TEXT_MODEL_ID"]) and is_test_running_on_supported_platforms([
"Linux"
])
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_TEXT_MODEL_FOLDER"], raise_if_not_set=False
) # Tests are optional for ONNX
pytestmark = pytest.mark.parametrize(
"service_id, execution_settings_kwargs, inputs, kwargs",
[
pytest.param(
"openai",
{},
["Repeat the word Hello once"],
{},
id="openai_text_completion",
),
pytest.param(
"hf_t2t",
{},
["translate English to Dutch: Hello"],
{},
id="huggingface_text_completion_translation",
),
pytest.param(
"hf_summ",
{},
[
"""Summarize: Whales are fully aquatic, open-ocean animals:
they can feed, mate, give birth, suckle and raise their young at sea.
Whales range in size from the 2.6 metres (8.5 ft) and 135 kilograms (298 lb)
dwarf sperm whale to the 29.9 metres (98 ft) and 190 tonnes (210 short tons) blue whale,
which is the largest known animal that has ever lived. The sperm whale is the largest
toothed predator on Earth. Several whale species exhibit sexual dimorphism,
in that the females are larger than males."""
],
{},
id="huggingface_text_completion_summarization",
),
pytest.param(
"hf_gen",
{},
["Hello, I like sleeping and "],
{},
id="huggingface_text_completion_generation",
),
pytest.param(
"ollama",
{},
["Repeat the word Hello once"],
{},
marks=(
pytest.mark.skip(
reason="Need local Ollama setup" if not ollama_setup else "Ollama responses are not always correct."
),
pytest.mark.ollama,
),
id="ollama_text_completion",
),
pytest.param(
"google_ai",
{},
["Repeat the word Hello once"],
{},
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_completion",
),
pytest.param(
"vertex_ai",
{},
["Repeat the word Hello once"],
{},
marks=pytest.mark.skipif(not vertex_ai_setup, reason="Need VertexAI setup"),
id="vertex_ai_text_completion",
),
pytest.param(
"onnx_gen_ai",
{},
["<|user|>Repeat the word Hello<|end|><|assistant|>"],
{},
marks=(
pytest.mark.skipif(not onnx_setup, reason="Need a Onnx Model setup"),
pytest.mark.onnx,
),
id="onnx_gen_ai_text_completion",
),
pytest.param(
"bedrock_anthropic_claude",
{},
["Repeat the word Hello once"],
{"streaming": False}, # Streaming is not supported for models from this provider
marks=pytest.mark.skip(reason="Skipping due to occasional throttling from Bedrock."),
id="bedrock_anthropic_claude_text_completion",
),
pytest.param(
"bedrock_cohere_command",
{},
["Repeat the word Hello once"],
{"streaming": False}, # Streaming is not supported for models from this provider
marks=pytest.mark.skip(reason="Skipping due to occasional throttling from Bedrock."),
id="bedrock_cohere_command_text_completion",
),
pytest.param(
"bedrock_ai21labs",
{},
["Repeat the word Hello once"],
{"streaming": False}, # Streaming is not supported for models from this provider
marks=pytest.mark.skip(reason="Skipping due to occasional throttling from Bedrock."),
id="bedrock_ai21labs_text_completion",
),
pytest.param(
"bedrock_meta_llama",
{},
["Repeat the word Hello once"],
{"streaming": False}, # Streaming is not supported for models from this provider
marks=pytest.mark.skip(reason="Skipping due to occasional throttling from Bedrock."),
id="bedrock_meta_llama_text_completion",
),
pytest.param(
"bedrock_mistralai",
{},
["Repeat the word Hello once"],
{"streaming": False}, # Streaming is not supported for models from this provider
marks=pytest.mark.skip(reason="Skipping due to occasional throttling from Bedrock."),
id="bedrock_mistralai_text_completion",
),
],
)
class TestTextCompletion(CompletionTestBase):
"""Test class for text completion"""
@override
@pytest.fixture(scope="class")
def services(self) -> dict[str, tuple[ServiceType | None, type[PromptExecutionSettings] | None]]:
"""Get the services to be tested"""
return {
"openai": (OpenAITextCompletion(), OpenAITextPromptExecutionSettings),
"ollama": (OllamaTextCompletion() if ollama_setup else None, OllamaTextPromptExecutionSettings),
"google_ai": (GoogleAITextCompletion() if google_ai_setup else None, GoogleAITextPromptExecutionSettings),
"vertex_ai": (
GoogleAITextCompletion(use_vertexai=True) if vertex_ai_setup else None,
GoogleAITextPromptExecutionSettings,
),
"hf_t2t": (
HuggingFaceTextCompletion(
service_id="patrickvonplaten/t5-tiny-random",
ai_model_id="patrickvonplaten/t5-tiny-random",
task="text2text-generation",
)
if hugging_face_setup
else None,
HuggingFacePromptExecutionSettings,
),
"hf_summ": (
HuggingFaceTextCompletion(
service_id="Falconsai/text_summarization",
ai_model_id="Falconsai/text_summarization",
task="summarization",
)
if hugging_face_setup
else None,
HuggingFacePromptExecutionSettings,
),
"hf_gen": (
HuggingFaceTextCompletion(
service_id="HuggingFaceM4/tiny-random-LlamaForCausalLM",
ai_model_id="HuggingFaceM4/tiny-random-LlamaForCausalLM",
task="text-generation",
)
if hugging_face_setup
else None,
HuggingFacePromptExecutionSettings,
),
"onnx_gen_ai": (
OnnxGenAITextCompletion() if onnx_setup else None,
OnnxGenAIPromptExecutionSettings,
),
# Amazon Bedrock supports models from multiple providers but requests to and responses from the models are
# inconsistent. So we need to test each model separately.
"bedrock_anthropic_claude": (
self._try_create_bedrock_text_completion_client("anthropic.claude-v2"),
BedrockTextPromptExecutionSettings,
),
"bedrock_cohere_command": (
self._try_create_bedrock_text_completion_client("cohere.command-text-v14"),
BedrockTextPromptExecutionSettings,
),
"bedrock_ai21labs": (
self._try_create_bedrock_text_completion_client("ai21.j2-mid-v1"),
BedrockTextPromptExecutionSettings,
),
"bedrock_meta_llama": (
self._try_create_bedrock_text_completion_client("meta.llama3-70b-instruct-v1:0"),
BedrockTextPromptExecutionSettings,
),
"bedrock_mistralai": (
self._try_create_bedrock_text_completion_client("mistral.mistral-7b-instruct-v0:2"),
BedrockTextPromptExecutionSettings,
),
}
async def get_text_completion_response(
self,
service: ServiceType,
execution_settings: PromptExecutionSettings,
prompt: str,
stream: bool,
) -> Any:
"""Get response from the service
Args:
kernel (Kernel): Kernel instance.
service (ChatCompletionClientBase): Chat completion service.
execution_settings (PromptExecutionSettings): Execution settings.
prompt (str): Input string.
stream (bool): Stream flag.
"""
assert isinstance(service, TextCompletionClientBase)
if stream:
response = service.get_streaming_text_content(
prompt=prompt,
settings=execution_settings,
)
parts: list[StreamingTextContent] = [part async for part in response if part is not None]
if parts:
return sum(parts[1:], parts[0])
raise AssertionError("No response")
return await service.get_text_content(
prompt=prompt,
settings=execution_settings,
)
return response
@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[str],
kwargs: dict[str, Any],
) -> None:
await self._test_helper(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[str],
kwargs: dict[str, Any],
):
if "streaming" in kwargs and not kwargs["streaming"]:
pytest.skip("Skipping streaming test")
await self._test_helper(service_id, services, execution_settings_kwargs, inputs, True)
@override
def evaluate(self, test_target: Any, **kwargs):
print(test_target)
if isinstance(test_target, TextContent):
# Test is considered successful if the test_target is not empty
assert test_target.text, "Error: Empty test target"
return
raise AssertionError(f"Unexpected output: {test_target}, type: {type(test_target)}")
async def _test_helper(
self,
service_id: str,
services: dict[str, tuple[ServiceType, type[PromptExecutionSettings]]],
execution_settings_kwargs: dict[str, Any],
inputs: list[str],
stream: bool,
):
service, settings_type = services[service_id]
if not service:
pytest.skip(f"Setup not ready for {service_id if service_id else 'None'}")
for test_input in inputs:
response = await retry(
partial(
self.get_text_completion_response,
service=service,
execution_settings=settings_type(**execution_settings_kwargs),
prompt=test_input,
stream=stream,
),
retries=5,
name="text completions",
)
self.evaluate(response)
def _try_create_bedrock_text_completion_client(self, model_id: str) -> BedrockTextCompletion | None:
try:
return BedrockTextCompletion(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