Skip to content

Commit 1f8fea2

Browse files
TaoChenOSUCopilotmoonbox3
authored
Python: Fix ai connector integration tests (#13311)
### Motivation and Context <!-- Thank you for your contribution to the semantic-kernel repo! Please help reviewers and future users, providing the following information: 1. Why is this change required? 2. What problem does it solve? 3. What scenario does it contribute to? 4. If it fixes an open issue, please link to the issue here. --> Vertex AI and Bedrock integration tests have been failing. ### Description <!-- Describe your changes, the overall approach, the underlying design. These notes will help understanding how your code works. Thanks! --> 1. This PR fixes the vertex ai integration tests by upgrading the Google AI SDK version. 2. This PR fixes the Bedrock integration tests by upgrading the models. ### Contribution Checklist <!-- Before submitting this PR, please make sure: --> - [x] The code builds clean without any errors or warnings - [x] The PR follows the [SK Contribution Guidelines](https://github.com/microsoft/semantic-kernel/blob/main/CONTRIBUTING.md) and the [pre-submission formatting script](https://github.com/microsoft/semantic-kernel/blob/main/CONTRIBUTING.md#development-scripts) raises no violations - [x] All unit tests pass, and I have added new tests where possible - [x] I didn't break anyone 😄 --------- Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com> Co-authored-by: moonbox3 <35585003+moonbox3@users.noreply.github.com>
1 parent f5fb44e commit 1f8fea2

File tree

11 files changed

+545
-461
lines changed

11 files changed

+545
-461
lines changed

python/pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ faiss = [
9292
"faiss-cpu>=1.10.0"
9393
]
9494
google = [
95-
"google-cloud-aiplatform == 1.97.0",
95+
"google-cloud-aiplatform ~= 1.114.0",
9696
"google-generativeai ~= 0.8"
9797
]
9898
hugging_face = [
@@ -154,7 +154,7 @@ usearch = [
154154
"pyarrow >= 12.0,< 22.0"
155155
]
156156
weaviate = [
157-
"weaviate-client>=4.10,<5.0,!=4.16.7",
157+
"weaviate-client>=4.17.0,<5.0",
158158
]
159159

160160
[tool.uv]

python/semantic_kernel/connectors/ai/google/vertex_ai/services/utils.py

Lines changed: 16 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,8 @@
44
import logging
55
from typing import TYPE_CHECKING
66

7-
from google.cloud.aiplatform_v1beta1.types.content import Blob, Candidate, Part
8-
from google.cloud.aiplatform_v1beta1.types.tool import FunctionCall, FunctionResponse
9-
from vertexai.generative_models import FunctionDeclaration, Tool, ToolConfig
7+
from google.cloud.aiplatform_v1beta1.types.content import Candidate
8+
from vertexai.generative_models import FunctionDeclaration, Part, Tool, ToolConfig
109

1110
from semantic_kernel.connectors.ai.function_choice_behavior import FunctionChoiceType
1211
from semantic_kernel.connectors.ai.google.shared_utils import (
@@ -63,7 +62,7 @@ def format_user_message(message: ChatMessageContent) -> list[Part]:
6362
parts: list[Part] = []
6463
for item in message.items:
6564
if isinstance(item, TextContent):
66-
parts.append(Part(text=message.content))
65+
parts.append(Part.from_text(message.content))
6766
elif isinstance(item, ImageContent):
6867
parts.append(_create_image_part(item))
6968
else:
@@ -88,16 +87,15 @@ def format_assistant_message(message: ChatMessageContent) -> list[Part]:
8887
for item in message.items:
8988
if isinstance(item, TextContent):
9089
if item.text:
91-
parts.append(Part(text=item.text))
90+
parts.append(Part.from_text(item.text))
9291
elif isinstance(item, FunctionCallContent):
9392
parts.append(
94-
Part(
95-
function_call=FunctionCall(
96-
name=item.name,
97-
# Convert the arguments to a dictionary if it is a string
98-
args=json.loads(item.arguments) if isinstance(item.arguments, str) else item.arguments,
99-
)
100-
)
93+
Part.from_dict({
94+
"function_call": {
95+
"name": item.name,
96+
"args": json.loads(item.arguments) if isinstance(item.arguments, str) else item.arguments,
97+
}
98+
})
10199
)
102100
elif isinstance(item, ImageContent):
103101
parts.append(_create_image_part(item))
@@ -124,14 +122,11 @@ def format_tool_message(message: ChatMessageContent) -> list[Part]:
124122
if isinstance(item, FunctionResultContent):
125123
gemini_function_name = item.custom_fully_qualified_name(GEMINI_FUNCTION_NAME_SEPARATOR)
126124
parts.append(
127-
Part(
128-
function_response=FunctionResponse(
129-
name=gemini_function_name,
130-
response={
131-
"name": gemini_function_name,
132-
"content": str(item.result),
133-
},
134-
)
125+
Part.from_function_response(
126+
gemini_function_name,
127+
{
128+
"content": str(item.result),
129+
},
135130
)
136131
)
137132

@@ -177,7 +172,7 @@ def update_settings_from_function_choice_configuration(
177172

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

182177
# The Google AI API doesn't support images from arbitrary URIs:
183178
# https://github.com/google-gemini/generative-ai-python/issues/357

python/semantic_kernel/connectors/ai/google/vertex_ai/services/vertex_ai_chat_completion.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,8 @@
1010
from typing_extensions import override # pragma: no cover
1111

1212
import vertexai
13-
from google.cloud.aiplatform_v1beta1.types.content import Content
1413
from pydantic import ValidationError
15-
from vertexai.generative_models import Candidate, GenerationResponse, GenerativeModel
14+
from vertexai.generative_models import Candidate, Content, GenerationResponse, GenerativeModel
1615

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

127126
vertexai.init(project=self.service_settings.project_id, location=self.service_settings.region)
127+
assert self.service_settings.gemini_model_id is not None # nosec
128128
model = GenerativeModel(
129129
self.service_settings.gemini_model_id,
130130
system_instruction=filter_system_message(chat_history),
@@ -154,6 +154,7 @@ async def _inner_get_streaming_chat_message_contents(
154154
assert isinstance(settings, VertexAIChatPromptExecutionSettings) # nosec
155155

156156
vertexai.init(project=self.service_settings.project_id, location=self.service_settings.region)
157+
assert self.service_settings.gemini_model_id is not None # nosec
157158
model = GenerativeModel(
158159
self.service_settings.gemini_model_id,
159160
system_instruction=filter_system_message(chat_history),

python/semantic_kernel/connectors/ai/google/vertex_ai/services/vertex_ai_text_completion.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ async def _inner_get_text_contents(
9696
assert isinstance(settings, VertexAITextPromptExecutionSettings) # nosec
9797

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

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

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

122124
response: AsyncIterable[GenerationResponse] = await model.generate_content_async(

python/semantic_kernel/connectors/ai/google/vertex_ai/services/vertex_ai_text_embedding.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,9 +93,10 @@ async def generate_raw_embeddings(
9393
assert isinstance(settings, VertexAIEmbeddingPromptExecutionSettings) # nosec
9494

9595
vertexai.init(project=self.service_settings.project_id, location=self.service_settings.region)
96+
assert self.service_settings.embedding_model_id is not None # nosec
9697
model = TextEmbeddingModel.from_pretrained(self.service_settings.embedding_model_id)
9798
response: list[TextEmbedding] = await model.get_embeddings_async(
98-
texts,
99+
texts, # type: ignore[arg-type]
99100
**settings.prepare_settings_dict(),
100101
)
101102

python/tests/integration/completions/chat_completion_test_base.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -155,8 +155,8 @@ def services(self) -> dict[str, tuple[ServiceType | None, type[PromptExecutionSe
155155
OnnxGenAIChatCompletion(template=ONNXTemplate.PHI3V) if onnx_setup else None,
156156
OnnxGenAIPromptExecutionSettings,
157157
),
158-
"bedrock_amazon_titan": (
159-
self._try_create_bedrock_chat_completion_client("amazon.titan-text-premier-v1:0"),
158+
"bedrock_amazon_nova": (
159+
self._try_create_bedrock_chat_completion_client("amazon.nova-lite-v1:0"),
160160
BedrockChatPromptExecutionSettings,
161161
),
162162
"bedrock_ai21labs": (

python/tests/integration/completions/test_chat_completions.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -182,14 +182,14 @@ class Reasoning(KernelBaseModel):
182182
# endregion
183183
# region Bedrock
184184
pytest.param(
185-
"bedrock_amazon_titan",
185+
"bedrock_amazon_nova",
186186
{},
187187
[
188188
ChatMessageContent(role=AuthorRole.USER, items=[TextContent(text="Hello")]),
189189
ChatMessageContent(role=AuthorRole.USER, items=[TextContent(text="How are you today?")]),
190190
],
191191
{},
192-
id="bedrock_amazon_titan_text_input",
192+
id="bedrock_amazon_nova_text_input",
193193
),
194194
pytest.param(
195195
"bedrock_ai21labs",

python/tests/integration/completions/test_text_completion.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -258,7 +258,7 @@ def services(self) -> dict[str, tuple[ServiceType | None, type[PromptExecutionSe
258258
# Amazon Bedrock supports models from multiple providers but requests to and responses from the models are
259259
# inconsistent. So we need to test each model separately.
260260
"bedrock_amazon_titan": (
261-
self._try_create_bedrock_text_completion_client("amazon.titan-text-premier-v1:0"),
261+
self._try_create_bedrock_text_completion_client("amazon.titan-text-express-v1"),
262262
BedrockTextPromptExecutionSettings,
263263
),
264264
"bedrock_anthropic_claude": (

python/tests/unit/connectors/ai/google/vertex_ai/services/test_vertex_ai_chat_completion.py

Lines changed: 71 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,7 @@
44
from unittest.mock import AsyncMock, patch
55

66
import pytest
7-
from google.cloud.aiplatform_v1beta1.types.content import Content
8-
from vertexai.generative_models import GenerativeModel
7+
from vertexai.generative_models import Content, GenerativeModel
98

109
from semantic_kernel.connectors.ai.function_choice_behavior import FunctionChoiceBehavior
1110
from semantic_kernel.connectors.ai.google.vertex_ai.services.vertex_ai_chat_completion import VertexAIChatCompletion
@@ -95,12 +94,23 @@ async def test_vertex_ai_chat_completion(
9594
chat_history, settings
9695
)
9796

98-
mock_vertex_ai_model_generate_content_async.assert_called_once_with(
99-
contents=vertex_ai_chat_completion._prepare_chat_history_for_request(chat_history),
100-
generation_config=settings.prepare_settings_dict(),
101-
tools=None,
102-
tool_config=None,
103-
)
97+
# Verify the call was made once
98+
mock_vertex_ai_model_generate_content_async.assert_called_once()
99+
100+
# Get the actual call arguments
101+
call_args = mock_vertex_ai_model_generate_content_async.call_args
102+
103+
# Verify the contents
104+
contents = call_args.kwargs["contents"]
105+
assert len(contents) == 1
106+
assert contents[0].role == "user"
107+
assert len(contents[0].parts) == 1
108+
assert contents[0].parts[0].text == "test_prompt"
109+
110+
# Verify other arguments
111+
assert call_args.kwargs["generation_config"] == settings.prepare_settings_dict()
112+
assert call_args.kwargs["tools"] is None
113+
assert call_args.kwargs["tool_config"] is None
104114
assert len(responses) == 1
105115
assert responses[0].role == "assistant"
106116
assert responses[0].content == mock_vertex_ai_chat_completion_response.candidates[0].content.parts[0].text
@@ -188,12 +198,23 @@ async def test_vertex_ai_chat_completion_with_function_choice_behavior_no_tool_c
188198
kernel=kernel,
189199
)
190200

191-
mock_vertex_ai_model_generate_content_async.assert_awaited_once_with(
192-
contents=vertex_ai_chat_completion._prepare_chat_history_for_request(chat_history),
193-
generation_config=settings.prepare_settings_dict(),
194-
tools=None,
195-
tool_config=None,
196-
)
201+
# Verify the call was made once
202+
mock_vertex_ai_model_generate_content_async.assert_awaited_once()
203+
204+
# Get the actual call arguments
205+
call_args = mock_vertex_ai_model_generate_content_async.await_args
206+
207+
# Verify the contents
208+
contents = call_args.kwargs["contents"]
209+
assert len(contents) == 1
210+
assert contents[0].role == "user"
211+
assert len(contents[0].parts) == 1
212+
assert contents[0].parts[0].text == "test_prompt"
213+
214+
# Verify other arguments
215+
assert call_args.kwargs["generation_config"] == settings.prepare_settings_dict()
216+
assert call_args.kwargs["tools"] is None
217+
assert call_args.kwargs["tool_config"] is None
197218
assert len(responses) == 1
198219
assert responses[0].role == "assistant"
199220
assert responses[0].content == mock_vertex_ai_chat_completion_response.candidates[0].content.parts[0].text
@@ -225,13 +246,24 @@ async def test_vertex_ai_streaming_chat_completion(
225246
assert "usage" in messages[0].metadata
226247
assert "prompt_feedback" in messages[0].metadata
227248

228-
mock_vertex_ai_model_generate_content_async.assert_called_once_with(
229-
contents=vertex_ai_chat_completion._prepare_chat_history_for_request(chat_history),
230-
generation_config=settings.prepare_settings_dict(),
231-
tools=None,
232-
tool_config=None,
233-
stream=True,
234-
)
249+
# Verify the call was made once
250+
mock_vertex_ai_model_generate_content_async.assert_called_once()
251+
252+
# Get the actual call arguments
253+
call_args = mock_vertex_ai_model_generate_content_async.call_args
254+
255+
# Verify the contents
256+
contents = call_args.kwargs["contents"]
257+
assert len(contents) == 1
258+
assert contents[0].role == "user"
259+
assert len(contents[0].parts) == 1
260+
assert contents[0].parts[0].text == "test_prompt"
261+
262+
# Verify other arguments
263+
assert call_args.kwargs["generation_config"] == settings.prepare_settings_dict()
264+
assert call_args.kwargs["tools"] is None
265+
assert call_args.kwargs["tool_config"] is None
266+
assert call_args.kwargs["stream"] is True
235267

236268

237269
async def test_vertex_ai_streaming_chat_completion_with_function_choice_behavior_fail_verification(
@@ -328,13 +360,24 @@ async def test_vertex_ai_streaming_chat_completion_with_function_choice_behavior
328360
assert len(messages) == 1
329361
assert messages[0].role == "assistant"
330362

331-
mock_vertex_ai_model_generate_content_async.assert_awaited_once_with(
332-
contents=vertex_ai_chat_completion._prepare_chat_history_for_request(chat_history),
333-
generation_config=settings.prepare_settings_dict(),
334-
tools=None,
335-
tool_config=None,
336-
stream=True,
337-
)
363+
# Verify the call was made once
364+
mock_vertex_ai_model_generate_content_async.assert_awaited_once()
365+
366+
# Get the actual call arguments
367+
call_args = mock_vertex_ai_model_generate_content_async.await_args
368+
369+
# Verify the contents
370+
contents = call_args.kwargs["contents"]
371+
assert len(contents) == 1
372+
assert contents[0].role == "user"
373+
assert len(contents[0].parts) == 1
374+
assert contents[0].parts[0].text == "test_prompt"
375+
376+
# Verify other arguments
377+
assert call_args.kwargs["generation_config"] == settings.prepare_settings_dict()
378+
assert call_args.kwargs["tools"] is None
379+
assert call_args.kwargs["tool_config"] is None
380+
assert call_args.kwargs["stream"] is True
338381

339382

340383
# endregion streaming chat completion

python/tests/unit/connectors/ai/google/vertex_ai/services/test_vertex_ai_utils.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
# Copyright (c) Microsoft. All rights reserved.
22

33
import pytest
4-
from google.cloud.aiplatform_v1beta1.types.content import Candidate, Part
4+
from google.cloud.aiplatform_v1beta1.types.content import Candidate
5+
from vertexai.generative_models import Part
56

67
from semantic_kernel.connectors.ai.google.vertex_ai.services.utils import (
78
finish_reason_from_vertex_ai_to_semantic_kernel,

0 commit comments

Comments
 (0)