chore: import upstream snapshot with attribution
CodeQL / Analyze (csharp) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 13:21:23 +08:00
commit b957a53def
5423 changed files with 863745 additions and 0 deletions
@@ -0,0 +1,31 @@
# Copyright (c) Microsoft. All rights reserved.
import warnings
from semantic_kernel.connectors.ai.google.vertex_ai.services.vertex_ai_chat_completion import VertexAIChatCompletion
from semantic_kernel.connectors.ai.google.vertex_ai.services.vertex_ai_text_completion import VertexAITextCompletion
from semantic_kernel.connectors.ai.google.vertex_ai.services.vertex_ai_text_embedding import VertexAITextEmbedding
from semantic_kernel.connectors.ai.google.vertex_ai.vertex_ai_prompt_execution_settings import (
VertexAIChatPromptExecutionSettings,
VertexAIEmbeddingPromptExecutionSettings,
VertexAIPromptExecutionSettings,
VertexAITextPromptExecutionSettings,
)
# Deprecation warning for the entire Vertex AI package
warnings.warn(
"The `semantic_kernel.connectors.ai.google.vertex_ai` package is deprecated and will be removed after 01/01/2026. "
"Please use `semantic_kernel.connectors.ai.google` instead for Google AI services.",
DeprecationWarning,
stacklevel=2,
)
__all__ = [
"VertexAIChatCompletion",
"VertexAIChatPromptExecutionSettings",
"VertexAIEmbeddingPromptExecutionSettings",
"VertexAIPromptExecutionSettings",
"VertexAITextCompletion",
"VertexAITextEmbedding",
"VertexAITextPromptExecutionSettings",
]
@@ -0,0 +1,11 @@
# Copyright (c) Microsoft. All rights reserved.
import warnings
# Deprecation warning for Vertex AI services
warnings.warn(
"The semantic_kernel.connectors.ai.google.vertex_ai module is deprecated and will be removed after 01/01/2026. "
"Please use semantic_kernel.connectors.ai.google instead for Google AI services.",
DeprecationWarning,
stacklevel=2,
)
@@ -0,0 +1,191 @@
# Copyright (c) Microsoft. All rights reserved.
import json
import logging
from typing import TYPE_CHECKING, Any
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 (
FUNCTION_CHOICE_TYPE_TO_GOOGLE_FUNCTION_CALLING_MODE,
GEMINI_FUNCTION_NAME_SEPARATOR,
sanitize_schema_for_google_ai,
)
from semantic_kernel.connectors.ai.google.vertex_ai.vertex_ai_prompt_execution_settings import (
VertexAIChatPromptExecutionSettings,
)
from semantic_kernel.contents.chat_message_content import ChatMessageContent
from semantic_kernel.contents.function_call_content import FunctionCallContent
from semantic_kernel.contents.function_result_content import FunctionResultContent
from semantic_kernel.contents.image_content import ImageContent
from semantic_kernel.contents.text_content import TextContent
from semantic_kernel.contents.utils.finish_reason import FinishReason as SemanticKernelFinishReason
from semantic_kernel.exceptions.service_exceptions import ServiceInvalidRequestError
from semantic_kernel.functions.kernel_function_metadata import KernelFunctionMetadata
if TYPE_CHECKING:
from semantic_kernel.connectors.ai.function_call_choice_configuration import FunctionCallChoiceConfiguration
from semantic_kernel.connectors.ai.prompt_execution_settings import PromptExecutionSettings
logger: logging.Logger = logging.getLogger(__name__)
def finish_reason_from_vertex_ai_to_semantic_kernel(
finish_reason: Candidate.FinishReason,
) -> SemanticKernelFinishReason | None:
"""Convert a Vertex AI FinishReason to a Semantic Kernel FinishReason.
This is best effort and may not cover all cases as the enums are not identical.
"""
if finish_reason == Candidate.FinishReason.STOP:
return SemanticKernelFinishReason.STOP
if finish_reason == Candidate.FinishReason.MAX_TOKENS:
return SemanticKernelFinishReason.LENGTH
if finish_reason == Candidate.FinishReason.SAFETY:
return SemanticKernelFinishReason.CONTENT_FILTER
return None
def format_user_message(message: ChatMessageContent) -> list[Part]:
"""Format a user message to the expected object for the client.
Args:
message: The user message.
Returns:
The formatted user message as a list of parts.
"""
parts: list[Part] = []
for item in message.items:
if isinstance(item, TextContent):
parts.append(Part.from_text(item.text))
elif isinstance(item, ImageContent):
parts.append(_create_image_part(item))
else:
raise ServiceInvalidRequestError(
"Unsupported item type in User message while formatting chat history for Vertex AI"
f" Inference: {type(item)}"
)
return parts
def format_assistant_message(message: ChatMessageContent) -> list[Part]:
"""Format an assistant message to the expected object for the client.
Args:
message: The assistant message.
Returns:
The formatted assistant message as a list of parts.
"""
parts: list[Part] = []
for item in message.items:
if isinstance(item, TextContent):
if item.text:
parts.append(Part.from_text(item.text))
elif isinstance(item, FunctionCallContent):
part_dict: dict[str, Any] = {
"function_call": {
"name": item.name, # type: ignore[arg-type]
"args": json.loads(item.arguments) if isinstance(item.arguments, str) else item.arguments,
}
}
thought_signature = item.metadata.get("thought_signature") if item.metadata else None
if thought_signature:
part_dict["thought_signature"] = thought_signature
parts.append(Part.from_dict(part_dict))
elif isinstance(item, ImageContent):
parts.append(_create_image_part(item))
else:
raise ServiceInvalidRequestError(
"Unsupported item type in Assistant message while formatting chat history for Vertex AI"
f" Inference: {type(item)}"
)
return parts
def format_tool_message(message: ChatMessageContent) -> list[Part]:
"""Format a tool message to the expected object for the client.
Args:
message: The tool message.
Returns:
The formatted tool message.
"""
parts: list[Part] = []
for item in message.items:
if isinstance(item, FunctionResultContent):
gemini_function_name = item.custom_fully_qualified_name(GEMINI_FUNCTION_NAME_SEPARATOR)
parts.append(
Part.from_function_response(
gemini_function_name,
{
"content": str(item.result),
},
)
)
return parts
def kernel_function_metadata_to_vertex_ai_function_call_format(metadata: KernelFunctionMetadata) -> FunctionDeclaration:
"""Convert the kernel function metadata to function calling format."""
properties: dict[str, Any] = {}
if metadata.parameters:
for param in metadata.parameters:
if param.name is None:
continue
prop_schema = sanitize_schema_for_google_ai(param.schema_data) if param.schema_data else param.schema_data
properties[param.name] = prop_schema
return FunctionDeclaration(
name=metadata.custom_fully_qualified_name(GEMINI_FUNCTION_NAME_SEPARATOR),
description=metadata.description or "",
parameters={
"type": "object",
"properties": properties,
"required": [p.name for p in metadata.parameters if p.is_required and p.name is not None],
},
)
def update_settings_from_function_choice_configuration(
function_choice_configuration: "FunctionCallChoiceConfiguration",
settings: "PromptExecutionSettings",
type: FunctionChoiceType,
) -> None:
"""Update the settings from a FunctionChoiceConfiguration."""
assert isinstance(settings, VertexAIChatPromptExecutionSettings) # nosec
if function_choice_configuration.available_functions:
settings.tool_config = ToolConfig(
function_calling_config=ToolConfig.FunctionCallingConfig(
mode=FUNCTION_CHOICE_TYPE_TO_GOOGLE_FUNCTION_CALLING_MODE[type],
),
)
settings.tools = [
Tool(
function_declarations=[
kernel_function_metadata_to_vertex_ai_function_call_format(f)
for f in function_choice_configuration.available_functions
]
)
]
def _create_image_part(image_content: ImageContent) -> Part:
if image_content.data_uri:
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
raise ServiceInvalidRequestError(
"ImageContent without data_uri in User message while formatting chat history for Google AI"
)
@@ -0,0 +1,18 @@
# Copyright (c) Microsoft. All rights reserved.
from abc import ABC
from typing import ClassVar
from typing_extensions import deprecated
from semantic_kernel.connectors.ai.google.vertex_ai.vertex_ai_settings import VertexAISettings
from semantic_kernel.kernel_pydantic import KernelBaseModel
@deprecated("VertexAIBase is deprecated and will be removed after 01/01/2026. Use google_ai connectors instead.")
class VertexAIBase(KernelBaseModel, ABC):
"""Vertex AI Service."""
MODEL_PROVIDER_NAME: ClassVar[str] = "vertexai"
service_settings: VertexAISettings
@@ -0,0 +1,375 @@
# Copyright (c) Microsoft. All rights reserved.
import sys
from collections.abc import AsyncGenerator, AsyncIterable, Callable
from typing import TYPE_CHECKING, Any, ClassVar
import vertexai
from pydantic import ValidationError
from typing_extensions import deprecated
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
from semantic_kernel.connectors.ai.function_choice_behavior import FunctionChoiceType
from semantic_kernel.connectors.ai.google.shared_utils import (
collapse_function_call_results_in_chat_history,
filter_system_message,
format_gemini_function_name_to_kernel_function_fully_qualified_name,
)
from semantic_kernel.connectors.ai.google.vertex_ai.services.utils import (
finish_reason_from_vertex_ai_to_semantic_kernel,
format_assistant_message,
format_tool_message,
format_user_message,
update_settings_from_function_choice_configuration,
)
from semantic_kernel.connectors.ai.google.vertex_ai.services.vertex_ai_base import VertexAIBase
from semantic_kernel.connectors.ai.google.vertex_ai.vertex_ai_prompt_execution_settings import (
VertexAIChatPromptExecutionSettings,
)
from semantic_kernel.connectors.ai.google.vertex_ai.vertex_ai_settings import VertexAISettings
from semantic_kernel.contents.chat_history import ChatHistory
from semantic_kernel.contents.chat_message_content import CMC_ITEM_TYPES, ChatMessageContent
from semantic_kernel.contents.function_call_content import FunctionCallContent
from semantic_kernel.contents.streaming_chat_message_content import STREAMING_CMC_ITEM_TYPES as STREAMING_ITEM_TYPES
from semantic_kernel.contents.streaming_chat_message_content import StreamingChatMessageContent
from semantic_kernel.contents.streaming_text_content import StreamingTextContent
from semantic_kernel.contents.text_content import TextContent
from semantic_kernel.contents.utils.author_role import AuthorRole
from semantic_kernel.contents.utils.finish_reason import FinishReason
from semantic_kernel.exceptions.service_exceptions import (
ServiceInitializationError,
ServiceInvalidExecutionSettingsError,
)
from semantic_kernel.utils.telemetry.model_diagnostics.decorators import (
trace_chat_completion,
trace_streaming_chat_completion,
)
if sys.version_info >= (3, 12):
from typing import override # pragma: no cover
else:
from typing_extensions import override # pragma: no cover
if TYPE_CHECKING:
from semantic_kernel.connectors.ai.function_call_choice_configuration import FunctionCallChoiceConfiguration
from semantic_kernel.connectors.ai.prompt_execution_settings import PromptExecutionSettings
@deprecated(
"VertexAIChatCompletion is deprecated and will be removed after 01/01/2026. "
"Use `semantic_kernel.connectors.ai.google.GoogleAIChatCompletion` connectors instead."
)
class VertexAIChatCompletion(VertexAIBase, ChatCompletionClientBase):
"""Google Vertex AI Chat Completion Service."""
SUPPORTS_FUNCTION_CALLING: ClassVar[bool] = True
def __init__(
self,
project_id: str | None = None,
region: str | None = None,
gemini_model_id: str | None = None,
service_id: str | None = None,
env_file_path: str | None = None,
env_file_encoding: str | None = None,
) -> None:
"""Initialize the Google Vertex AI Chat Completion Service.
If no arguments are provided, the service will attempt to load the settings from the environment.
The following environment variables are used:
- VERTEX_AI_GEMINI_MODEL_ID
- VERTEX_AI_PROJECT_ID
- VERTEX_AI_REGION
Args:
project_id (str): The Google Cloud project ID.
region (str): The Google Cloud region.
gemini_model_id (str): The Gemini model ID.
service_id (str): The Vertex AI service ID.
env_file_path (str): The path to the environment file.
env_file_encoding (str): The encoding of the environment file.
"""
try:
vertex_ai_settings = VertexAISettings(
project_id=project_id,
region=region,
gemini_model_id=gemini_model_id,
env_file_path=env_file_path,
env_file_encoding=env_file_encoding,
)
except ValidationError as e:
raise ServiceInitializationError(f"Failed to validate Vertex AI settings: {e}") from e
if not vertex_ai_settings.gemini_model_id:
raise ServiceInitializationError("The Vertex AI Gemini model ID is required.")
super().__init__(
ai_model_id=vertex_ai_settings.gemini_model_id,
service_id=service_id or vertex_ai_settings.gemini_model_id,
service_settings=vertex_ai_settings,
)
# region Overriding base class methods
# Override from AIServiceClientBase
@override
def get_prompt_execution_settings_class(self) -> type["PromptExecutionSettings"]:
return VertexAIChatPromptExecutionSettings
@override
@trace_chat_completion(VertexAIBase.MODEL_PROVIDER_NAME)
async def _inner_get_chat_message_contents(
self,
chat_history: "ChatHistory",
settings: "PromptExecutionSettings",
) -> list["ChatMessageContent"]:
if not isinstance(settings, VertexAIChatPromptExecutionSettings):
settings = self.get_prompt_execution_settings_from_settings(settings)
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),
)
collapse_function_call_results_in_chat_history(chat_history)
response: GenerationResponse = await model.generate_content_async(
contents=self._prepare_chat_history_for_request(chat_history),
generation_config=settings.prepare_settings_dict(),
tools=settings.tools,
tool_config=settings.tool_config,
)
return [self._create_chat_message_content(response, candidate) for candidate in response.candidates]
@override
@trace_streaming_chat_completion(VertexAIBase.MODEL_PROVIDER_NAME)
async def _inner_get_streaming_chat_message_contents(
self,
chat_history: "ChatHistory",
settings: "PromptExecutionSettings",
function_invoke_attempt: int = 0,
) -> AsyncGenerator[list["StreamingChatMessageContent"], Any]:
if not isinstance(settings, VertexAIChatPromptExecutionSettings):
settings = self.get_prompt_execution_settings_from_settings(settings)
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),
)
collapse_function_call_results_in_chat_history(chat_history)
response: AsyncIterable[GenerationResponse] = await model.generate_content_async(
contents=self._prepare_chat_history_for_request(chat_history),
generation_config=settings.prepare_settings_dict(),
tools=settings.tools,
tool_config=settings.tool_config,
stream=True,
)
async for chunk in response:
yield [
self._create_streaming_chat_message_content(chunk, candidate, function_invoke_attempt)
for candidate in chunk.candidates
]
@override
def _verify_function_choice_settings(self, settings: "PromptExecutionSettings") -> None:
if not isinstance(settings, VertexAIChatPromptExecutionSettings):
raise ServiceInvalidExecutionSettingsError("The settings must be an VertexAIChatPromptExecutionSettings.")
if settings.candidate_count is not None and settings.candidate_count > 1:
raise ServiceInvalidExecutionSettingsError(
"Auto-invocation of tool calls may only be used with a "
"VertexAIChatPromptExecutionSettings.candidate_count of 1."
)
@override
def _update_function_choice_settings_callback(
self,
) -> Callable[["FunctionCallChoiceConfiguration", "PromptExecutionSettings", FunctionChoiceType], None]:
return update_settings_from_function_choice_configuration
@override
def _reset_function_choice_settings(self, settings: "PromptExecutionSettings") -> None:
if hasattr(settings, "tool_config"):
settings.tool_config = None
if hasattr(settings, "tools"):
settings.tools = None
@override
def _prepare_chat_history_for_request(
self,
chat_history: ChatHistory,
role_key: str = "role",
content_key: str = "content",
) -> list[Content]:
chat_request_messages: list[Content] = []
for message in chat_history.messages:
if message.role == AuthorRole.SYSTEM:
# Skip system messages since they are not part of the chat request.
# System message will be provided as system_instruction in the model.
continue
if message.role == AuthorRole.USER:
chat_request_messages.append(Content(role="user", parts=format_user_message(message)))
elif message.role == AuthorRole.ASSISTANT:
chat_request_messages.append(Content(role="model", parts=format_assistant_message(message)))
elif message.role == AuthorRole.TOOL:
chat_request_messages.append(Content(role="function", parts=format_tool_message(message)))
return chat_request_messages
# endregion
# region Non-streaming
def _create_chat_message_content(self, response: GenerationResponse, candidate: Candidate) -> ChatMessageContent:
"""Create a chat message content object.
Args:
response: The response from the service.
candidate: The candidate from the response.
Returns:
A chat message content object.
"""
# Best effort conversion of finish reason. The raw value will be available in metadata.
finish_reason: FinishReason | None = finish_reason_from_vertex_ai_to_semantic_kernel(candidate.finish_reason)
response_metadata = self._get_metadata_from_response(response)
response_metadata.update(self._get_metadata_from_candidate(candidate))
items: list[CMC_ITEM_TYPES] = []
for idx, part in enumerate(candidate.content.parts):
part_dict = part.to_dict()
if "text" in part_dict:
items.append(TextContent(text=part.text, inner_content=response, metadata=response_metadata))
elif "function_call" in part_dict:
fc_metadata: dict[str, Any] = {}
thought_sig = part_dict.get("thought_signature")
if thought_sig:
fc_metadata["thought_signature"] = thought_sig
items.append(
FunctionCallContent(
id=f"{part.function_call.name}_{idx!s}",
name=format_gemini_function_name_to_kernel_function_fully_qualified_name(
part.function_call.name
),
arguments={k: v for k, v in part.function_call.args.items()},
metadata=fc_metadata if fc_metadata else None,
)
)
return ChatMessageContent(
ai_model_id=self.ai_model_id,
role=AuthorRole.ASSISTANT,
items=items,
inner_content=response,
finish_reason=finish_reason,
metadata=response_metadata,
)
# endregion
# region Streaming
def _create_streaming_chat_message_content(
self,
chunk: GenerationResponse,
candidate: Candidate,
function_invoke_attempt: int,
) -> StreamingChatMessageContent:
"""Create a streaming chat message content object.
Args:
chunk: The response from the service.
candidate: The candidate from the response.
function_invoke_attempt: The function invoke attempt.
Returns:
A streaming chat message content object.
"""
# Best effort conversion of finish reason. The raw value will be available in metadata.
finish_reason: FinishReason | None = finish_reason_from_vertex_ai_to_semantic_kernel(candidate.finish_reason)
response_metadata = self._get_metadata_from_response(chunk)
response_metadata.update(self._get_metadata_from_candidate(candidate))
items: list[STREAMING_ITEM_TYPES] = []
for idx, part in enumerate(candidate.content.parts):
part_dict = part.to_dict()
if "text" in part_dict:
items.append(
StreamingTextContent(
choice_index=candidate.index,
text=part.text,
inner_content=chunk,
metadata=response_metadata,
)
)
elif "function_call" in part_dict:
fc_metadata_s: dict[str, Any] = {}
thought_sig_s = part_dict.get("thought_signature")
if thought_sig_s:
fc_metadata_s["thought_signature"] = thought_sig_s
items.append(
FunctionCallContent(
id=f"{part.function_call.name}_{idx!s}",
name=format_gemini_function_name_to_kernel_function_fully_qualified_name(
part.function_call.name
),
arguments={k: v for k, v in part.function_call.args.items()},
metadata=fc_metadata_s if fc_metadata_s else None,
)
)
return StreamingChatMessageContent(
ai_model_id=self.ai_model_id,
role=AuthorRole.ASSISTANT,
choice_index=candidate.index,
items=items,
inner_content=chunk,
finish_reason=finish_reason,
metadata=response_metadata,
function_invoke_attempt=function_invoke_attempt,
)
# endregion
def _get_metadata_from_response(self, response: GenerationResponse) -> dict[str, Any]:
"""Get metadata from the response.
Args:
response: The response from the service.
Returns:
A dictionary containing metadata.
"""
return {
"prompt_feedback": response.prompt_feedback,
"usage": CompletionUsage(
prompt_tokens=response.usage_metadata.prompt_token_count,
completion_tokens=response.usage_metadata.candidates_token_count,
),
}
def _get_metadata_from_candidate(self, candidate: Candidate) -> dict[str, Any]:
"""Get metadata from the candidate.
Args:
candidate: The candidate from the response.
Returns:
A dictionary containing metadata.
"""
return {
"index": candidate.index,
"finish_reason": candidate.finish_reason,
"safety_ratings": candidate.safety_ratings,
}
@@ -0,0 +1,211 @@
# Copyright (c) Microsoft. All rights reserved.
import sys
from collections.abc import AsyncGenerator, AsyncIterable
from typing import Any
import vertexai
from pydantic import ValidationError
from typing_extensions import deprecated
from vertexai.generative_models import Candidate, GenerationResponse, GenerativeModel
from semantic_kernel.connectors.ai.completion_usage import CompletionUsage
from semantic_kernel.connectors.ai.google.vertex_ai.services.vertex_ai_base import VertexAIBase
from semantic_kernel.connectors.ai.google.vertex_ai.vertex_ai_prompt_execution_settings import (
VertexAITextPromptExecutionSettings,
)
from semantic_kernel.connectors.ai.google.vertex_ai.vertex_ai_settings import VertexAISettings
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.streaming_text_content import StreamingTextContent
from semantic_kernel.contents.text_content import TextContent
from semantic_kernel.exceptions.service_exceptions import ServiceInitializationError
from semantic_kernel.utils.telemetry.model_diagnostics.decorators import (
trace_streaming_text_completion,
trace_text_completion,
)
if sys.version_info >= (3, 12):
from typing import override # pragma: no cover
else:
from typing_extensions import override # pragma: no cover
@deprecated(
"VertexAITextCompletion is deprecated and will be removed after 01/01/2026. "
"Use `semantic_kernel.connectors.ai.google.GoogleAITextCompletion` connectors instead."
)
class VertexAITextCompletion(VertexAIBase, TextCompletionClientBase):
"""Vertex AI Text Completion Client."""
def __init__(
self,
project_id: str | None = None,
region: str | None = None,
gemini_model_id: str | None = None,
service_id: str | None = None,
env_file_path: str | None = None,
env_file_encoding: str | None = None,
) -> None:
"""Initialize the Google Vertex AI Text Completion Service.
If no arguments are provided, the service will attempt to load the settings from the environment.
The following environment variables are used:
- VERTEX_AI_GEMINI_MODEL_ID
- VERTEX_AI_PROJECT_ID
Args:
project_id (str): The Google Cloud project ID.
region (str): The Google Cloud region.
gemini_model_id (str): The Gemini model ID.
service_id (str): The Vertex AI service ID.
env_file_path (str): The path to the environment file.
env_file_encoding (str): The encoding of the environment file.
"""
try:
vertex_ai_settings = VertexAISettings(
project_id=project_id,
region=region,
gemini_model_id=gemini_model_id,
env_file_path=env_file_path,
env_file_encoding=env_file_encoding,
)
except ValidationError as e:
raise ServiceInitializationError(f"Failed to validate Vertex AI settings: {e}") from e
if not vertex_ai_settings.gemini_model_id:
raise ServiceInitializationError("The Vertex AI Gemini model ID is required.")
super().__init__(
ai_model_id=vertex_ai_settings.gemini_model_id,
service_id=service_id or vertex_ai_settings.gemini_model_id,
service_settings=vertex_ai_settings,
)
# region Overriding base class methods
# Override from AIServiceClientBase
@override
def get_prompt_execution_settings_class(self) -> type["PromptExecutionSettings"]:
return VertexAITextPromptExecutionSettings
@override
@trace_text_completion(VertexAIBase.MODEL_PROVIDER_NAME)
async def _inner_get_text_contents(
self,
prompt: str,
settings: "PromptExecutionSettings",
) -> list[TextContent]:
if not isinstance(settings, VertexAITextPromptExecutionSettings):
settings = self.get_prompt_execution_settings_from_settings(settings)
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(
contents=prompt,
generation_config=settings.prepare_settings_dict(),
)
return [self._create_text_content(response, candidate) for candidate in response.candidates]
@override
@trace_streaming_text_completion(VertexAIBase.MODEL_PROVIDER_NAME)
async def _inner_get_streaming_text_contents(
self,
prompt: str,
settings: "PromptExecutionSettings",
) -> AsyncGenerator[list[StreamingTextContent], Any]:
if not isinstance(settings, VertexAITextPromptExecutionSettings):
settings = self.get_prompt_execution_settings_from_settings(settings)
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(
contents=prompt,
generation_config=settings.prepare_settings_dict(),
stream=True,
)
async for chunk in response:
yield [self._create_streaming_text_content(chunk, candidate) for candidate in chunk.candidates]
# endregion
def _create_text_content(self, response: GenerationResponse, candidate: Candidate) -> TextContent:
"""Create a text content object.
Args:
response: The response from the service.
candidate: The candidate from the response.
Returns:
A text content object.
"""
response_metadata = self._get_metadata_from_response(response)
response_metadata.update(self._get_metadata_from_candidate(candidate))
return TextContent(
ai_model_id=self.ai_model_id,
text=candidate.content.parts[0].text,
inner_content=response,
metadata=response_metadata,
)
def _create_streaming_text_content(self, chunk: GenerationResponse, candidate: Candidate) -> StreamingTextContent:
"""Create a streaming text content object.
Args:
chunk: The response from the service.
candidate: The candidate from the response.
Returns:
A streaming text content object.
"""
response_metadata = self._get_metadata_from_response(chunk)
response_metadata.update(self._get_metadata_from_candidate(candidate))
return StreamingTextContent(
ai_model_id=self.ai_model_id,
choice_index=candidate.index,
text=candidate.content.parts[0].text,
inner_content=chunk,
metadata=response_metadata,
)
def _get_metadata_from_response(self, response: GenerationResponse) -> dict[str, Any]:
"""Get metadata from the response.
Args:
response: The response from the service.
Returns:
A dictionary containing metadata.
"""
return {
"prompt_feedback": response.prompt_feedback,
"usage": CompletionUsage(
prompt_tokens=response.usage_metadata.prompt_token_count,
completion_tokens=response.usage_metadata.candidates_token_count,
),
}
def _get_metadata_from_candidate(self, candidate: Candidate) -> dict[str, Any]:
"""Get metadata from the candidate.
Args:
candidate: The candidate from the response.
Returns:
A dictionary containing metadata.
"""
return {
"index": candidate.index,
"finish_reason": candidate.finish_reason,
"safety_ratings": candidate.safety_ratings,
}
@@ -0,0 +1,116 @@
# Copyright (c) Microsoft. All rights reserved.
import sys
from typing import Any
import vertexai
from numpy import array, ndarray
from pydantic import ValidationError
from vertexai.language_models import TextEmbedding, TextEmbeddingModel
from semantic_kernel.connectors.ai.embedding_generator_base import EmbeddingGeneratorBase
from semantic_kernel.connectors.ai.google.vertex_ai.services.vertex_ai_base import VertexAIBase
from semantic_kernel.connectors.ai.google.vertex_ai.vertex_ai_prompt_execution_settings import (
VertexAIEmbeddingPromptExecutionSettings,
)
from semantic_kernel.connectors.ai.google.vertex_ai.vertex_ai_settings import VertexAISettings
from semantic_kernel.connectors.ai.prompt_execution_settings import PromptExecutionSettings
from semantic_kernel.exceptions.service_exceptions import ServiceInitializationError
if sys.version_info >= (3, 12):
from typing import override # pragma: no cover
else:
from typing_extensions import override # pragma: no cover
from typing_extensions import deprecated
@deprecated(
"VertexAITextEmbedding is deprecated and will be removed after 01/01/2026. "
"Use `semantic_kernel.connectors.ai.google.GoogleAITextEmbedding` connectors instead."
)
class VertexAITextEmbedding(VertexAIBase, EmbeddingGeneratorBase):
"""Vertex AI Text Embedding Service."""
def __init__(
self,
project_id: str | None = None,
region: str | None = None,
embedding_model_id: str | None = None,
service_id: str | None = None,
env_file_path: str | None = None,
env_file_encoding: str | None = None,
) -> None:
"""Initialize the Google Vertex AI Chat Completion Service.
If no arguments are provided, the service will attempt to load the settings from the environment.
The following environment variables are used:
- VERTEX_AI_EMBEDDING_MODEL_ID
- VERTEX_AI_PROJECT_ID
Args:
project_id (str): The Google Cloud project ID.
region (str): The Google Cloud region.
embedding_model_id (str): The Gemini model ID.
service_id (str): The Vertex AI service ID.
env_file_path (str): The path to the environment file.
env_file_encoding (str): The encoding of the environment file.
"""
try:
vertex_ai_settings = VertexAISettings(
project_id=project_id,
region=region,
embedding_model_id=embedding_model_id,
env_file_path=env_file_path,
env_file_encoding=env_file_encoding,
)
except ValidationError as e:
raise ServiceInitializationError(f"Failed to validate Vertex AI settings: {e}") from e
if not vertex_ai_settings.embedding_model_id:
raise ServiceInitializationError("The Vertex AI embedding model ID is required.")
super().__init__(
ai_model_id=vertex_ai_settings.embedding_model_id,
service_id=service_id or vertex_ai_settings.embedding_model_id,
service_settings=vertex_ai_settings,
)
@override
async def generate_embeddings(
self,
texts: list[str],
settings: "PromptExecutionSettings | None" = None,
**kwargs: Any,
) -> ndarray:
raw_embeddings = await self.generate_raw_embeddings(texts, settings, **kwargs)
return array(raw_embeddings)
@override
async def generate_raw_embeddings(
self,
texts: list[str],
settings: "PromptExecutionSettings | None" = None,
**kwargs: Any,
) -> list[list[float]]:
if not settings:
settings = VertexAIEmbeddingPromptExecutionSettings()
else:
settings = self.get_prompt_execution_settings_from_settings(settings)
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, # type: ignore[arg-type]
**settings.prepare_settings_dict(),
)
return [text_embedding.values for text_embedding in response]
@override
def get_prompt_execution_settings_class(
self,
) -> type["PromptExecutionSettings"]:
"""Get the request settings class."""
return VertexAIEmbeddingPromptExecutionSettings
@@ -0,0 +1,89 @@
# Copyright (c) Microsoft. All rights reserved.
import sys
from typing import Annotated, Any, Literal
if sys.version_info >= (3, 12):
from typing import override # pragma: no cover
else:
from typing_extensions import override # pragma: no cover
from pydantic import Field
from typing_extensions import deprecated
from vertexai.generative_models import Tool, ToolConfig
from semantic_kernel.connectors.ai.prompt_execution_settings import PromptExecutionSettings
@deprecated(
"VertexAIPromptExecutionSettings is deprecated and will be removed after 01/01/2026. "
"Use google_ai connectors instead."
)
class VertexAIPromptExecutionSettings(PromptExecutionSettings):
"""Vertex AI Prompt Execution Settings."""
stop_sequences: Annotated[list[str] | None, Field(max_length=5)] = None
response_mime_type: Literal["text/plain", "application/json"] | None = None
response_schema: Any | None = None
candidate_count: Annotated[int | None, Field(ge=1)] = None
max_output_tokens: Annotated[int | None, Field(ge=1)] = None
temperature: Annotated[float | None, Field(ge=0.0, le=2.0)] = None
top_p: float | None = None
top_k: int | None = None
@deprecated(
"VertexAITextPromptExecutionSettings is deprecated and will be removed after 01/01/2026. "
"Use google_ai connectors instead."
)
class VertexAITextPromptExecutionSettings(VertexAIPromptExecutionSettings):
"""Vertex AI Text Prompt Execution Settings."""
pass
@deprecated(
"VertexAIChatPromptExecutionSettings is deprecated and will be removed after 01/01/2026. "
"Use google_ai connectors instead."
)
class VertexAIChatPromptExecutionSettings(VertexAIPromptExecutionSettings):
"""Vertex AI Chat Prompt Execution Settings."""
tools: Annotated[
list[Tool] | None,
Field(
description="Do not set this manually. It is set by the service based "
"on the function choice configuration.",
),
] = None
tool_config: Annotated[
ToolConfig | None,
Field(
description="Do not set this manually. It is set by the service based "
"on the function choice configuration.",
),
] = None
@override
def prepare_settings_dict(self, **kwargs) -> dict[str, Any]:
"""Prepare the settings as a dictionary for sending to the AI service.
This method removes the tools and tool_config keys from the settings dictionary, as
the Vertex AI service mandates these two settings to be sent as separate parameters.
"""
settings_dict = super().prepare_settings_dict(**kwargs)
settings_dict.pop("tools", None)
settings_dict.pop("tool_config", None)
return settings_dict
@deprecated(
"VertexAIEmbeddingPromptExecutionSettings is deprecated and will be removed after 01/01/2026. "
"Use google_ai connectors instead."
)
class VertexAIEmbeddingPromptExecutionSettings(PromptExecutionSettings):
"""Google AI Embedding Prompt Execution Settings."""
auto_truncate: bool | None = None
output_dimensionality: int | None = None
@@ -0,0 +1,40 @@
# Copyright (c) Microsoft. All rights reserved.
from typing import ClassVar
from typing_extensions import deprecated
from semantic_kernel.kernel_pydantic import KernelBaseSettings
@deprecated("VertexAISettings is deprecated and will be removed after 01/01/2026. Use google_ai connectors instead.")
class VertexAISettings(KernelBaseSettings):
"""Vertex AI settings.
The settings are first loaded from environment variables with
the prefix 'VERTEX_AI_'.
If the environment variables are not found, the settings can
be loaded from a .env file with the encoding 'utf-8'.
If the settings are not found in the .env file, the settings
are ignored; however, validation will fail alerting that the
settings are missing.
Required settings for prefix 'VERTEX_AI_' are:
- gemini_model_id: str - The Gemini model ID for the Vertex AI service, i.e. gemini-1.5-pro
This value can be found in the Vertex AI service deployment.
(Env var VERTEX_AI_GEMINI_MODEL_ID)
- embedding_model_id: str - The embedding model ID for the Vertex AI service, i.e. text-embedding-004
This value can be found in the Vertex AI service deployment.
(Env var VERTEX_AI_EMBEDDING_MODEL_ID)
- project_id: str - The Google Cloud project ID.
(Env var VERTEX_AI_PROJECT_ID)
- region: str - The Google Cloud region.
(Env var VERTEX_AI_REGION)
"""
env_prefix: ClassVar[str] = "VERTEX_AI_"
gemini_model_id: str | None = None
embedding_model_id: str | None = None
project_id: str
region: str | None = None