mirror of
https://github.com/microsoft/semantic-kernel.git
synced 2026-10-12 02:19:06 +00:00
### 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. --> extend mypy coverage for prompt_templates, template_engine and some other smaller folders. still todo: connectors, memory and planner folders. ### Description <!-- Describe your changes, the overall approach, the underlying design. These notes will help understanding how your code works. Thanks! --> ### 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 😄
134 lines
5.8 KiB
Python
134 lines
5.8 KiB
Python
# Copyright (c) Microsoft. All rights reserved.
|
|
|
|
import logging
|
|
from abc import ABC
|
|
from typing import TYPE_CHECKING
|
|
|
|
from pydantic import Field, field_validator
|
|
|
|
from semantic_kernel.connectors.ai.prompt_execution_settings import PromptExecutionSettings
|
|
from semantic_kernel.const import DEFAULT_SERVICE_NAME
|
|
from semantic_kernel.exceptions import KernelFunctionAlreadyExistsError, KernelServiceNotFoundError
|
|
from semantic_kernel.kernel_pydantic import KernelBaseModel
|
|
from semantic_kernel.kernel_types import AI_SERVICE_CLIENT_TYPE
|
|
from semantic_kernel.services.ai_service_client_base import AIServiceClientBase
|
|
from semantic_kernel.services.ai_service_selector import AIServiceSelector
|
|
|
|
if TYPE_CHECKING:
|
|
from semantic_kernel.functions.kernel_arguments import KernelArguments
|
|
from semantic_kernel.functions.kernel_function import KernelFunction
|
|
|
|
|
|
logger: logging.Logger = logging.getLogger(__name__)
|
|
|
|
|
|
class KernelServicesExtension(KernelBaseModel, ABC):
|
|
services: dict[str, AIServiceClientBase] = Field(default_factory=dict)
|
|
ai_service_selector: AIServiceSelector = Field(default_factory=AIServiceSelector)
|
|
|
|
@field_validator("services", mode="before")
|
|
@classmethod
|
|
def rewrite_services(
|
|
cls,
|
|
services: (
|
|
AI_SERVICE_CLIENT_TYPE | list[AI_SERVICE_CLIENT_TYPE] | dict[str, AI_SERVICE_CLIENT_TYPE] | None
|
|
) = None,
|
|
) -> dict[str, AI_SERVICE_CLIENT_TYPE]:
|
|
"""Rewrite services to a dictionary."""
|
|
if not services:
|
|
return {}
|
|
if isinstance(services, AIServiceClientBase):
|
|
return {services.service_id if services.service_id else DEFAULT_SERVICE_NAME: services} # type: ignore
|
|
if isinstance(services, list):
|
|
return {s.service_id if s.service_id else DEFAULT_SERVICE_NAME: s for s in services}
|
|
return services
|
|
|
|
def select_ai_service(
|
|
self, function: "KernelFunction", arguments: "KernelArguments"
|
|
) -> tuple[AIServiceClientBase, PromptExecutionSettings]:
|
|
"""Uses the AI service selector to select a service for the function."""
|
|
return self.ai_service_selector.select_ai_service(self, function, arguments)
|
|
|
|
def get_service(
|
|
self,
|
|
service_id: str | None = None,
|
|
type: type[AI_SERVICE_CLIENT_TYPE] | tuple[type[AI_SERVICE_CLIENT_TYPE], ...] | None = None,
|
|
) -> AIServiceClientBase:
|
|
"""Get a service by service_id and type.
|
|
|
|
Type is optional and when not supplied, no checks are done.
|
|
Type should be
|
|
TextCompletionClientBase, ChatCompletionClientBase, EmbeddingGeneratorBase
|
|
or a subclass of one.
|
|
You can also check for multiple types in one go,
|
|
by using a tuple: (TextCompletionClientBase, ChatCompletionClientBase).
|
|
|
|
If type and service_id are both None, the first service is returned.
|
|
|
|
Args:
|
|
service_id (str | None): The service id,
|
|
if None, the default service is returned or the first service is returned.
|
|
type (Type[AI_SERVICE_CLIENT_TYPE] | tuple[type[AI_SERVICE_CLIENT_TYPE], ...] | None):
|
|
The type of the service, if None, no checks are done on service type.
|
|
|
|
Returns:
|
|
AIServiceClientBase: The service, should be a class derived from AIServiceClientBase.
|
|
|
|
Raises:
|
|
KernelServiceNotFoundError: If no service is found that matches the type or id.
|
|
|
|
"""
|
|
services = self.get_services_by_type(type)
|
|
if not services:
|
|
raise KernelServiceNotFoundError(f"No services found of type {type}.")
|
|
if not service_id:
|
|
service_id = DEFAULT_SERVICE_NAME
|
|
|
|
if service_id not in services:
|
|
if service_id == DEFAULT_SERVICE_NAME:
|
|
return next(iter(services.values()))
|
|
raise KernelServiceNotFoundError(
|
|
f"Service with service_id '{service_id}' does not exist or has a different type."
|
|
)
|
|
return services[service_id]
|
|
|
|
def get_services_by_type(
|
|
self, type: type[AI_SERVICE_CLIENT_TYPE] | tuple[type[AI_SERVICE_CLIENT_TYPE], ...] | None
|
|
) -> dict[str, AIServiceClientBase]:
|
|
"""Get all services of a specific type."""
|
|
if type is None:
|
|
return self.services
|
|
return {service.service_id: service for service in self.services.values() if isinstance(service, type)}
|
|
|
|
def get_prompt_execution_settings_from_service_id(
|
|
self, service_id: str, type: type[AI_SERVICE_CLIENT_TYPE] | None = None
|
|
) -> PromptExecutionSettings:
|
|
"""Get the specific request settings from the service, instantiated with the service_id and ai_model_id."""
|
|
service = self.get_service(service_id, type=type)
|
|
return service.instantiate_prompt_execution_settings(
|
|
service_id=service_id,
|
|
extension_data={"ai_model_id": service.ai_model_id},
|
|
)
|
|
|
|
def add_service(self, service: AIServiceClientBase, overwrite: bool = False) -> None:
|
|
"""Add a single service to the Kernel.
|
|
|
|
Args:
|
|
service (AIServiceClientBase): The service to add.
|
|
overwrite (bool, optional): Whether to overwrite the service if it already exists. Defaults to False.
|
|
"""
|
|
if service.service_id not in self.services or overwrite:
|
|
self.services[service.service_id] = service
|
|
return
|
|
raise KernelFunctionAlreadyExistsError(f"Service with service_id '{service.service_id}' already exists")
|
|
|
|
def remove_service(self, service_id: str) -> None:
|
|
"""Delete a single service from the Kernel."""
|
|
if service_id not in self.services:
|
|
raise KernelServiceNotFoundError(f"Service with service_id '{service_id}' does not exist")
|
|
del self.services[service_id]
|
|
|
|
def remove_all_services(self) -> None:
|
|
"""Removes the services from the Kernel, does not delete them."""
|
|
self.services.clear()
|