From e7456ca1b9e03ea83112ca6ace84367dc6372b9e Mon Sep 17 00:00:00 2001 From: Tao Chen Date: Wed, 29 Oct 2025 12:06:54 -0700 Subject: [PATCH 1/6] Fix Vertex AI integration tests --- python/pyproject.toml | 2 +- .../ai/google/vertex_ai/services/utils.py | 35 ++++++------------- .../services/vertex_ai_chat_completion.py | 5 +-- .../services/vertex_ai_text_completion.py | 2 ++ .../services/vertex_ai_text_embedding.py | 3 +- python/uv.lock | 13 ++++--- 6 files changed, 27 insertions(+), 33 deletions(-) diff --git a/python/pyproject.toml b/python/pyproject.toml index d1b22c85541a..a58c71e21655 100644 --- a/python/pyproject.toml +++ b/python/pyproject.toml @@ -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 = [ diff --git a/python/semantic_kernel/connectors/ai/google/vertex_ai/services/utils.py b/python/semantic_kernel/connectors/ai/google/vertex_ai/services/utils.py index f3211066d466..ba4a5f9d8f14 100644 --- a/python/semantic_kernel/connectors/ai/google/vertex_ai/services/utils.py +++ b/python/semantic_kernel/connectors/ai/google/vertex_ai/services/utils.py @@ -1,12 +1,10 @@ # Copyright (c) Microsoft. All rights reserved. -import json 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 ( @@ -63,7 +61,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: @@ -88,17 +86,9 @@ 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, - ) - ) - ) + parts.append(Part.from_dict({"function_call": {"name": item.name, "args": item.arguments}})) elif isinstance(item, ImageContent): parts.append(_create_image_part(item)) else: @@ -124,14 +114,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), + }, ) ) @@ -177,7 +164,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 diff --git a/python/semantic_kernel/connectors/ai/google/vertex_ai/services/vertex_ai_chat_completion.py b/python/semantic_kernel/connectors/ai/google/vertex_ai/services/vertex_ai_chat_completion.py index 0fd4d3757db6..2a54aa4ec55c 100644 --- a/python/semantic_kernel/connectors/ai/google/vertex_ai/services/vertex_ai_chat_completion.py +++ b/python/semantic_kernel/connectors/ai/google/vertex_ai/services/vertex_ai_chat_completion.py @@ -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 @@ -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), @@ -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), diff --git a/python/semantic_kernel/connectors/ai/google/vertex_ai/services/vertex_ai_text_completion.py b/python/semantic_kernel/connectors/ai/google/vertex_ai/services/vertex_ai_text_completion.py index 8c31bf0fd804..4aa3992c634e 100644 --- a/python/semantic_kernel/connectors/ai/google/vertex_ai/services/vertex_ai_text_completion.py +++ b/python/semantic_kernel/connectors/ai/google/vertex_ai/services/vertex_ai_text_completion.py @@ -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( @@ -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( diff --git a/python/semantic_kernel/connectors/ai/google/vertex_ai/services/vertex_ai_text_embedding.py b/python/semantic_kernel/connectors/ai/google/vertex_ai/services/vertex_ai_text_embedding.py index 6bf2cf32b933..b0de375d3811 100644 --- a/python/semantic_kernel/connectors/ai/google/vertex_ai/services/vertex_ai_text_embedding.py +++ b/python/semantic_kernel/connectors/ai/google/vertex_ai/services/vertex_ai_text_embedding.py @@ -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(), ) diff --git a/python/uv.lock b/python/uv.lock index e06bb5906896..5f6cd156f26d 100644 --- a/python/uv.lock +++ b/python/uv.lock @@ -1,5 +1,5 @@ version = 1 -revision = 3 +revision = 2 requires-python = ">=3.10" resolution-markers = [ "python_full_version >= '4' and sys_platform == 'darwin'", @@ -1587,7 +1587,7 @@ wheels = [ [[package]] name = "google-cloud-aiplatform" -version = "1.97.0" +version = "1.114.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "docstring-parser", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, @@ -1604,9 +1604,9 @@ dependencies = [ { name = "shapely", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, { name = "typing-extensions", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/9b/ea/38224d2972e16c82ee16c13407e647586e25671bd2f75d4455491c678c92/google_cloud_aiplatform-1.97.0.tar.gz", hash = "sha256:01277ac5648abe7d2af688b123d7d050c1a34922e9f4297e51e44d165cb79b45", size = 9229557, upload-time = "2025-06-11T06:40:19.907Z" } +sdist = { url = "https://files.pythonhosted.org/packages/3d/0e/8097231fba8e688993b0b6d371ee298ac3955cdca77fc0731799de1253ca/google_cloud_aiplatform-1.114.0.tar.gz", hash = "sha256:44e5e3da9b23c9316a4d9e7cd6a04258ebf84f3aadf95a725d5d1de179e2c2ce", size = 9650673, upload-time = "2025-09-16T19:47:55.12Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9a/b8/f9ca10a648bc2596e904c30270c49e72528e2b3b583d886eeeec5080b27d/google_cloud_aiplatform-1.97.0-py2.py3-none-any.whl", hash = "sha256:4db9455308110b1e8c1b587bd3ff34449fa459fda45c4466b9b2d9ae259a7af6", size = 7687924, upload-time = "2025-06-11T06:40:16.947Z" }, + { url = "https://files.pythonhosted.org/packages/7a/0a/526e70e5cd8e0e96207e201721457dac020d9b8d1bd2ce7326e550b8150d/google_cloud_aiplatform-1.114.0-py2.py3-none-any.whl", hash = "sha256:87386d9364bd0bed4dd33873845afbbe251d1ed83ee25d676c3c0cea630af682", size = 8032171, upload-time = "2025-09-16T19:47:52.725Z" }, ] [[package]] @@ -2925,6 +2925,7 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b8/f6/f6abdcfefcedab3c964868048cfe472764ed358c2bf6819a70dd4ed4ed3a/mmh3-5.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:72d80005b7634a3a2220f81fbeb94775ebd12794623bb2e1451701ea732b4aa3", size = 41597, upload-time = "2025-07-29T07:42:28.894Z" }, { url = "https://files.pythonhosted.org/packages/15/fd/f7420e8cbce45c259c770cac5718badf907b302d3a99ec587ba5ce030237/mmh3-5.2.0-cp312-cp312-win_arm64.whl", hash = "sha256:3d6bfd9662a20c054bc216f861fa330c2dac7c81e7fb8307b5e32ab5b9b4d2e0", size = 39350, upload-time = "2025-07-29T07:42:29.794Z" }, { url = "https://files.pythonhosted.org/packages/d8/fa/27f6ab93995ef6ad9f940e96593c5dd24744d61a7389532b0fec03745607/mmh3-5.2.0-cp313-cp313-android_21_arm64_v8a.whl", hash = "sha256:e79c00eba78f7258e5b354eccd4d7907d60317ced924ea4a5f2e9d83f5453065", size = 40874, upload-time = "2025-07-29T07:42:30.662Z" }, + { url = "https://files.pythonhosted.org/packages/11/9c/03d13bcb6a03438bc8cac3d2e50f80908d159b31a4367c2e1a7a077ded32/mmh3-5.2.0-cp313-cp313-android_21_x86_64.whl", hash = "sha256:956127e663d05edbeec54df38885d943dfa27406594c411139690485128525de", size = 42012, upload-time = "2025-07-29T07:42:31.539Z" }, { url = "https://files.pythonhosted.org/packages/4e/78/0865d9765408a7d504f1789944e678f74e0888b96a766d578cb80b040999/mmh3-5.2.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:c3dca4cb5b946ee91b3d6bb700d137b1cd85c20827f89fdf9c16258253489044", size = 39197, upload-time = "2025-07-29T07:42:32.374Z" }, { url = "https://files.pythonhosted.org/packages/3e/12/76c3207bd186f98b908b6706c2317abb73756d23a4e68ea2bc94825b9015/mmh3-5.2.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:e651e17bfde5840e9e4174b01e9e080ce49277b70d424308b36a7969d0d1af73", size = 39840, upload-time = "2025-07-29T07:42:33.227Z" }, { url = "https://files.pythonhosted.org/packages/5d/0d/574b6cce5555c9f2b31ea189ad44986755eb14e8862db28c8b834b8b64dc/mmh3-5.2.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:9f64bf06f4bf623325fda3a6d02d36cd69199b9ace99b04bb2d7fd9f89688504", size = 40644, upload-time = "2025-07-29T07:42:34.099Z" }, @@ -4529,6 +4530,7 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/4f/bc/d5c277496063a09707486180f17abbdbdebbf2f5c4441b20b11d3cb7dc7c/pybase64-1.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:c995d21b8bd08aa179cd7dd4db0695c185486ecc72da1e8f6c37ec86cadb8182", size = 35817, upload-time = "2025-07-27T13:03:32.99Z" }, { url = "https://files.pythonhosted.org/packages/e6/69/e4be18ae685acff0ae77f75d4586590f29d2cd187bf603290cf1d635cad4/pybase64-1.4.2-cp312-cp312-win_arm64.whl", hash = "sha256:e254b9258c40509c2ea063a7784f6994988f3f26099d6e08704e3c15dfed9a55", size = 30900, upload-time = "2025-07-27T13:03:34.499Z" }, { url = "https://files.pythonhosted.org/packages/f4/56/5337f27a8b8d2d6693f46f7b36bae47895e5820bfa259b0072574a4e1057/pybase64-1.4.2-cp313-cp313-android_21_arm64_v8a.whl", hash = "sha256:0f331aa59549de21f690b6ccc79360ffed1155c3cfbc852eb5c097c0b8565a2b", size = 33888, upload-time = "2025-07-27T13:03:35.698Z" }, + { url = "https://files.pythonhosted.org/packages/4c/09/f3f4b11fc9beda7e8625e29fb0f549958fcbb34fea3914e1c1d95116e344/pybase64-1.4.2-cp313-cp313-android_21_x86_64.whl", hash = "sha256:9dad20bf1f3ed9e6fe566c4c9d07d9a6c04f5a280daebd2082ffb8620b0a880d", size = 40796, upload-time = "2025-07-27T13:03:36.927Z" }, { url = "https://files.pythonhosted.org/packages/e3/ff/470768f0fe6de0aa302a8cb1bdf2f9f5cffc3f69e60466153be68bc953aa/pybase64-1.4.2-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:69d3f0445b0faeef7bb7f93bf8c18d850785e2a77f12835f49e524cc54af04e7", size = 30914, upload-time = "2025-07-27T13:03:38.475Z" }, { url = "https://files.pythonhosted.org/packages/75/6b/d328736662665e0892409dc410353ebef175b1be5eb6bab1dad579efa6df/pybase64-1.4.2-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:2372b257b1f4dd512f317fb27e77d313afd137334de64c87de8374027aacd88a", size = 31380, upload-time = "2025-07-27T13:03:39.7Z" }, { url = "https://files.pythonhosted.org/packages/ca/96/7ff718f87c67f4147c181b73d0928897cefa17dc75d7abc6e37730d5908f/pybase64-1.4.2-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:fb794502b4b1ec91c4ca5d283ae71aef65e3de7721057bd9e2b3ec79f7a62d7d", size = 38230, upload-time = "2025-07-27T13:03:41.637Z" }, @@ -5197,6 +5199,7 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/40/96/5c50a7d2d2b05b19994bf7336b97db254299353dd9b49b565bb71b485f03/pyzmq-27.0.1-cp312-abi3-win_amd64.whl", hash = "sha256:ff3f8757570e45da7a5bedaa140489846510014f7a9d5ee9301c61f3f1b8a686", size = 618923, upload-time = "2025-08-03T05:03:45.438Z" }, { url = "https://files.pythonhosted.org/packages/13/33/1ec89c8f21c89d21a2eaff7def3676e21d8248d2675705e72554fb5a6f3f/pyzmq-27.0.1-cp312-abi3-win_arm64.whl", hash = "sha256:df2c55c958d3766bdb3e9d858b911288acec09a9aab15883f384fc7180df5bed", size = 552358, upload-time = "2025-08-03T05:03:46.887Z" }, { url = "https://files.pythonhosted.org/packages/6c/a0/f26e276211ec8090a4d11e4ec70eb8a8b15781e591c1d44ce62f372963a0/pyzmq-27.0.1-cp313-cp313-android_24_arm64_v8a.whl", hash = "sha256:497bd8af534ae55dc4ef67eebd1c149ff2a0b0f1e146db73c8b5a53d83c1a5f5", size = 1122287, upload-time = "2025-08-03T05:03:48.838Z" }, + { url = "https://files.pythonhosted.org/packages/9c/d8/af4b507e4f7eeea478cc8ee873995a6fd55582bfb99140593ed460e1db3c/pyzmq-27.0.1-cp313-cp313-android_24_x86_64.whl", hash = "sha256:a066ea6ad6218b4c233906adf0ae67830f451ed238419c0db609310dd781fbe7", size = 1155756, upload-time = "2025-08-03T05:03:50.907Z" }, { url = "https://files.pythonhosted.org/packages/ac/55/37fae0013e11f88681da42698e550b08a316d608242551f65095cc99232a/pyzmq-27.0.1-cp313-cp313t-macosx_10_15_universal2.whl", hash = "sha256:72d235d6365ca73d8ce92f7425065d70f5c1e19baa458eb3f0d570e425b73a96", size = 1340826, upload-time = "2025-08-03T05:03:52.568Z" }, { url = "https://files.pythonhosted.org/packages/f2/e4/3a87854c64b26fcf63a9d1b6f4382bd727d4797c772ceb334a97b7489be9/pyzmq-27.0.1-cp313-cp313t-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:313a7b374e3dc64848644ca348a51004b41726f768b02e17e689f1322366a4d9", size = 897283, upload-time = "2025-08-03T05:03:54.167Z" }, { url = "https://files.pythonhosted.org/packages/17/3e/4296c6b0ad2d07be11ae1395dccf9cae48a0a655cf9be1c3733ad2b591d1/pyzmq-27.0.1-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:119ce8590409702394f959c159d048002cbed2f3c0645ec9d6a88087fc70f0f1", size = 660565, upload-time = "2025-08-03T05:03:56.152Z" }, @@ -6033,7 +6036,7 @@ requires-dist = [ { name = "defusedxml", specifier = "~=0.7" }, { name = "faiss-cpu", marker = "extra == 'faiss'", specifier = ">=1.10.0" }, { name = "flask-dapr", marker = "extra == 'dapr'", specifier = ">=1.14.0" }, - { name = "google-cloud-aiplatform", marker = "extra == 'google'", specifier = "==1.97.0" }, + { name = "google-cloud-aiplatform", marker = "extra == 'google'", specifier = "~=1.114.0" }, { name = "google-generativeai", marker = "extra == 'google'", specifier = "~=0.8" }, { name = "ipykernel", marker = "extra == 'notebooks'", specifier = "~=6.29" }, { name = "jinja2", specifier = "~=3.1" }, From 18c74e9f70d0d4c58cfa418968910cdc1a91bf42 Mon Sep 17 00:00:00 2001 From: Tao Chen Date: Wed, 29 Oct 2025 13:24:29 -0700 Subject: [PATCH 2/6] Fix Bedrock connector unit tests --- .../integration/completions/chat_completion_test_base.py | 4 ++-- python/tests/integration/completions/test_chat_completions.py | 4 ++-- python/tests/integration/completions/test_text_completion.py | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/python/tests/integration/completions/chat_completion_test_base.py b/python/tests/integration/completions/chat_completion_test_base.py index 82c5f2bafe8d..48a370641fa6 100644 --- a/python/tests/integration/completions/chat_completion_test_base.py +++ b/python/tests/integration/completions/chat_completion_test_base.py @@ -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": ( diff --git a/python/tests/integration/completions/test_chat_completions.py b/python/tests/integration/completions/test_chat_completions.py index f0e9a4fdb5ea..277591e50d26 100644 --- a/python/tests/integration/completions/test_chat_completions.py +++ b/python/tests/integration/completions/test_chat_completions.py @@ -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", diff --git a/python/tests/integration/completions/test_text_completion.py b/python/tests/integration/completions/test_text_completion.py index c74e23b6beff..470df7bfa332 100644 --- a/python/tests/integration/completions/test_text_completion.py +++ b/python/tests/integration/completions/test_text_completion.py @@ -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": ( From 9b9468792e5f83010b49cea31d1c3d5f94babbf1 Mon Sep 17 00:00:00 2001 From: Tao Chen Date: Wed, 29 Oct 2025 14:07:27 -0700 Subject: [PATCH 3/6] Fix unit tests --- .../test_vertex_ai_chat_completion.py | 99 +++++++++++++------ .../services/test_vertex_ai_utils.py | 3 +- 2 files changed, 73 insertions(+), 29 deletions(-) diff --git a/python/tests/unit/connectors/ai/google/vertex_ai/services/test_vertex_ai_chat_completion.py b/python/tests/unit/connectors/ai/google/vertex_ai/services/test_vertex_ai_chat_completion.py index 9ea53c68910e..894a8ac07877 100644 --- a/python/tests/unit/connectors/ai/google/vertex_ai/services/test_vertex_ai_chat_completion.py +++ b/python/tests/unit/connectors/ai/google/vertex_ai/services/test_vertex_ai_chat_completion.py @@ -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 @@ -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 @@ -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 @@ -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( @@ -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 diff --git a/python/tests/unit/connectors/ai/google/vertex_ai/services/test_vertex_ai_utils.py b/python/tests/unit/connectors/ai/google/vertex_ai/services/test_vertex_ai_utils.py index e874262e69ef..510b03379ea2 100644 --- a/python/tests/unit/connectors/ai/google/vertex_ai/services/test_vertex_ai_utils.py +++ b/python/tests/unit/connectors/ai/google/vertex_ai/services/test_vertex_ai_utils.py @@ -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, From 583ea1476c0044e121ccf446bcf10738fc7d16a4 Mon Sep 17 00:00:00 2001 From: Tao Chen Date: Wed, 29 Oct 2025 16:48:54 -0700 Subject: [PATCH 4/6] Empty commit to trigger CI From a0bfc12e91efcc2daae5bbfdf74af0e4b9e03013 Mon Sep 17 00:00:00 2001 From: Tao Chen Date: Wed, 29 Oct 2025 17:18:14 -0700 Subject: [PATCH 5/6] Fix failing tests --- .../connectors/ai/google/vertex_ai/services/utils.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/python/semantic_kernel/connectors/ai/google/vertex_ai/services/utils.py b/python/semantic_kernel/connectors/ai/google/vertex_ai/services/utils.py index ba4a5f9d8f14..544ba584c680 100644 --- a/python/semantic_kernel/connectors/ai/google/vertex_ai/services/utils.py +++ b/python/semantic_kernel/connectors/ai/google/vertex_ai/services/utils.py @@ -1,5 +1,6 @@ # Copyright (c) Microsoft. All rights reserved. +import json import logging from typing import TYPE_CHECKING @@ -88,7 +89,14 @@ def format_assistant_message(message: ChatMessageContent) -> list[Part]: if item.text: parts.append(Part.from_text(item.text)) elif isinstance(item, FunctionCallContent): - parts.append(Part.from_dict({"function_call": {"name": item.name, "args": item.arguments}})) + parts.append( + 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)) else: From 0c8f26fe03c4a5c5c878283dd8113eacfed7bdbb Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Thu, 30 Oct 2025 09:54:24 +0900 Subject: [PATCH 6/6] Python: Fix AI connector integration tests and bump weaviate dependency (#13312) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Vertex AI and Bedrock integration tests were failing due to outdated dependencies and deprecated models. **Changes:** - Upgraded Google AI SDK version to fix Vertex AI integration tests - Updated Bedrock integration test models to current supported versions - Bumped `weaviate-client` minimum version from `>=4.10` to `>=4.17.0` (removed `!=4.16.7` exclusion) --- ✨ Let Copilot coding agent [set things up for you](https://github.com/microsoft/semantic-kernel/issues/new?title=✨+Set+up+Copilot+instructions&body=Configure%20instructions%20for%20this%20repository%20as%20documented%20in%20%5BBest%20practices%20for%20Copilot%20coding%20agent%20in%20your%20repository%5D%28https://gh.io/copilot-coding-agent-tips%29%2E%0A%0A%3COnboard%20this%20repo%3E&assignees=copilot) — coding agent works faster and does higher quality work when set up for your repo. --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: moonbox3 <35585003+moonbox3@users.noreply.github.com> --- python/pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/pyproject.toml b/python/pyproject.toml index d66bfca6c0d3..8ab32c6e87cc 100644 --- a/python/pyproject.toml +++ b/python/pyproject.toml @@ -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]