Skip to content

Commit bd34da9

Browse files
authored
Python: New AI Connector abstract methods (microsoft#8526)
### 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. --> This PR implements: https://github.com/microsoft/semantic-kernel/blob/main/docs/decisions/0052-python-ai-connector-new-abstract-methods.md (PR: microsoft#8430). ### Description <!-- Describe your changes, the overall approach, the underlying design. These notes will help understanding how your code works. Thanks! --> 1. Add abstract methods `_inner_get_chat_message_content` and `_inner_get_streaming_chat_message_content` to `ChatCompletionClientBase`. 2. Implement the abstractions in all chat completion connectors. 3. Add abstract methods `_inner_get_text_contents` and `_inner_get_streaming_text_contents` to `TextCompletionClientBase`. 4. Implement the abstractions in all text completion connectors. 5. Remove text completion APIs from `OllamaChatCompletion` (breaking changes). > No breaking changes on other connectors except Ollama, and public APIs stay the same. ### 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 😄
1 parent 24294bf commit bd34da9

26 files changed

Lines changed: 912 additions & 1422 deletions

‎docs/decisions/0052-python-ai-connector-new-abstract-methods.md‎

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,24 +30,24 @@ Auto function invocation can cause a side effect where a single call to get_chat
3030

3131
### Two new abstract methods
3232

33+
> Revision: In order to not break existing customers who have implemented their own AI connectors, these two methods are not decorated with the `@abstractmethod` decorator, but instead throw an exception if they are not implemented in the built-in AI connectors.
34+
3335
```python
34-
@abstractmethod
35-
async def _send_chat_request(
36+
async def _inner_get_chat_message_content(
3637
self,
3738
chat_history: ChatHistory,
3839
settings: PromptExecutionSettings
3940
) -> list[ChatMessageContent]:
40-
pass
41+
raise NotImplementedError
4142
```
4243

4344
```python
44-
@abstractmethod
45-
async def _send_streaming_chat_request(
45+
async def _inner_get_streaming_chat_message_content(
4646
self,
4747
chat_history: ChatHistory,
4848
settings: PromptExecutionSettings
4949
) -> AsyncGenerator[list[StreamingChatMessageContent], Any]:
50-
pass
50+
raise NotImplementedError
5151
```
5252

5353
### A new `ClassVar[bool]` variable in `ChatCompletionClientBase` to indicate whether a connector supports function calling

‎python/.cspell.json‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
"contoso",
5858
"opentelemetry",
5959
"SEMANTICKERNEL",
60-
"OTEL"
60+
"OTEL",
61+
"mistralai"
6162
]
6263
}

‎python/semantic_kernel/connectors/ai/anthropic/services/anthropic_chat_completion.py‎

Lines changed: 18 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ class AnthropicChatCompletion(ChatCompletionClientBase):
5454
"""Antropic ChatCompletion class."""
5555

5656
MODEL_PROVIDER_NAME: ClassVar[str] = "anthropic"
57+
SUPPORTS_FUNCTION_CALLING: ClassVar[bool] = False
5758

5859
async_client: AsyncAnthropic
5960

@@ -103,31 +104,25 @@ def __init__(
103104
ai_model_id=anthropic_settings.chat_model_id,
104105
)
105106

107+
# region Overriding base class methods
108+
109+
# Override from AIServiceClientBase
110+
@override
111+
def get_prompt_execution_settings_class(self) -> type["PromptExecutionSettings"]:
112+
return AnthropicChatPromptExecutionSettings
113+
106114
@override
107115
@trace_chat_completion(MODEL_PROVIDER_NAME)
108-
async def get_chat_message_contents(
116+
async def _inner_get_chat_message_contents(
109117
self,
110118
chat_history: "ChatHistory",
111119
settings: "PromptExecutionSettings",
112-
**kwargs: Any,
113120
) -> list["ChatMessageContent"]:
114-
"""Executes a chat completion request and returns the result.
115-
116-
Args:
117-
chat_history: The chat history to use for the chat completion.
118-
settings: The settings to use for the chat completion request.
119-
kwargs: The optional arguments.
120-
121-
Returns:
122-
The completion result(s).
123-
"""
124121
if not isinstance(settings, AnthropicChatPromptExecutionSettings):
125122
settings = self.get_prompt_execution_settings_from_settings(settings)
126123
assert isinstance(settings, AnthropicChatPromptExecutionSettings) # nosec
127124

128-
if not settings.ai_model_id:
129-
settings.ai_model_id = self.ai_model_id
130-
125+
settings.ai_model_id = settings.ai_model_id or self.ai_model_id
131126
settings.messages = self._prepare_chat_history_for_request(chat_history)
132127
try:
133128
response = await self.async_client.messages.create(**settings.prepare_settings_dict())
@@ -146,29 +141,17 @@ async def get_chat_message_contents(
146141
self._create_chat_message_content(response, content_block, metadata) for content_block in response.content
147142
]
148143

149-
async def get_streaming_chat_message_contents(
144+
@override
145+
async def _inner_get_streaming_chat_message_contents(
150146
self,
151-
chat_history: ChatHistory,
152-
settings: PromptExecutionSettings,
153-
**kwargs: Any,
154-
) -> AsyncGenerator[list[StreamingChatMessageContent], Any]:
155-
"""Executes a streaming chat completion request and returns the result.
156-
157-
Args:
158-
chat_history: The chat history to use for the chat completion.
159-
settings: The settings to use for the chat completion request.
160-
kwargs: The optional arguments.
161-
162-
Yields:
163-
A stream of StreamingChatMessageContent.
164-
"""
147+
chat_history: "ChatHistory",
148+
settings: "PromptExecutionSettings",
149+
) -> AsyncGenerator[list["StreamingChatMessageContent"], Any]:
165150
if not isinstance(settings, AnthropicChatPromptExecutionSettings):
166151
settings = self.get_prompt_execution_settings_from_settings(settings)
167152
assert isinstance(settings, AnthropicChatPromptExecutionSettings) # nosec
168153

169-
if not settings.ai_model_id:
170-
settings.ai_model_id = self.ai_model_id
171-
154+
settings.ai_model_id = settings.ai_model_id or self.ai_model_id
172155
settings.messages = self._prepare_chat_history_for_request(chat_history)
173156
try:
174157
async with self.async_client.messages.stream(**settings.prepare_settings_dict()) as stream:
@@ -189,13 +172,14 @@ async def get_streaming_chat_message_contents(
189172
]
190173
elif isinstance(stream_event, ContentBlockStopEvent):
191174
content_block_idx += 1
192-
193175
except Exception as ex:
194176
raise ServiceResponseException(
195177
f"{type(self)} service failed to complete the request",
196178
ex,
197179
) from ex
198180

181+
# endregion
182+
199183
def _create_chat_message_content(
200184
self, response: Message, content: TextBlock, response_metadata: dict[str, Any]
201185
) -> "ChatMessageContent":
@@ -249,7 +233,3 @@ def _create_streaming_chat_message_content(
249233
finish_reason=finish_reason,
250234
items=items,
251235
)
252-
253-
def get_prompt_execution_settings_class(self) -> "type[AnthropicChatPromptExecutionSettings]":
254-
"""Create a request settings object."""
255-
return AnthropicChatPromptExecutionSettings

0 commit comments

Comments
 (0)