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
4 changes: 2 additions & 2 deletions python/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ faiss = [
"faiss-cpu>=1.10.0"
]
google = [
"google-cloud-aiplatform == 1.97.0",
"google-cloud-aiplatform ~= 1.114.0",
"google-generativeai ~= 0.8"
]
hugging_face = [
Expand Down Expand Up @@ -154,7 +154,7 @@ usearch = [
"pyarrow >= 12.0,< 22.0"
]
weaviate = [
"weaviate-client>=4.10,<5.0,!=4.16.7",
"weaviate-client>=4.17.0,<5.0",
]

[tool.uv]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,8 @@
import logging
from typing import TYPE_CHECKING

from google.cloud.aiplatform_v1beta1.types.content import Blob, Candidate, Part
from google.cloud.aiplatform_v1beta1.types.tool import FunctionCall, FunctionResponse
from vertexai.generative_models import FunctionDeclaration, Tool, ToolConfig
from google.cloud.aiplatform_v1beta1.types.content import Candidate
from vertexai.generative_models import FunctionDeclaration, Part, Tool, ToolConfig

from semantic_kernel.connectors.ai.function_choice_behavior import FunctionChoiceType
from semantic_kernel.connectors.ai.google.shared_utils import (
Expand Down Expand Up @@ -63,7 +62,7 @@ def format_user_message(message: ChatMessageContent) -> list[Part]:
parts: list[Part] = []
for item in message.items:
if isinstance(item, TextContent):
parts.append(Part(text=message.content))
parts.append(Part.from_text(message.content))
elif isinstance(item, ImageContent):
parts.append(_create_image_part(item))
else:
Expand All @@ -88,16 +87,15 @@ def format_assistant_message(message: ChatMessageContent) -> list[Part]:
for item in message.items:
if isinstance(item, TextContent):
if item.text:
parts.append(Part(text=item.text))
parts.append(Part.from_text(item.text))
elif isinstance(item, FunctionCallContent):
parts.append(
Part(
function_call=FunctionCall(
name=item.name,
# Convert the arguments to a dictionary if it is a string
args=json.loads(item.arguments) if isinstance(item.arguments, str) else item.arguments,
)
)
Part.from_dict({
"function_call": {
"name": item.name,
"args": json.loads(item.arguments) if isinstance(item.arguments, str) else item.arguments,
}
})
)
elif isinstance(item, ImageContent):
parts.append(_create_image_part(item))
Expand All @@ -124,14 +122,11 @@ def format_tool_message(message: ChatMessageContent) -> list[Part]:
if isinstance(item, FunctionResultContent):
gemini_function_name = item.custom_fully_qualified_name(GEMINI_FUNCTION_NAME_SEPARATOR)
parts.append(
Part(
function_response=FunctionResponse(
name=gemini_function_name,
response={
"name": gemini_function_name,
"content": str(item.result),
},
)
Part.from_function_response(
gemini_function_name,
{
"content": str(item.result),
},
)
)

Expand Down Expand Up @@ -177,7 +172,7 @@ def update_settings_from_function_choice_configuration(

def _create_image_part(image_content: ImageContent) -> Part:
if image_content.data_uri:
return Part(inline_data=Blob(mime_type=image_content.mime_type, data=image_content.data))
return Part.from_data(image_content.data, image_content.mime_type) # type: ignore[arg-type]

# The Google AI API doesn't support images from arbitrary URIs:
# https://github.com/google-gemini/generative-ai-python/issues/357
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,8 @@
from typing_extensions import override # pragma: no cover

import vertexai
from google.cloud.aiplatform_v1beta1.types.content import Content
from pydantic import ValidationError
from vertexai.generative_models import Candidate, GenerationResponse, GenerativeModel
from vertexai.generative_models import Candidate, Content, GenerationResponse, GenerativeModel

from semantic_kernel.connectors.ai.chat_completion_client_base import ChatCompletionClientBase
from semantic_kernel.connectors.ai.completion_usage import CompletionUsage
Expand Down Expand Up @@ -125,6 +124,7 @@ async def _inner_get_chat_message_contents(
assert isinstance(settings, VertexAIChatPromptExecutionSettings) # nosec

vertexai.init(project=self.service_settings.project_id, location=self.service_settings.region)
assert self.service_settings.gemini_model_id is not None # nosec
model = GenerativeModel(
self.service_settings.gemini_model_id,
system_instruction=filter_system_message(chat_history),
Expand Down Expand Up @@ -154,6 +154,7 @@ async def _inner_get_streaming_chat_message_contents(
assert isinstance(settings, VertexAIChatPromptExecutionSettings) # nosec

vertexai.init(project=self.service_settings.project_id, location=self.service_settings.region)
assert self.service_settings.gemini_model_id is not None # nosec
model = GenerativeModel(
self.service_settings.gemini_model_id,
system_instruction=filter_system_message(chat_history),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ async def _inner_get_text_contents(
assert isinstance(settings, VertexAITextPromptExecutionSettings) # nosec

vertexai.init(project=self.service_settings.project_id, location=self.service_settings.region)
assert self.service_settings.gemini_model_id is not None # nosec
model = GenerativeModel(self.service_settings.gemini_model_id)

response: GenerationResponse = await model.generate_content_async(
Expand All @@ -117,6 +118,7 @@ async def _inner_get_streaming_text_contents(
assert isinstance(settings, VertexAITextPromptExecutionSettings) # nosec

vertexai.init(project=self.service_settings.project_id, location=self.service_settings.region)
assert self.service_settings.gemini_model_id is not None # nosec
model = GenerativeModel(self.service_settings.gemini_model_id)

response: AsyncIterable[GenerationResponse] = await model.generate_content_async(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,9 +93,10 @@ async def generate_raw_embeddings(
assert isinstance(settings, VertexAIEmbeddingPromptExecutionSettings) # nosec

vertexai.init(project=self.service_settings.project_id, location=self.service_settings.region)
assert self.service_settings.embedding_model_id is not None # nosec
model = TextEmbeddingModel.from_pretrained(self.service_settings.embedding_model_id)
response: list[TextEmbedding] = await model.get_embeddings_async(
texts,
texts, # type: ignore[arg-type]
**settings.prepare_settings_dict(),
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -155,8 +155,8 @@ def services(self) -> dict[str, tuple[ServiceType | None, type[PromptExecutionSe
OnnxGenAIChatCompletion(template=ONNXTemplate.PHI3V) if onnx_setup else None,
OnnxGenAIPromptExecutionSettings,
),
"bedrock_amazon_titan": (
self._try_create_bedrock_chat_completion_client("amazon.titan-text-premier-v1:0"),
"bedrock_amazon_nova": (
self._try_create_bedrock_chat_completion_client("amazon.nova-lite-v1:0"),
BedrockChatPromptExecutionSettings,
),
"bedrock_ai21labs": (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -182,14 +182,14 @@ class Reasoning(KernelBaseModel):
# endregion
# region Bedrock
pytest.param(
"bedrock_amazon_titan",
"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_titan_text_input",
id="bedrock_amazon_nova_text_input",
),
pytest.param(
"bedrock_ai21labs",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,7 @@ def services(self) -> dict[str, tuple[ServiceType | None, type[PromptExecutionSe
# 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_amazon_titan": (
self._try_create_bedrock_text_completion_client("amazon.titan-text-premier-v1:0"),
self._try_create_bedrock_text_completion_client("amazon.titan-text-express-v1"),
BedrockTextPromptExecutionSettings,
),
"bedrock_anthropic_claude": (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@
from unittest.mock import AsyncMock, patch

import pytest
from google.cloud.aiplatform_v1beta1.types.content import Content
from vertexai.generative_models import GenerativeModel
from vertexai.generative_models import Content, GenerativeModel

from semantic_kernel.connectors.ai.function_choice_behavior import FunctionChoiceBehavior
from semantic_kernel.connectors.ai.google.vertex_ai.services.vertex_ai_chat_completion import VertexAIChatCompletion
Expand Down Expand Up @@ -95,12 +94,23 @@ async def test_vertex_ai_chat_completion(
chat_history, settings
)

mock_vertex_ai_model_generate_content_async.assert_called_once_with(
contents=vertex_ai_chat_completion._prepare_chat_history_for_request(chat_history),
generation_config=settings.prepare_settings_dict(),
tools=None,
tool_config=None,
)
# Verify the call was made once
mock_vertex_ai_model_generate_content_async.assert_called_once()

# Get the actual call arguments
call_args = mock_vertex_ai_model_generate_content_async.call_args

# Verify the contents
contents = call_args.kwargs["contents"]
assert len(contents) == 1
assert contents[0].role == "user"
assert len(contents[0].parts) == 1
assert contents[0].parts[0].text == "test_prompt"

# Verify other arguments
assert call_args.kwargs["generation_config"] == settings.prepare_settings_dict()
assert call_args.kwargs["tools"] is None
assert call_args.kwargs["tool_config"] is None
assert len(responses) == 1
assert responses[0].role == "assistant"
assert responses[0].content == mock_vertex_ai_chat_completion_response.candidates[0].content.parts[0].text
Expand Down Expand Up @@ -188,12 +198,23 @@ async def test_vertex_ai_chat_completion_with_function_choice_behavior_no_tool_c
kernel=kernel,
)

mock_vertex_ai_model_generate_content_async.assert_awaited_once_with(
contents=vertex_ai_chat_completion._prepare_chat_history_for_request(chat_history),
generation_config=settings.prepare_settings_dict(),
tools=None,
tool_config=None,
)
# Verify the call was made once
mock_vertex_ai_model_generate_content_async.assert_awaited_once()

# Get the actual call arguments
call_args = mock_vertex_ai_model_generate_content_async.await_args

# Verify the contents
contents = call_args.kwargs["contents"]
assert len(contents) == 1
assert contents[0].role == "user"
assert len(contents[0].parts) == 1
assert contents[0].parts[0].text == "test_prompt"

# Verify other arguments
assert call_args.kwargs["generation_config"] == settings.prepare_settings_dict()
assert call_args.kwargs["tools"] is None
assert call_args.kwargs["tool_config"] is None
assert len(responses) == 1
assert responses[0].role == "assistant"
assert responses[0].content == mock_vertex_ai_chat_completion_response.candidates[0].content.parts[0].text
Expand Down Expand Up @@ -225,13 +246,24 @@ async def test_vertex_ai_streaming_chat_completion(
assert "usage" in messages[0].metadata
assert "prompt_feedback" in messages[0].metadata

mock_vertex_ai_model_generate_content_async.assert_called_once_with(
contents=vertex_ai_chat_completion._prepare_chat_history_for_request(chat_history),
generation_config=settings.prepare_settings_dict(),
tools=None,
tool_config=None,
stream=True,
)
# Verify the call was made once
mock_vertex_ai_model_generate_content_async.assert_called_once()

# Get the actual call arguments
call_args = mock_vertex_ai_model_generate_content_async.call_args

# Verify the contents
contents = call_args.kwargs["contents"]
assert len(contents) == 1
assert contents[0].role == "user"
assert len(contents[0].parts) == 1
assert contents[0].parts[0].text == "test_prompt"

# Verify other arguments
assert call_args.kwargs["generation_config"] == settings.prepare_settings_dict()
assert call_args.kwargs["tools"] is None
assert call_args.kwargs["tool_config"] is None
assert call_args.kwargs["stream"] is True


async def test_vertex_ai_streaming_chat_completion_with_function_choice_behavior_fail_verification(
Expand Down Expand Up @@ -328,13 +360,24 @@ async def test_vertex_ai_streaming_chat_completion_with_function_choice_behavior
assert len(messages) == 1
assert messages[0].role == "assistant"

mock_vertex_ai_model_generate_content_async.assert_awaited_once_with(
contents=vertex_ai_chat_completion._prepare_chat_history_for_request(chat_history),
generation_config=settings.prepare_settings_dict(),
tools=None,
tool_config=None,
stream=True,
)
# Verify the call was made once
mock_vertex_ai_model_generate_content_async.assert_awaited_once()

# Get the actual call arguments
call_args = mock_vertex_ai_model_generate_content_async.await_args

# Verify the contents
contents = call_args.kwargs["contents"]
assert len(contents) == 1
assert contents[0].role == "user"
assert len(contents[0].parts) == 1
assert contents[0].parts[0].text == "test_prompt"

# Verify other arguments
assert call_args.kwargs["generation_config"] == settings.prepare_settings_dict()
assert call_args.kwargs["tools"] is None
assert call_args.kwargs["tool_config"] is None
assert call_args.kwargs["stream"] is True


# endregion streaming chat completion
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# Copyright (c) Microsoft. All rights reserved.

import pytest
from google.cloud.aiplatform_v1beta1.types.content import Candidate, Part
from google.cloud.aiplatform_v1beta1.types.content import Candidate
from vertexai.generative_models import Part

from semantic_kernel.connectors.ai.google.vertex_ai.services.utils import (
finish_reason_from_vertex_ai_to_semantic_kernel,
Expand Down
Loading
Loading