Added changes (#1909)

This commit is contained in:
Dmytro Struk
2025-11-04 13:13:21 -08:00
committed by GitHub
Unverified
parent 8b4aa1ebb5
commit 39d3111734
16 changed files with 4783 additions and 3890 deletions
@@ -6,6 +6,7 @@ from typing import Any
_IMPORTS: dict[str, tuple[str, str]] = {
"AzureAIAgentClient": ("agent_framework_azure_ai", "azure-ai"),
"AzureAIAgentClientV2": ("agent_framework_azure_ai", "azure-ai"),
"AzureOpenAIAssistantsClient": ("agent_framework.azure._assistants_client", "core"),
"AzureOpenAIChatClient": ("agent_framework.azure._chat_client", "core"),
"AzureAISettings": ("agent_framework_azure_ai", "azure-ai"),
@@ -1,6 +1,6 @@
# Copyright (c) Microsoft. All rights reserved.
from agent_framework_azure_ai import AzureAIAgentClient, AzureAISettings
from agent_framework_azure_ai import AzureAIAgentClient, AzureAIAgentClientV2, AzureAISettings
from agent_framework.azure._assistants_client import AzureOpenAIAssistantsClient
from agent_framework.azure._chat_client import AzureOpenAIChatClient
@@ -10,6 +10,7 @@ from agent_framework.azure._shared import AzureOpenAISettings
__all__ = [
"AzureAIAgentClient",
"AzureAIAgentClientV2",
"AzureAISettings",
"AzureOpenAIAssistantsClient",
"AzureOpenAIChatClient",
@@ -89,23 +89,24 @@ class OpenAIBaseResponsesClient(OpenAIBase, BaseChatClient):
chat_options: ChatOptions,
**kwargs: Any,
) -> ChatResponse:
options_dict = self._prepare_options(messages, chat_options)
client = await self.ensure_client()
run_options = await self.prepare_options(messages, chat_options)
try:
if not chat_options.response_format:
response = await self.client.responses.create(
response = await client.responses.create(
stream=False,
**options_dict,
**run_options,
)
chat_options.conversation_id = response.id if chat_options.store is True else None
chat_options.conversation_id = self.get_conversation_id(response, chat_options.store)
return self._create_response_content(response, chat_options=chat_options)
# create call does not support response_format, so we need to handle it via parse call
resp_format = chat_options.response_format
parsed_response: ParsedResponse[BaseModel] = await self.client.responses.parse(
parsed_response: ParsedResponse[BaseModel] = await client.responses.parse(
text_format=resp_format,
stream=False,
**options_dict,
**run_options,
)
chat_options.conversation_id = parsed_response.id if chat_options.store is True else None
chat_options.conversation_id = self.get_conversation_id(parsed_response, chat_options.store)
return self._create_response_content(parsed_response, chat_options=chat_options)
except BadRequestError as ex:
if ex.code == "content_filter":
@@ -130,13 +131,14 @@ class OpenAIBaseResponsesClient(OpenAIBase, BaseChatClient):
chat_options: ChatOptions,
**kwargs: Any,
) -> AsyncIterable[ChatResponseUpdate]:
options_dict = self._prepare_options(messages, chat_options)
client = await self.ensure_client()
run_options = await self.prepare_options(messages, chat_options)
function_call_ids: dict[int, tuple[str, str]] = {} # output_index: (call_id, name)
try:
if not chat_options.response_format:
response = await self.client.responses.create(
response = await client.responses.create(
stream=True,
**options_dict,
**run_options,
)
async for chunk in response:
update = self._create_streaming_response_content(
@@ -145,9 +147,9 @@ class OpenAIBaseResponsesClient(OpenAIBase, BaseChatClient):
yield update
return
# create call does not support response_format, so we need to handle it via stream call
async with self.client.responses.stream(
async with client.responses.stream(
text_format=chat_options.response_format,
**options_dict,
**run_options,
) as response:
async for chunk in response:
update = self._create_streaming_response_content(
@@ -170,6 +172,10 @@ class OpenAIBaseResponsesClient(OpenAIBase, BaseChatClient):
inner_exception=ex,
) from ex
def get_conversation_id(self, response: OpenAIResponse | ParsedResponse[BaseModel], store: bool) -> str | None:
"""Get the conversation ID from the response if store is True."""
return response.id if store else None
# region Prep methods
def _tools_to_response_tools(
@@ -298,9 +304,11 @@ class OpenAIBaseResponsesClient(OpenAIBase, BaseChatClient):
response_tools.append(tool_dict)
return response_tools
def _prepare_options(self, messages: MutableSequence[ChatMessage], chat_options: ChatOptions) -> dict[str, Any]:
async def prepare_options(
self, messages: MutableSequence[ChatMessage], chat_options: ChatOptions
) -> dict[str, Any]:
"""Take ChatOptions and create the specific options for Responses API."""
options_dict: dict[str, Any] = chat_options.to_dict(
run_options: dict[str, Any] = chat_options.to_dict(
exclude={
"type",
"response_format", # handled in inner get methods
@@ -319,35 +327,35 @@ class OpenAIBaseResponsesClient(OpenAIBase, BaseChatClient):
"max_tokens": "max_output_tokens",
}
for old_key, new_key in translations.items():
if old_key in options_dict and old_key != new_key:
options_dict[new_key] = options_dict.pop(old_key)
if old_key in run_options and old_key != new_key:
run_options[new_key] = run_options.pop(old_key)
# tools
if chat_options.tools is None:
options_dict.pop("parallel_tool_calls", None)
run_options.pop("parallel_tool_calls", None)
else:
options_dict["tools"] = self._tools_to_response_tools(chat_options.tools)
run_options["tools"] = self._tools_to_response_tools(chat_options.tools)
# model id
if not options_dict.get("model"):
options_dict["model"] = self.model_id
if not run_options.get("model"):
run_options["model"] = self.model_id
# messages
request_input = self._prepare_chat_messages_for_request(messages)
if not request_input:
raise ServiceInvalidRequestError("Messages are required for chat completions")
options_dict["input"] = request_input
run_options["input"] = request_input
# additional provider specific settings
if additional_properties := options_dict.pop("additional_properties", None):
if additional_properties := run_options.pop("additional_properties", None):
for key, value in additional_properties.items():
if value is not None:
options_dict[key] = value
if "store" not in options_dict:
options_dict["store"] = False
if (tool_choice := options_dict.get("tool_choice")) and len(tool_choice.keys()) == 1:
options_dict["tool_choice"] = tool_choice["mode"]
return options_dict
run_options[key] = value
if "store" not in run_options:
run_options["store"] = False
if (tool_choice := run_options.get("tool_choice")) and len(tool_choice.keys()) == 1:
run_options["tool_choice"] = tool_choice["mode"]
return run_options
def _prepare_chat_messages_for_request(self, chat_messages: Sequence[ChatMessage]) -> list[dict[str, Any]]:
"""Prepare the chat messages for a request.
@@ -749,7 +757,7 @@ class OpenAIBaseResponsesClient(OpenAIBase, BaseChatClient):
"raw_representation": response,
}
if chat_options.store:
args["conversation_id"] = response.id
args["conversation_id"] = self.get_conversation_id(response, chat_options.store)
if response.usage and (usage_details := self._usage_details_from_openai(response.usage)):
args["usage_details"] = usage_details
if structured_response:
@@ -849,7 +857,7 @@ class OpenAIBaseResponsesClient(OpenAIBase, BaseChatClient):
contents.append(TextReasoningContent(text=event.text, raw_representation=event))
metadata.update(self._get_metadata_from_response(event))
case "response.completed":
conversation_id = event.response.id if chat_options.store is True else None
conversation_id = self.get_conversation_id(event.response, chat_options.store)
model = event.response.model
if event.response.usage:
usage = self._usage_details_from_openai(event.response.usage)
@@ -127,7 +127,7 @@ class OpenAIBase(SerializationMixin):
INJECTABLE: ClassVar[set[str]] = {"client"}
def __init__(self, *, client: AsyncOpenAI, model_id: str, **kwargs: Any) -> None:
def __init__(self, *, model_id: str, client: AsyncOpenAI | None = None, **kwargs: Any) -> None:
"""Initialize OpenAIBase.
Keyword Args:
@@ -162,6 +162,21 @@ class OpenAIBase(SerializationMixin):
for key, value in kwargs.items():
setattr(self, key, value)
async def initialize_client(self):
"""Initialize OpenAI client asynchronously.
Override in subclasses to initialize the OpenAI client asynchronously.
"""
pass
async def ensure_client(self) -> AsyncOpenAI:
"""Ensure OpenAI client is initialized."""
await self.initialize_client()
if self.client is None:
raise ServiceInitializationError("OpenAI client is not initialized")
return self.client
def _get_api_key(
self, api_key: str | SecretStr | Callable[[], str | Awaitable[str]] | None
) -> str | Callable[[], str | Awaitable[str]] | None: