Python: OpenAI Assistants Chat Client and Agent (#288)

* Initial version of assistant client

* More updates to assistant client

* Finished assistant chat client implementation

* Small fixes and basic example

* Added code interpreter example

* More examples

* Added chat client example

* Small fixes

* Added tests

* Enabled telemetry

* Small fix

* Removed files temporarily

* Revert "Removed files temporarily"

This reverts commit 5cdfa0d299.

* Small fixes

* Addressed PR feedback

* Fixed tests

* Small update
This commit is contained in:
Dmytro Struk
2025-08-01 05:12:41 -07:00
committed by GitHub
Unverified
parent 624709e5d1
commit 75794b4687
14 changed files with 1338 additions and 78 deletions
@@ -130,7 +130,7 @@ class FoundryChatClient(ChatClientBase):
nor agent_id is provided, both will be created and managed automatically.
agent_name: The name to use when creating new agents.
thread_id: Default thread ID to use for conversations. Can be overridden by
conversation_id property from ChatOptions, when making a request.
conversation_id property, when making a request.
project_endpoint: The Azure AI Foundry project endpoint URL. Used if client is not provided.
model_deployment_name: The model deployment name to use for agent creation.
credential: Azure async credential to use for authentication. If not provided,
@@ -289,6 +289,7 @@ class FoundryChatClient(ChatClientBase):
# Get any active run for this thread
thread_run = await self._get_active_thread_run(thread_id)
stream: AsyncAgentRunStream[AsyncAgentEventHandler[Any]] | AsyncAgentEventHandler[Any]
handler: AsyncAgentEventHandler[Any] = AsyncAgentEventHandler()
tool_run_id, tool_outputs = self._convert_function_results_to_tool_output(tool_results)
@@ -354,28 +355,21 @@ class FoundryChatClient(ChatClientBase):
thread_id: str,
) -> AsyncIterable[ChatResponseUpdate]:
"""Process events from the agent stream and yield ChatResponseUpdate objects."""
response_id: str | None = None
if stream is not None:
# Use 'async with' only if the stream supports async context management (main agent stream).
# Tool output handlers only support async iteration, not context management.
if isinstance(stream, contextlib.AbstractAsyncContextManager):
async with stream as response_stream: # type: ignore
async for update in self._process_stream_events_from_iterator(
response_stream, thread_id, response_id
):
yield update
else:
async for update in self._process_stream_events_from_iterator(stream, thread_id, response_id):
# Use 'async with' only if the stream supports async context management (main agent stream).
# Tool output handlers only support async iteration, not context management.
if isinstance(stream, contextlib.AbstractAsyncContextManager):
async with stream as response_stream: # type: ignore
async for update in self._process_stream_events_from_iterator(response_stream, thread_id):
yield update
else:
async for update in self._process_stream_events_from_iterator(stream, thread_id):
yield update
async def _process_stream_events_from_iterator(
self,
stream_iter: Any,
thread_id: str,
response_id: str | None,
self, stream_iter: Any, thread_id: str
) -> AsyncIterable[ChatResponseUpdate]:
"""Process events from the stream iterator and yield ChatResponseUpdate objects."""
response_id: str | None = None
async for event_type, event_data, _ in stream_iter: # type: ignore
if event_type == AgentStreamEvent.THREAD_RUN_CREATED and isinstance(event_data, ThreadRun):
yield ChatResponseUpdate(
@@ -496,7 +490,7 @@ class FoundryChatClient(ChatClientBase):
if chat_options.tool_choice != "none" and chat_options.tools is not None:
for tool in chat_options.tools:
if isinstance(tool, AIFunction):
tool_definitions.append(ai_function_to_json_schema_spec(tool))
tool_definitions.append(ai_function_to_json_schema_spec(tool)) # type: ignore[reportUnknownArgumentType]
elif isinstance(tool, HostedCodeInterpreterTool):
tool_definitions.append(CodeInterpreterToolDefinition())
elif isinstance(tool, MutableMapping):