agent-framework-openai 1.0.0rc6__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- agent_framework_openai/__init__.py +87 -0
- agent_framework_openai/_assistant_provider.py +564 -0
- agent_framework_openai/_assistants_client.py +968 -0
- agent_framework_openai/_chat_client.py +2729 -0
- agent_framework_openai/_chat_completion_client.py +1326 -0
- agent_framework_openai/_embedding_client.py +509 -0
- agent_framework_openai/_exceptions.py +90 -0
- agent_framework_openai/_shared.py +629 -0
- agent_framework_openai/py.typed +0 -0
- agent_framework_openai-1.0.0rc6.dist-info/METADATA +133 -0
- agent_framework_openai-1.0.0rc6.dist-info/RECORD +13 -0
- agent_framework_openai-1.0.0rc6.dist-info/WHEEL +4 -0
- agent_framework_openai-1.0.0rc6.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# Copyright (c) Microsoft. All rights reserved.
|
|
2
|
+
|
|
3
|
+
"""OpenAI integration for Microsoft Agent Framework.
|
|
4
|
+
|
|
5
|
+
This package provides OpenAI client implementations for the Agent Framework,
|
|
6
|
+
including clients for the Responses API and Chat Completions API.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import importlib.metadata
|
|
10
|
+
import sys
|
|
11
|
+
|
|
12
|
+
if sys.version_info >= (3, 13):
|
|
13
|
+
from warnings import deprecated # type: ignore # pragma: no cover
|
|
14
|
+
else:
|
|
15
|
+
from typing_extensions import deprecated # type: ignore # pragma: no cover
|
|
16
|
+
|
|
17
|
+
from ._assistant_provider import OpenAIAssistantProvider
|
|
18
|
+
from ._assistants_client import (
|
|
19
|
+
AssistantToolResources,
|
|
20
|
+
OpenAIAssistantsClient, # type: ignore[reportDeprecated]
|
|
21
|
+
OpenAIAssistantsOptions,
|
|
22
|
+
)
|
|
23
|
+
from ._chat_client import (
|
|
24
|
+
OpenAIChatClient,
|
|
25
|
+
OpenAIChatOptions,
|
|
26
|
+
OpenAIContinuationToken,
|
|
27
|
+
RawOpenAIChatClient,
|
|
28
|
+
)
|
|
29
|
+
from ._chat_completion_client import (
|
|
30
|
+
OpenAIChatCompletionClient,
|
|
31
|
+
OpenAIChatCompletionOptions,
|
|
32
|
+
RawOpenAIChatCompletionClient,
|
|
33
|
+
)
|
|
34
|
+
from ._embedding_client import OpenAIEmbeddingClient, OpenAIEmbeddingOptions
|
|
35
|
+
from ._exceptions import ContentFilterResultSeverity, OpenAIContentFilterException
|
|
36
|
+
from ._shared import OpenAISettings
|
|
37
|
+
|
|
38
|
+
try:
|
|
39
|
+
__version__ = importlib.metadata.version("agent-framework-openai")
|
|
40
|
+
except importlib.metadata.PackageNotFoundError:
|
|
41
|
+
__version__ = "0.0.0" # Fallback for development mode
|
|
42
|
+
|
|
43
|
+
# Deprecated aliases for old names — use subclasses so the warning only fires for the alias
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@deprecated(
|
|
47
|
+
"OpenAIResponsesClient is deprecated, use OpenAIChatClient instead.",
|
|
48
|
+
category=DeprecationWarning,
|
|
49
|
+
)
|
|
50
|
+
class OpenAIResponsesClient(OpenAIChatClient): # type: ignore[misc]
|
|
51
|
+
"""Deprecated alias for :class:`OpenAIChatClient`."""
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@deprecated(
|
|
55
|
+
"RawOpenAIResponsesClient is deprecated, use RawOpenAIChatClient instead.",
|
|
56
|
+
category=DeprecationWarning,
|
|
57
|
+
)
|
|
58
|
+
class RawOpenAIResponsesClient(RawOpenAIChatClient): # type: ignore[misc]
|
|
59
|
+
"""Deprecated alias for :class:`RawOpenAIChatClient`."""
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
OpenAIResponsesOptions = OpenAIChatOptions
|
|
63
|
+
"""Deprecated alias for :class:`OpenAIChatOptions`."""
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
__all__ = [
|
|
67
|
+
"AssistantToolResources",
|
|
68
|
+
"ContentFilterResultSeverity",
|
|
69
|
+
"OpenAIAssistantProvider",
|
|
70
|
+
"OpenAIAssistantsClient",
|
|
71
|
+
"OpenAIAssistantsOptions",
|
|
72
|
+
"OpenAIChatClient",
|
|
73
|
+
"OpenAIChatCompletionClient",
|
|
74
|
+
"OpenAIChatCompletionOptions",
|
|
75
|
+
"OpenAIChatOptions",
|
|
76
|
+
"OpenAIContentFilterException",
|
|
77
|
+
"OpenAIContinuationToken",
|
|
78
|
+
"OpenAIEmbeddingClient",
|
|
79
|
+
"OpenAIEmbeddingOptions",
|
|
80
|
+
"OpenAIResponsesClient",
|
|
81
|
+
"OpenAIResponsesOptions",
|
|
82
|
+
"OpenAISettings",
|
|
83
|
+
"RawOpenAIChatClient",
|
|
84
|
+
"RawOpenAIChatCompletionClient",
|
|
85
|
+
"RawOpenAIResponsesClient",
|
|
86
|
+
"__version__",
|
|
87
|
+
]
|
|
@@ -0,0 +1,564 @@
|
|
|
1
|
+
# Copyright (c) Microsoft. All rights reserved.
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import sys
|
|
6
|
+
from collections.abc import Awaitable, Callable, Mapping, MutableMapping, Sequence
|
|
7
|
+
from typing import TYPE_CHECKING, Any, Generic, cast
|
|
8
|
+
|
|
9
|
+
from agent_framework._agents import Agent
|
|
10
|
+
from agent_framework._middleware import MiddlewareTypes
|
|
11
|
+
from agent_framework._sessions import BaseContextProvider
|
|
12
|
+
from agent_framework._settings import SecretString, load_settings
|
|
13
|
+
from agent_framework._tools import FunctionTool, ToolTypes, normalize_tools
|
|
14
|
+
from openai import AsyncOpenAI
|
|
15
|
+
from openai.types.beta.assistant import Assistant
|
|
16
|
+
from pydantic import BaseModel
|
|
17
|
+
|
|
18
|
+
from ._assistants_client import OpenAIAssistantsClient # type: ignore[reportDeprecated]
|
|
19
|
+
from ._shared import OpenAISettings, from_assistant_tools, to_assistant_tools
|
|
20
|
+
|
|
21
|
+
if TYPE_CHECKING:
|
|
22
|
+
from ._assistants_client import OpenAIAssistantsOptions
|
|
23
|
+
|
|
24
|
+
if sys.version_info >= (3, 13):
|
|
25
|
+
from typing import TypeVar # type:ignore # pragma: no cover
|
|
26
|
+
else:
|
|
27
|
+
from typing_extensions import TypeVar # type:ignore # pragma: no cover
|
|
28
|
+
if sys.version_info >= (3, 11):
|
|
29
|
+
from typing import Self, TypedDict # type:ignore # pragma: no cover
|
|
30
|
+
else:
|
|
31
|
+
from typing_extensions import Self, TypedDict # type:ignore # pragma: no cover
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
# Type variable for options - allows typed OpenAIAssistantProvider[OptionsCoT] returns
|
|
35
|
+
# Default matches OpenAIAssistantsClient's default options type
|
|
36
|
+
OptionsCoT = TypeVar(
|
|
37
|
+
"OptionsCoT",
|
|
38
|
+
bound=TypedDict, # type: ignore[valid-type]
|
|
39
|
+
default="OpenAIAssistantsOptions",
|
|
40
|
+
covariant=True,
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class OpenAIAssistantProvider(Generic[OptionsCoT]):
|
|
45
|
+
"""Provider for creating Agent instances from OpenAI Assistants API.
|
|
46
|
+
|
|
47
|
+
This provider allows you to create, retrieve, and wrap OpenAI Assistants
|
|
48
|
+
as Agent instances for use in the agent framework.
|
|
49
|
+
|
|
50
|
+
Examples:
|
|
51
|
+
Basic usage with automatic client creation:
|
|
52
|
+
|
|
53
|
+
.. code-block:: python
|
|
54
|
+
|
|
55
|
+
from agent_framework.openai import OpenAIAssistantProvider
|
|
56
|
+
|
|
57
|
+
# Uses OPENAI_API_KEY environment variable
|
|
58
|
+
provider = OpenAIAssistantProvider()
|
|
59
|
+
|
|
60
|
+
# Create a new assistant
|
|
61
|
+
agent = await provider.create_agent(
|
|
62
|
+
name="MyAssistant",
|
|
63
|
+
model="gpt-4",
|
|
64
|
+
instructions="You are a helpful assistant.",
|
|
65
|
+
tools=[my_function],
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
result = await agent.run("Hello!")
|
|
69
|
+
|
|
70
|
+
Using an existing client:
|
|
71
|
+
|
|
72
|
+
.. code-block:: python
|
|
73
|
+
|
|
74
|
+
from openai import AsyncOpenAI
|
|
75
|
+
from agent_framework.openai import OpenAIAssistantProvider
|
|
76
|
+
|
|
77
|
+
client = AsyncOpenAI()
|
|
78
|
+
provider = OpenAIAssistantProvider(client)
|
|
79
|
+
|
|
80
|
+
# Get an existing assistant by ID
|
|
81
|
+
agent = await provider.get_agent(
|
|
82
|
+
assistant_id="asst_123",
|
|
83
|
+
tools=[my_function], # Provide implementations for function tools
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
Wrapping an SDK Assistant object:
|
|
87
|
+
|
|
88
|
+
.. code-block:: python
|
|
89
|
+
|
|
90
|
+
# Fetch assistant directly via SDK
|
|
91
|
+
assistant = await client.beta.assistants.retrieve("asst_123")
|
|
92
|
+
|
|
93
|
+
# Wrap without additional HTTP call
|
|
94
|
+
agent = provider.as_agent(assistant, tools=[my_function])
|
|
95
|
+
"""
|
|
96
|
+
|
|
97
|
+
def __init__(
|
|
98
|
+
self,
|
|
99
|
+
client: AsyncOpenAI | None = None,
|
|
100
|
+
*,
|
|
101
|
+
api_key: str | SecretString | Callable[[], str | Awaitable[str]] | None = None,
|
|
102
|
+
org_id: str | None = None,
|
|
103
|
+
base_url: str | None = None,
|
|
104
|
+
env_file_path: str | None = None,
|
|
105
|
+
env_file_encoding: str | None = None,
|
|
106
|
+
) -> None:
|
|
107
|
+
"""Initialize the OpenAI Assistant Provider.
|
|
108
|
+
|
|
109
|
+
Args:
|
|
110
|
+
client: An existing AsyncOpenAI client to use. If not provided,
|
|
111
|
+
a new client will be created using the other parameters.
|
|
112
|
+
|
|
113
|
+
Keyword Args:
|
|
114
|
+
api_key: OpenAI API key. Can also be set via OPENAI_API_KEY env var.
|
|
115
|
+
org_id: OpenAI organization ID. Can also be set via OPENAI_ORG_ID env var.
|
|
116
|
+
base_url: Base URL for the OpenAI API. Can also be set via OPENAI_BASE_URL env var.
|
|
117
|
+
env_file_path: Path to .env file for configuration.
|
|
118
|
+
env_file_encoding: Encoding of the .env file.
|
|
119
|
+
|
|
120
|
+
Raises:
|
|
121
|
+
ValueError: If no client is provided and API key is missing.
|
|
122
|
+
|
|
123
|
+
Examples:
|
|
124
|
+
.. code-block:: python
|
|
125
|
+
|
|
126
|
+
# Using environment variables
|
|
127
|
+
provider = OpenAIAssistantProvider()
|
|
128
|
+
|
|
129
|
+
# Using explicit API key
|
|
130
|
+
provider = OpenAIAssistantProvider(api_key="sk-...")
|
|
131
|
+
|
|
132
|
+
# Using existing client
|
|
133
|
+
client = AsyncOpenAI()
|
|
134
|
+
provider = OpenAIAssistantProvider(client)
|
|
135
|
+
"""
|
|
136
|
+
self._client: AsyncOpenAI | None = client
|
|
137
|
+
self._should_close_client: bool = client is None
|
|
138
|
+
|
|
139
|
+
if client is None:
|
|
140
|
+
# Load settings and create client
|
|
141
|
+
settings = load_settings(
|
|
142
|
+
OpenAISettings,
|
|
143
|
+
env_prefix="OPENAI_",
|
|
144
|
+
api_key=api_key,
|
|
145
|
+
org_id=org_id,
|
|
146
|
+
base_url=base_url,
|
|
147
|
+
env_file_path=env_file_path,
|
|
148
|
+
env_file_encoding=env_file_encoding,
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
api_key_setting = settings.get("api_key")
|
|
152
|
+
if not api_key_setting:
|
|
153
|
+
raise ValueError(
|
|
154
|
+
"OpenAI API key is required. Set via 'api_key' parameter or 'OPENAI_API_KEY' environment variable."
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
# Get API key value
|
|
158
|
+
api_key_value: str | Callable[[], str | Awaitable[str]]
|
|
159
|
+
if isinstance(api_key_setting, SecretString):
|
|
160
|
+
api_key_value = api_key_setting.get_secret_value()
|
|
161
|
+
else:
|
|
162
|
+
api_key_value = api_key_setting
|
|
163
|
+
|
|
164
|
+
# Create client
|
|
165
|
+
client_args: dict[str, Any] = {"api_key": api_key_value}
|
|
166
|
+
if org_id_value := settings.get("org_id"):
|
|
167
|
+
client_args["organization"] = org_id_value
|
|
168
|
+
if base_url_value := settings.get("base_url"):
|
|
169
|
+
client_args["base_url"] = base_url_value
|
|
170
|
+
|
|
171
|
+
self._client = AsyncOpenAI(**client_args)
|
|
172
|
+
|
|
173
|
+
async def __aenter__(self) -> Self:
|
|
174
|
+
"""Async context manager entry."""
|
|
175
|
+
return self
|
|
176
|
+
|
|
177
|
+
async def __aexit__(self, exc_type: type[BaseException] | None, exc_val: BaseException | None, exc_tb: Any) -> None:
|
|
178
|
+
"""Async context manager exit."""
|
|
179
|
+
await self.close()
|
|
180
|
+
|
|
181
|
+
async def close(self) -> None:
|
|
182
|
+
"""Close the provider and clean up resources.
|
|
183
|
+
|
|
184
|
+
If the provider created its own client, it will be closed.
|
|
185
|
+
If an external client was provided, it will not be closed.
|
|
186
|
+
"""
|
|
187
|
+
if self._should_close_client and self._client is not None:
|
|
188
|
+
await self._client.close()
|
|
189
|
+
|
|
190
|
+
async def create_agent(
|
|
191
|
+
self,
|
|
192
|
+
*,
|
|
193
|
+
name: str,
|
|
194
|
+
model: str,
|
|
195
|
+
instructions: str | None = None,
|
|
196
|
+
description: str | None = None,
|
|
197
|
+
tools: ToolTypes | Callable[..., Any] | Sequence[ToolTypes | Callable[..., Any]] | None = None,
|
|
198
|
+
metadata: dict[str, str] | None = None,
|
|
199
|
+
default_options: OptionsCoT | None = None,
|
|
200
|
+
middleware: Sequence[MiddlewareTypes] | None = None,
|
|
201
|
+
context_providers: Sequence[BaseContextProvider] | None = None,
|
|
202
|
+
) -> Agent[OptionsCoT]:
|
|
203
|
+
"""Create a new assistant on OpenAI and return a Agent.
|
|
204
|
+
|
|
205
|
+
This method creates a new assistant on the OpenAI service and wraps it
|
|
206
|
+
in a Agent instance. The assistant will persist on OpenAI until deleted.
|
|
207
|
+
|
|
208
|
+
Keyword Args:
|
|
209
|
+
name: The name of the assistant (required).
|
|
210
|
+
model: The model ID to use, e.g., "gpt-4", "gpt-4o" (required).
|
|
211
|
+
instructions: System instructions for the assistant.
|
|
212
|
+
description: A description of the assistant.
|
|
213
|
+
tools: Tools available to the assistant. Can include:
|
|
214
|
+
- FunctionTool instances or callables decorated with @tool
|
|
215
|
+
- Dict-based tools from OpenAIAssistantsClient.get_code_interpreter_tool()
|
|
216
|
+
- Dict-based tools from OpenAIAssistantsClient.get_file_search_tool()
|
|
217
|
+
- Raw tool dictionaries
|
|
218
|
+
metadata: Metadata to attach to the assistant (max 16 key-value pairs).
|
|
219
|
+
default_options: A TypedDict containing default chat options for the agent.
|
|
220
|
+
These options are applied to every run unless overridden.
|
|
221
|
+
Include ``response_format`` here for structured output responses.
|
|
222
|
+
middleware: MiddlewareTypes for the Agent.
|
|
223
|
+
context_providers: Context providers for the Agent.
|
|
224
|
+
|
|
225
|
+
Returns:
|
|
226
|
+
A Agent instance wrapping the created assistant.
|
|
227
|
+
|
|
228
|
+
Raises:
|
|
229
|
+
ValueError: If assistant creation fails.
|
|
230
|
+
|
|
231
|
+
Examples:
|
|
232
|
+
.. code-block:: python
|
|
233
|
+
|
|
234
|
+
provider = OpenAIAssistantProvider()
|
|
235
|
+
|
|
236
|
+
# Create with function tools
|
|
237
|
+
agent = await provider.create_agent(
|
|
238
|
+
name="WeatherBot",
|
|
239
|
+
model="gpt-4",
|
|
240
|
+
instructions="You are a helpful weather assistant.",
|
|
241
|
+
tools=[get_weather],
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
# Create with structured output
|
|
245
|
+
agent = await provider.create_agent(
|
|
246
|
+
name="StructuredBot",
|
|
247
|
+
model="gpt-4",
|
|
248
|
+
default_options={"response_format": MyPydanticModel},
|
|
249
|
+
)
|
|
250
|
+
"""
|
|
251
|
+
# Normalize tools
|
|
252
|
+
normalized_tools = normalize_tools(tools)
|
|
253
|
+
assistant_tools: list[FunctionTool | MutableMapping[str, Any]] = [
|
|
254
|
+
tool for tool in normalized_tools if isinstance(tool, (FunctionTool, MutableMapping))
|
|
255
|
+
]
|
|
256
|
+
api_tools = to_assistant_tools(assistant_tools) if assistant_tools else []
|
|
257
|
+
|
|
258
|
+
# Extract response_format from default_options if present
|
|
259
|
+
opts = dict(default_options) if default_options else {}
|
|
260
|
+
response_format = opts.get("response_format")
|
|
261
|
+
|
|
262
|
+
# Build assistant creation parameters
|
|
263
|
+
create_params: dict[str, Any] = {
|
|
264
|
+
"model": model,
|
|
265
|
+
"name": name,
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
if instructions is not None:
|
|
269
|
+
create_params["instructions"] = instructions
|
|
270
|
+
if description is not None:
|
|
271
|
+
create_params["description"] = description
|
|
272
|
+
if api_tools:
|
|
273
|
+
create_params["tools"] = api_tools
|
|
274
|
+
if metadata is not None:
|
|
275
|
+
create_params["metadata"] = metadata
|
|
276
|
+
|
|
277
|
+
# Handle response format for OpenAI API
|
|
278
|
+
if response_format is not None and isinstance(response_format, type) and issubclass(response_format, BaseModel):
|
|
279
|
+
create_params["response_format"] = {
|
|
280
|
+
"type": "json_schema",
|
|
281
|
+
"json_schema": {
|
|
282
|
+
"name": response_format.__name__,
|
|
283
|
+
"schema": response_format.model_json_schema(),
|
|
284
|
+
"strict": True,
|
|
285
|
+
},
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
# Create the assistant
|
|
289
|
+
if not self._client:
|
|
290
|
+
raise RuntimeError("OpenAI client is not initialized.")
|
|
291
|
+
|
|
292
|
+
assistant = await self._client.beta.assistants.create(**create_params) # type: ignore[reportDeprecated]
|
|
293
|
+
|
|
294
|
+
# Create Agent - pass default_options which contains response_format
|
|
295
|
+
return self._create_chat_agent_from_assistant(
|
|
296
|
+
assistant=assistant,
|
|
297
|
+
tools=normalized_tools,
|
|
298
|
+
instructions=instructions,
|
|
299
|
+
middleware=middleware,
|
|
300
|
+
context_providers=context_providers,
|
|
301
|
+
default_options=default_options,
|
|
302
|
+
)
|
|
303
|
+
|
|
304
|
+
async def get_agent(
|
|
305
|
+
self,
|
|
306
|
+
assistant_id: str,
|
|
307
|
+
*,
|
|
308
|
+
tools: ToolTypes | Callable[..., Any] | Sequence[ToolTypes | Callable[..., Any]] | None = None,
|
|
309
|
+
instructions: str | None = None,
|
|
310
|
+
default_options: OptionsCoT | None = None,
|
|
311
|
+
middleware: Sequence[MiddlewareTypes] | None = None,
|
|
312
|
+
context_providers: Sequence[BaseContextProvider] | None = None,
|
|
313
|
+
) -> Agent[OptionsCoT]:
|
|
314
|
+
"""Retrieve an existing assistant by ID and return a Agent.
|
|
315
|
+
|
|
316
|
+
This method fetches an existing assistant from OpenAI by its ID
|
|
317
|
+
and wraps it in a Agent instance.
|
|
318
|
+
|
|
319
|
+
Args:
|
|
320
|
+
assistant_id: The ID of the assistant to retrieve (e.g., "asst_123").
|
|
321
|
+
|
|
322
|
+
Keyword Args:
|
|
323
|
+
tools: Function tools to make available. IMPORTANT: If the assistant
|
|
324
|
+
was created with function tools, you MUST provide matching
|
|
325
|
+
implementations here. Hosted tools (code_interpreter, file_search)
|
|
326
|
+
are automatically included.
|
|
327
|
+
instructions: Override the assistant's instructions (optional).
|
|
328
|
+
default_options: A TypedDict containing default chat options for the agent.
|
|
329
|
+
These options are applied to every run unless overridden.
|
|
330
|
+
middleware: MiddlewareTypes for the Agent.
|
|
331
|
+
context_providers: Context providers for the Agent.
|
|
332
|
+
|
|
333
|
+
Returns:
|
|
334
|
+
A Agent instance wrapping the retrieved assistant.
|
|
335
|
+
|
|
336
|
+
Raises:
|
|
337
|
+
RuntimeError: If the assistant cannot be retrieved.
|
|
338
|
+
ValueError: If required function tools are missing.
|
|
339
|
+
|
|
340
|
+
Examples:
|
|
341
|
+
.. code-block:: python
|
|
342
|
+
|
|
343
|
+
provider = OpenAIAssistantProvider()
|
|
344
|
+
|
|
345
|
+
# Get assistant without function tools
|
|
346
|
+
agent = await provider.get_agent(assistant_id="asst_123")
|
|
347
|
+
|
|
348
|
+
# Get assistant with function tools
|
|
349
|
+
agent = await provider.get_agent(
|
|
350
|
+
assistant_id="asst_456",
|
|
351
|
+
tools=[get_weather, search_database], # Implementations required!
|
|
352
|
+
)
|
|
353
|
+
"""
|
|
354
|
+
# Fetch the assistant
|
|
355
|
+
if not self._client:
|
|
356
|
+
raise RuntimeError("OpenAI client is not initialized.")
|
|
357
|
+
|
|
358
|
+
assistant = await self._client.beta.assistants.retrieve(assistant_id) # type: ignore[reportDeprecated]
|
|
359
|
+
|
|
360
|
+
# Use as_agent to wrap it
|
|
361
|
+
return self.as_agent(
|
|
362
|
+
assistant=assistant,
|
|
363
|
+
tools=tools,
|
|
364
|
+
instructions=instructions,
|
|
365
|
+
default_options=default_options,
|
|
366
|
+
middleware=middleware,
|
|
367
|
+
context_providers=context_providers,
|
|
368
|
+
)
|
|
369
|
+
|
|
370
|
+
def as_agent(
|
|
371
|
+
self,
|
|
372
|
+
assistant: Assistant,
|
|
373
|
+
*,
|
|
374
|
+
tools: ToolTypes | Callable[..., Any] | Sequence[ToolTypes | Callable[..., Any]] | None = None,
|
|
375
|
+
instructions: str | None = None,
|
|
376
|
+
default_options: OptionsCoT | None = None,
|
|
377
|
+
middleware: Sequence[MiddlewareTypes] | None = None,
|
|
378
|
+
context_providers: Sequence[BaseContextProvider] | None = None,
|
|
379
|
+
) -> Agent[OptionsCoT]:
|
|
380
|
+
"""Wrap an existing SDK Assistant object as a Agent.
|
|
381
|
+
|
|
382
|
+
This method does NOT make any HTTP calls. It simply wraps an already-
|
|
383
|
+
fetched Assistant object in a Agent.
|
|
384
|
+
|
|
385
|
+
Args:
|
|
386
|
+
assistant: The OpenAI Assistant SDK object to wrap.
|
|
387
|
+
|
|
388
|
+
Keyword Args:
|
|
389
|
+
tools: Function tools to make available. If the assistant has
|
|
390
|
+
function tools defined, you MUST provide matching implementations.
|
|
391
|
+
Hosted tools (code_interpreter, file_search) are automatically included.
|
|
392
|
+
instructions: Override the assistant's instructions (optional).
|
|
393
|
+
default_options: A TypedDict containing default chat options for the agent.
|
|
394
|
+
These options are applied to every run unless overridden.
|
|
395
|
+
middleware: MiddlewareTypes for the Agent.
|
|
396
|
+
context_providers: Context providers for the Agent.
|
|
397
|
+
|
|
398
|
+
Returns:
|
|
399
|
+
A Agent instance wrapping the assistant.
|
|
400
|
+
|
|
401
|
+
Raises:
|
|
402
|
+
ValueError: If required function tools are missing.
|
|
403
|
+
|
|
404
|
+
Examples:
|
|
405
|
+
.. code-block:: python
|
|
406
|
+
|
|
407
|
+
client = AsyncOpenAI()
|
|
408
|
+
provider = OpenAIAssistantProvider(client)
|
|
409
|
+
|
|
410
|
+
# Fetch assistant via SDK
|
|
411
|
+
assistant = await client.beta.assistants.retrieve("asst_123")
|
|
412
|
+
|
|
413
|
+
# Wrap without additional HTTP call
|
|
414
|
+
agent = provider.as_agent(
|
|
415
|
+
assistant,
|
|
416
|
+
tools=[my_function],
|
|
417
|
+
instructions="Custom instructions override",
|
|
418
|
+
)
|
|
419
|
+
"""
|
|
420
|
+
# Validate that required function tools are provided
|
|
421
|
+
self._validate_function_tools(assistant.tools or [], tools)
|
|
422
|
+
|
|
423
|
+
# Merge hosted tools with user-provided function tools
|
|
424
|
+
merged_tools = self._merge_tools(assistant.tools or [], tools)
|
|
425
|
+
|
|
426
|
+
# Create Agent
|
|
427
|
+
return self._create_chat_agent_from_assistant(
|
|
428
|
+
assistant=assistant,
|
|
429
|
+
tools=merged_tools,
|
|
430
|
+
instructions=instructions,
|
|
431
|
+
default_options=default_options,
|
|
432
|
+
middleware=middleware,
|
|
433
|
+
context_providers=context_providers,
|
|
434
|
+
)
|
|
435
|
+
|
|
436
|
+
def _validate_function_tools(
|
|
437
|
+
self,
|
|
438
|
+
assistant_tools: list[Any],
|
|
439
|
+
provided_tools: ToolTypes | Callable[..., Any] | Sequence[ToolTypes | Callable[..., Any]] | None,
|
|
440
|
+
) -> None:
|
|
441
|
+
"""Validate that required function tools are provided.
|
|
442
|
+
|
|
443
|
+
Args:
|
|
444
|
+
assistant_tools: Tools defined on the assistant.
|
|
445
|
+
provided_tools: Tools provided by the user.
|
|
446
|
+
|
|
447
|
+
Raises:
|
|
448
|
+
ValueError: If a required function tool is missing.
|
|
449
|
+
"""
|
|
450
|
+
# Get function tool names from assistant
|
|
451
|
+
required_functions: set[str] = set()
|
|
452
|
+
for tool in assistant_tools:
|
|
453
|
+
if (
|
|
454
|
+
hasattr(tool, "type")
|
|
455
|
+
and tool.type == "function"
|
|
456
|
+
and hasattr(tool, "function")
|
|
457
|
+
and hasattr(tool.function, "name")
|
|
458
|
+
):
|
|
459
|
+
required_functions.add(tool.function.name)
|
|
460
|
+
|
|
461
|
+
if not required_functions:
|
|
462
|
+
return # No function tools required
|
|
463
|
+
|
|
464
|
+
# Get provided function names using normalize_tools
|
|
465
|
+
provided_functions: set[str] = set()
|
|
466
|
+
if provided_tools is not None:
|
|
467
|
+
normalized = normalize_tools(provided_tools)
|
|
468
|
+
for tool in normalized:
|
|
469
|
+
if isinstance(tool, FunctionTool):
|
|
470
|
+
provided_functions.add(tool.name)
|
|
471
|
+
elif isinstance(tool, Mapping):
|
|
472
|
+
typed_tool = cast(Mapping[str, Any], tool)
|
|
473
|
+
raw_func_spec = typed_tool.get("function")
|
|
474
|
+
if isinstance(raw_func_spec, Mapping):
|
|
475
|
+
typed_func_spec = cast(Mapping[str, Any], raw_func_spec)
|
|
476
|
+
raw_name = typed_func_spec.get("name")
|
|
477
|
+
if isinstance(raw_name, str) and raw_name:
|
|
478
|
+
provided_functions.add(raw_name)
|
|
479
|
+
|
|
480
|
+
# Check for missing functions
|
|
481
|
+
missing = required_functions - provided_functions
|
|
482
|
+
if missing:
|
|
483
|
+
missing_list = ", ".join(sorted(missing))
|
|
484
|
+
raise ValueError(
|
|
485
|
+
f"Assistant requires function tool(s) '{missing_list}' but no implementation was provided. "
|
|
486
|
+
f"Please pass the function implementation(s) in the 'tools' parameter."
|
|
487
|
+
)
|
|
488
|
+
|
|
489
|
+
def _merge_tools(
|
|
490
|
+
self,
|
|
491
|
+
assistant_tools: list[Any],
|
|
492
|
+
user_tools: ToolTypes | Callable[..., Any] | Sequence[ToolTypes | Callable[..., Any]] | None,
|
|
493
|
+
) -> list[FunctionTool | MutableMapping[str, Any] | Any]:
|
|
494
|
+
"""Merge hosted tools from assistant with user-provided function tools.
|
|
495
|
+
|
|
496
|
+
Args:
|
|
497
|
+
assistant_tools: Tools defined on the assistant.
|
|
498
|
+
user_tools: Tools provided by the user.
|
|
499
|
+
|
|
500
|
+
Returns:
|
|
501
|
+
A list of all tools (hosted tools + user function implementations).
|
|
502
|
+
"""
|
|
503
|
+
merged: list[FunctionTool | MutableMapping[str, Any] | Any] = []
|
|
504
|
+
|
|
505
|
+
# Add hosted tools from assistant using shared conversion
|
|
506
|
+
hosted_tools = from_assistant_tools(assistant_tools)
|
|
507
|
+
merged.extend(hosted_tools)
|
|
508
|
+
|
|
509
|
+
# Add user-provided tools (normalized)
|
|
510
|
+
if user_tools is not None:
|
|
511
|
+
normalized_user_tools = normalize_tools(user_tools)
|
|
512
|
+
merged.extend(normalized_user_tools)
|
|
513
|
+
|
|
514
|
+
return merged
|
|
515
|
+
|
|
516
|
+
def _create_chat_agent_from_assistant(
|
|
517
|
+
self,
|
|
518
|
+
assistant: Assistant,
|
|
519
|
+
tools: list[FunctionTool | MutableMapping[str, Any] | Any] | None,
|
|
520
|
+
instructions: str | None,
|
|
521
|
+
middleware: Sequence[MiddlewareTypes] | None,
|
|
522
|
+
context_providers: Sequence[BaseContextProvider] | None,
|
|
523
|
+
default_options: OptionsCoT | None = None,
|
|
524
|
+
**kwargs: Any,
|
|
525
|
+
) -> Agent[OptionsCoT]:
|
|
526
|
+
"""Create a Agent from an Assistant.
|
|
527
|
+
|
|
528
|
+
Args:
|
|
529
|
+
assistant: The OpenAI Assistant object.
|
|
530
|
+
tools: Tools for the agent.
|
|
531
|
+
instructions: Instructions override.
|
|
532
|
+
middleware: MiddlewareTypes for the agent.
|
|
533
|
+
context_providers: Context providers for the agent.
|
|
534
|
+
default_options: Default chat options for the agent (may include response_format).
|
|
535
|
+
**kwargs: Additional arguments passed to Agent.
|
|
536
|
+
|
|
537
|
+
Returns:
|
|
538
|
+
A configured Agent instance.
|
|
539
|
+
"""
|
|
540
|
+
# Create the chat client with the assistant
|
|
541
|
+
client = OpenAIAssistantsClient( # type: ignore[reportDeprecated]
|
|
542
|
+
model=assistant.model,
|
|
543
|
+
assistant_id=assistant.id,
|
|
544
|
+
assistant_name=assistant.name,
|
|
545
|
+
assistant_description=assistant.description,
|
|
546
|
+
async_client=self._client,
|
|
547
|
+
)
|
|
548
|
+
|
|
549
|
+
# Use instructions from assistant if not overridden
|
|
550
|
+
final_instructions = instructions if instructions is not None else assistant.instructions
|
|
551
|
+
|
|
552
|
+
# Create and return Agent
|
|
553
|
+
return Agent(
|
|
554
|
+
client=client,
|
|
555
|
+
id=assistant.id,
|
|
556
|
+
name=assistant.name,
|
|
557
|
+
description=assistant.description,
|
|
558
|
+
instructions=final_instructions,
|
|
559
|
+
tools=tools if tools else None,
|
|
560
|
+
middleware=middleware,
|
|
561
|
+
context_providers=context_providers,
|
|
562
|
+
default_options=default_options, # type: ignore[arg-type]
|
|
563
|
+
**kwargs,
|
|
564
|
+
)
|