2
2
3
3
import logging
4
4
from abc import ABC
5
- from typing import TYPE_CHECKING , TypeVar , Union
5
+ from typing import TYPE_CHECKING
6
6
7
7
from pydantic import Field , field_validator
8
8
9
9
from semantic_kernel .connectors .ai .prompt_execution_settings import PromptExecutionSettings
10
- from semantic_kernel .exceptions import (
11
- KernelFunctionAlreadyExistsError ,
12
- KernelServiceNotFoundError ,
13
- ServiceInvalidTypeError ,
14
- )
15
- from semantic_kernel .functions .kernel_arguments import KernelArguments
10
+ from semantic_kernel .const import DEFAULT_SERVICE_NAME
11
+ from semantic_kernel .exceptions import KernelFunctionAlreadyExistsError , KernelServiceNotFoundError
16
12
from semantic_kernel .kernel_pydantic import KernelBaseModel
13
+ from semantic_kernel .kernel_types import AI_SERVICE_CLIENT_TYPE
17
14
from semantic_kernel .services .ai_service_client_base import AIServiceClientBase
18
15
from semantic_kernel .services .ai_service_selector import AIServiceSelector
19
16
20
17
if TYPE_CHECKING :
21
- from semantic_kernel .connectors .ai .chat_completion_client_base import ChatCompletionClientBase
22
- from semantic_kernel .connectors .ai .embeddings .embedding_generator_base import EmbeddingGeneratorBase
23
- from semantic_kernel .connectors .ai .text_completion_client_base import TextCompletionClientBase
18
+ from semantic_kernel .functions .kernel_arguments import KernelArguments
24
19
from semantic_kernel .functions .kernel_function import KernelFunction
25
20
26
- T = TypeVar ("T" )
27
-
28
- AI_SERVICE_CLIENT_TYPE = TypeVar ("AI_SERVICE_CLIENT_TYPE" , bound = AIServiceClientBase )
29
- ALL_SERVICE_TYPES = Union ["TextCompletionClientBase" , "ChatCompletionClientBase" , "EmbeddingGeneratorBase" ]
30
-
31
21
32
22
logger : logging .Logger = logging .getLogger (__name__ )
33
23
@@ -48,69 +38,70 @@ def rewrite_services(
48
38
if not services :
49
39
return {}
50
40
if isinstance (services , AIServiceClientBase ):
51
- return {services .service_id if services .service_id else "default" : services } # type: ignore
41
+ return {services .service_id if services .service_id else DEFAULT_SERVICE_NAME : services } # type: ignore
52
42
if isinstance (services , list ):
53
- return {s .service_id if s .service_id else "default" : s for s in services }
43
+ return {s .service_id if s .service_id else DEFAULT_SERVICE_NAME : s for s in services }
54
44
return services
55
45
56
46
def select_ai_service (
57
- self , function : "KernelFunction" , arguments : KernelArguments
58
- ) -> tuple [ALL_SERVICE_TYPES , PromptExecutionSettings ]:
47
+ self , function : "KernelFunction" , arguments : " KernelArguments"
48
+ ) -> tuple [AIServiceClientBase , PromptExecutionSettings ]:
59
49
"""Uses the AI service selector to select a service for the function."""
60
50
return self .ai_service_selector .select_ai_service (self , function , arguments )
61
51
62
52
def get_service (
63
53
self ,
64
54
service_id : str | None = None ,
65
- type : type [ALL_SERVICE_TYPES ] | None = None ,
66
- ) -> " AIServiceClientBase" :
55
+ type : type [AI_SERVICE_CLIENT_TYPE ] | tuple [ type [ AI_SERVICE_CLIENT_TYPE ], ... ] | None = None ,
56
+ ) -> AIServiceClientBase :
67
57
"""Get a service by service_id and type.
68
58
69
59
Type is optional and when not supplied, no checks are done.
70
60
Type should be
71
61
TextCompletionClientBase, ChatCompletionClientBase, EmbeddingGeneratorBase
72
62
or a subclass of one.
73
63
You can also check for multiple types in one go,
74
- by using TextCompletionClientBase | ChatCompletionClientBase.
64
+ by using a tuple: (TextCompletionClientBase, ChatCompletionClientBase) .
75
65
76
66
If type and service_id are both None, the first service is returned.
77
67
78
68
Args:
79
69
service_id (str | None): The service id,
80
70
if None, the default service is returned or the first service is returned.
81
- type (Type[ALL_SERVICE_TYPES] | None): The type of the service, if None, no checks are done.
71
+ type (Type[AI_SERVICE_CLIENT_TYPE] | tuple[type[AI_SERVICE_CLIENT_TYPE], ...] | None):
72
+ The type of the service, if None, no checks are done on service type.
82
73
83
74
Returns:
84
- ALL_SERVICE_TYPES : The service.
75
+ AIServiceClientBase : The service, should be a class derived from AIServiceClientBase .
85
76
86
77
Raises:
87
- ValueError : If no service is found that matches the type.
78
+ KernelServiceNotFoundError : If no service is found that matches the type or id .
88
79
89
80
"""
90
- service : "AIServiceClientBase | None" = None
91
- if not service_id or service_id == "default" :
92
- if not type :
93
- if default_service := self .services .get ("default" ):
94
- return default_service
95
- return next (iter (self .services .values ()))
96
- if (default_service := self .services .get ("default" )) and isinstance (default_service , type ):
97
- return default_service
98
- for service in self .services .values ():
99
- if isinstance (service , type ):
100
- return service
101
- raise KernelServiceNotFoundError (f"No service found of type { type } " )
102
- if not (service := self .services .get (service_id )):
103
- raise KernelServiceNotFoundError (f"Service with service_id '{ service_id } ' does not exist" )
104
- if type and not isinstance (service , type ):
105
- raise ServiceInvalidTypeError (f"Service with service_id '{ service_id } ' is not of type { type } " )
106
- return service
107
-
108
- def get_services_by_type (self , type : type [ALL_SERVICE_TYPES ]) -> dict [str , ALL_SERVICE_TYPES ]:
81
+ services = self .get_services_by_type (type )
82
+ if not services :
83
+ raise KernelServiceNotFoundError (f"No services found of type { type } ." )
84
+ if not service_id :
85
+ service_id = DEFAULT_SERVICE_NAME
86
+
87
+ if service_id not in services :
88
+ if service_id == DEFAULT_SERVICE_NAME :
89
+ return next (iter (services .values ()))
90
+ raise KernelServiceNotFoundError (
91
+ f"Service with service_id '{ service_id } ' does not exist or has a different type."
92
+ )
93
+ return services [service_id ]
94
+
95
+ def get_services_by_type (
96
+ self , type : type [AI_SERVICE_CLIENT_TYPE ] | tuple [type [AI_SERVICE_CLIENT_TYPE ], ...] | None
97
+ ) -> dict [str , AIServiceClientBase ]:
109
98
"""Get all services of a specific type."""
110
- return {service .service_id : service for service in self .services .values () if isinstance (service , type )} # type: ignore
99
+ if type is None :
100
+ return self .services
101
+ return {service .service_id : service for service in self .services .values () if isinstance (service , type )}
111
102
112
103
def get_prompt_execution_settings_from_service_id (
113
- self , service_id : str , type : type [ALL_SERVICE_TYPES ] | None = None
104
+ self , service_id : str , type : type [AI_SERVICE_CLIENT_TYPE ] | None = None
114
105
) -> PromptExecutionSettings :
115
106
"""Get the specific request settings from the service, instantiated with the service_id and ai_model_id."""
116
107
service = self .get_service (service_id , type = type )
@@ -128,8 +119,8 @@ def add_service(self, service: AIServiceClientBase, overwrite: bool = False) ->
128
119
"""
129
120
if service .service_id not in self .services or overwrite :
130
121
self .services [service .service_id ] = service
131
- else :
132
- raise KernelFunctionAlreadyExistsError (f"Service with service_id '{ service .service_id } ' already exists" )
122
+ return
123
+ raise KernelFunctionAlreadyExistsError (f"Service with service_id '{ service .service_id } ' already exists" )
133
124
134
125
def remove_service (self , service_id : str ) -> None :
135
126
"""Delete a single service from the Kernel."""
0 commit comments