Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/_typos.toml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ ist = "ist" # German language
dall = "dall" # OpenAI model name
pn = "pn" # Kiota parameter
nin = "nin" # MongoDB "not in" operator
asend = "asend" # Async generator method

[default.extend-identifiers]
ags = "ags" # Azure Graph Service
Expand Down
18 changes: 13 additions & 5 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
{
"prettier.enable": true,
"css.lint.validProperties": ["composes"],
"css.lint.validProperties": [
"composes"
],
"editor.formatOnType": true,
"editor.formatOnSave": true,
"editor.formatOnPaste": true,
Expand All @@ -13,9 +15,13 @@
"editor.bracketPairColorization.enabled": true,
"editor.guides.bracketPairs": "active",
"python.formatting.provider": "autopep8",
"python.formatting.autopep8Args": ["--max-line-length=120"],
"python.formatting.autopep8Args": [
"--max-line-length=120"
],
"notebook.output.textLineLimit": 500,
"python.analysis.extraPaths": ["./python/src"],
"python.analysis.extraPaths": [
"./python/src"
],
"javascript.updateImportsOnFileMove.enabled": "always",
"search.exclude": {
"**/node_modules": true,
Expand Down Expand Up @@ -93,7 +99,9 @@
"java.debug.settings.onBuildFailureProceed": true,
"java.compile.nullAnalysis.mode": "disabled",
"dotnet.defaultSolution": "dotnet\\SK-dotnet.sln",
"python.testing.pytestArgs": ["python/tests"],
"python.testing.pytestArgs": [
"python/tests"
],
"python.testing.unittestEnabled": false,
"python.testing.pytestEnabled": true
}
}
5 changes: 5 additions & 0 deletions python/.vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,9 @@
"python.analysis.extraPaths": [
"${workspaceFolder}/samples/learn_resources"
],
"python.testing.pytestArgs": [
"tests"
],
"python.testing.unittestEnabled": false,
"python.testing.pytestEnabled": true
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
# Copyright (c) Microsoft. All rights reserved.

from unittest.mock import patch
from unittest.mock import AsyncMock, MagicMock, patch

import httpx
import pytest
from ollama import AsyncClient, ChatResponse, Message

from semantic_kernel.connectors.ai.function_choice_behavior import FunctionChoiceBehavior
from semantic_kernel.connectors.ai.ollama.ollama_prompt_execution_settings import OllamaChatPromptExecutionSettings
from semantic_kernel.connectors.ai.ollama.services.ollama_chat_completion import OllamaChatCompletion
from semantic_kernel.contents.chat_history import ChatHistory
from semantic_kernel.contents.chat_message_content import ChatMessageContent
from semantic_kernel.exceptions.service_exceptions import (
ServiceInitializationError,
ServiceInvalidExecutionSettingsError,
Expand Down Expand Up @@ -228,3 +232,137 @@ async def test_streaming_chat_completion_wrong_return_type(
OllamaChatPromptExecutionSettings(service_id=service_id, options=default_options),
):
pass


@pytest.fixture
async def ollama_chat_completion(model_id):
with patch(
"ollama.AsyncClient",
spec=AsyncClient,
) as mock_client:
mock_client.return_value = AsyncMock(spec=AsyncClient)
yield OllamaChatCompletion(client=mock_client, ai_model_id=model_id)


def test_initialization_failure():
"""Test initialization with missing chat model id raises an error."""
with patch(
"semantic_kernel.connectors.ai.ollama.ollama_settings.OllamaSettings.create",
side_effect=ServiceInitializationError,
) as mock_create:
with pytest.raises(ServiceInitializationError):
OllamaChatCompletion(ai_model_id=None)

mock_create.assert_called_once()


async def test_service_url_returns_default_value_without_client(
model_id,
):
"""Test the service_url method returns default value when client has no base_url."""
chat_service = OllamaChatCompletion(ai_model_id=model_id, client=None)
assert chat_service.service_url() == "http://127.0.0.1:11434"


async def test_get_prompt_execution_settings_class(ollama_chat_completion):
"""Test getting the correct prompt execution settings class."""
assert ollama_chat_completion.get_prompt_execution_settings_class() is OllamaChatPromptExecutionSettings


async def test_inner_get_chat_message_contents_valid(ollama_chat_completion):
"""Test _inner_get_chat_message_contents with valid response."""
valid_message = Message(content="Valid content", role="user")
valid_response = ChatResponse(message=valid_message)
ollama_chat_completion.client.chat = AsyncMock(return_value=valid_response)

chat_history = ChatHistory(messages=[])
settings = OllamaChatPromptExecutionSettings()

result = await ollama_chat_completion._inner_get_chat_message_contents(chat_history, settings)

assert len(result) == 1
assert isinstance(result[0], ChatMessageContent)
assert result[0].items[0].text == "Valid content"


async def test_inner_get_chat_message_contents_invalid_response(ollama_chat_completion):
"""Test _inner_get_chat_message_contents raises error on invalid response types."""
invalid_response = MagicMock()
invalid_response.message.content = None
ollama_chat_completion.client.chat.return_value = invalid_response

chat_history = ChatHistory(messages=[])
settings = OllamaChatPromptExecutionSettings()

with pytest.raises(ServiceInvalidResponseError):
await ollama_chat_completion._inner_get_chat_message_contents(chat_history, settings)


@pytest.fixture
async def setup_ollama_chat_completion():
async_client_mock = AsyncMock(spec=AsyncClient)
async_client_mock.chat = AsyncMock()
ollama_chat_completion = OllamaChatCompletion(
service_id="test_service", ai_model_id="test_model_id", client=async_client_mock
)
return ollama_chat_completion, async_client_mock


async def test_ollama_chat_completion_initialization(setup_ollama_chat_completion):
_, _ = setup_ollama_chat_completion # Ensure no exceptions were raised in initialization


async def test_service_url_new(setup_ollama_chat_completion):
ollama_chat_completion, async_client_mock = setup_ollama_chat_completion
# Mock the client's internal structure
async_client_mock._client = AsyncMock(spec=httpx.AsyncClient)
async_client_mock._client.base_url = "http://mocked_base_url"

service_url = ollama_chat_completion.service_url()
assert service_url == "http://mocked_base_url"


async def test_get_prompt_execution_settings_class_new(setup_ollama_chat_completion):
ollama_chat_completion, _ = setup_ollama_chat_completion

assert ollama_chat_completion.get_prompt_execution_settings_class() == OllamaChatPromptExecutionSettings


async def test_inner_get_chat_message_contents(setup_ollama_chat_completion):
ollama_chat_completion, async_client_mock = setup_ollama_chat_completion

async_client_mock.chat.return_value = MagicMock()
chat_history = MagicMock(spec=ChatHistory)
chat_history.messages = []

prompt_settings = OllamaChatPromptExecutionSettings()

with pytest.raises(ServiceInvalidResponseError):
await ollama_chat_completion._inner_get_chat_message_contents(chat_history, prompt_settings)


async def test_inner_get_streaming_chat_message_contents_invalid_response(setup_ollama_chat_completion):
ollama_chat_completion, _ = setup_ollama_chat_completion
chat_history = MagicMock(spec=ChatHistory)
chat_history.messages = []
prompt_settings = OllamaChatPromptExecutionSettings()

with patch.object(
OllamaChatCompletion, "_inner_get_streaming_chat_message_contents", new_callable=MagicMock
) as mocked_inner_stream:
mocked_inner_stream.side_effect = ServiceInvalidResponseError("Invalid streaming response")

with pytest.raises(ServiceInvalidResponseError, match="Invalid streaming response"):
async for _ in ollama_chat_completion._inner_get_streaming_chat_message_contents(
chat_history, prompt_settings, 0
):
pass


async def test_prepare_chat_history_for_request(setup_ollama_chat_completion):
ollama_chat_completion, _ = setup_ollama_chat_completion
chat_history = MagicMock(spec=ChatHistory)
chat_history.messages = []

prepared_history = ollama_chat_completion._prepare_chat_history_for_request(chat_history)
assert prepared_history == []
Loading
Loading