openai-agents 0.2.8__py3-none-any.whl → 0.6.8__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.
- agents/__init__.py +105 -4
- agents/_debug.py +15 -4
- agents/_run_impl.py +1203 -96
- agents/agent.py +164 -19
- agents/apply_diff.py +329 -0
- agents/editor.py +47 -0
- agents/exceptions.py +35 -0
- agents/extensions/experimental/__init__.py +6 -0
- agents/extensions/experimental/codex/__init__.py +92 -0
- agents/extensions/experimental/codex/codex.py +89 -0
- agents/extensions/experimental/codex/codex_options.py +35 -0
- agents/extensions/experimental/codex/codex_tool.py +1142 -0
- agents/extensions/experimental/codex/events.py +162 -0
- agents/extensions/experimental/codex/exec.py +263 -0
- agents/extensions/experimental/codex/items.py +245 -0
- agents/extensions/experimental/codex/output_schema_file.py +50 -0
- agents/extensions/experimental/codex/payloads.py +31 -0
- agents/extensions/experimental/codex/thread.py +214 -0
- agents/extensions/experimental/codex/thread_options.py +54 -0
- agents/extensions/experimental/codex/turn_options.py +36 -0
- agents/extensions/handoff_filters.py +13 -1
- agents/extensions/memory/__init__.py +120 -0
- agents/extensions/memory/advanced_sqlite_session.py +1285 -0
- agents/extensions/memory/async_sqlite_session.py +239 -0
- agents/extensions/memory/dapr_session.py +423 -0
- agents/extensions/memory/encrypt_session.py +185 -0
- agents/extensions/memory/redis_session.py +261 -0
- agents/extensions/memory/sqlalchemy_session.py +334 -0
- agents/extensions/models/litellm_model.py +449 -36
- agents/extensions/models/litellm_provider.py +3 -1
- agents/function_schema.py +47 -5
- agents/guardrail.py +16 -2
- agents/{handoffs.py → handoffs/__init__.py} +89 -47
- agents/handoffs/history.py +268 -0
- agents/items.py +237 -11
- agents/lifecycle.py +75 -14
- agents/mcp/server.py +280 -37
- agents/mcp/util.py +24 -3
- agents/memory/__init__.py +22 -2
- agents/memory/openai_conversations_session.py +91 -0
- agents/memory/openai_responses_compaction_session.py +249 -0
- agents/memory/session.py +19 -261
- agents/memory/sqlite_session.py +275 -0
- agents/memory/util.py +20 -0
- agents/model_settings.py +14 -3
- agents/models/__init__.py +13 -0
- agents/models/chatcmpl_converter.py +303 -50
- agents/models/chatcmpl_helpers.py +63 -0
- agents/models/chatcmpl_stream_handler.py +290 -68
- agents/models/default_models.py +58 -0
- agents/models/interface.py +4 -0
- agents/models/openai_chatcompletions.py +103 -49
- agents/models/openai_provider.py +10 -4
- agents/models/openai_responses.py +162 -46
- agents/realtime/__init__.py +4 -0
- agents/realtime/_util.py +14 -3
- agents/realtime/agent.py +7 -0
- agents/realtime/audio_formats.py +53 -0
- agents/realtime/config.py +78 -10
- agents/realtime/events.py +18 -0
- agents/realtime/handoffs.py +2 -2
- agents/realtime/items.py +17 -1
- agents/realtime/model.py +13 -0
- agents/realtime/model_events.py +12 -0
- agents/realtime/model_inputs.py +18 -1
- agents/realtime/openai_realtime.py +696 -150
- agents/realtime/session.py +243 -23
- agents/repl.py +7 -3
- agents/result.py +197 -38
- agents/run.py +949 -168
- agents/run_context.py +13 -2
- agents/stream_events.py +1 -0
- agents/strict_schema.py +14 -0
- agents/tool.py +413 -15
- agents/tool_context.py +22 -1
- agents/tool_guardrails.py +279 -0
- agents/tracing/__init__.py +2 -0
- agents/tracing/config.py +9 -0
- agents/tracing/create.py +4 -0
- agents/tracing/processor_interface.py +84 -11
- agents/tracing/processors.py +65 -54
- agents/tracing/provider.py +64 -7
- agents/tracing/spans.py +105 -0
- agents/tracing/traces.py +116 -16
- agents/usage.py +134 -12
- agents/util/_json.py +19 -1
- agents/util/_transforms.py +12 -2
- agents/voice/input.py +5 -4
- agents/voice/models/openai_stt.py +17 -9
- agents/voice/pipeline.py +2 -0
- agents/voice/pipeline_config.py +4 -0
- {openai_agents-0.2.8.dist-info → openai_agents-0.6.8.dist-info}/METADATA +44 -19
- openai_agents-0.6.8.dist-info/RECORD +134 -0
- {openai_agents-0.2.8.dist-info → openai_agents-0.6.8.dist-info}/WHEEL +1 -1
- openai_agents-0.2.8.dist-info/RECORD +0 -103
- {openai_agents-0.2.8.dist-info → openai_agents-0.6.8.dist-info}/licenses/LICENSE +0 -0
agents/result.py
CHANGED
|
@@ -2,9 +2,10 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import abc
|
|
4
4
|
import asyncio
|
|
5
|
+
import weakref
|
|
5
6
|
from collections.abc import AsyncIterator
|
|
6
7
|
from dataclasses import dataclass, field
|
|
7
|
-
from typing import TYPE_CHECKING, Any, cast
|
|
8
|
+
from typing import TYPE_CHECKING, Any, Literal, cast
|
|
8
9
|
|
|
9
10
|
from typing_extensions import TypeVar
|
|
10
11
|
|
|
@@ -31,6 +32,7 @@ from .util._pretty_print import (
|
|
|
31
32
|
if TYPE_CHECKING:
|
|
32
33
|
from ._run_impl import QueueCompleteSentinel
|
|
33
34
|
from .agent import Agent
|
|
35
|
+
from .tool_guardrails import ToolInputGuardrailResult, ToolOutputGuardrailResult
|
|
34
36
|
|
|
35
37
|
T = TypeVar("T")
|
|
36
38
|
|
|
@@ -59,6 +61,12 @@ class RunResultBase(abc.ABC):
|
|
|
59
61
|
output_guardrail_results: list[OutputGuardrailResult]
|
|
60
62
|
"""Guardrail results for the final output of the agent."""
|
|
61
63
|
|
|
64
|
+
tool_input_guardrail_results: list[ToolInputGuardrailResult]
|
|
65
|
+
"""Tool input guardrail results from all tools executed during the run."""
|
|
66
|
+
|
|
67
|
+
tool_output_guardrail_results: list[ToolOutputGuardrailResult]
|
|
68
|
+
"""Tool output guardrail results from all tools executed during the run."""
|
|
69
|
+
|
|
62
70
|
context_wrapper: RunContextWrapper[Any]
|
|
63
71
|
"""The context wrapper for the agent run."""
|
|
64
72
|
|
|
@@ -67,6 +75,35 @@ class RunResultBase(abc.ABC):
|
|
|
67
75
|
def last_agent(self) -> Agent[Any]:
|
|
68
76
|
"""The last agent that was run."""
|
|
69
77
|
|
|
78
|
+
def release_agents(self, *, release_new_items: bool = True) -> None:
|
|
79
|
+
"""
|
|
80
|
+
Release strong references to agents held by this result. After calling this method,
|
|
81
|
+
accessing `item.agent` or `last_agent` may return `None` if the agent has been garbage
|
|
82
|
+
collected. Callers can use this when they are done inspecting the result and want to
|
|
83
|
+
eagerly drop any associated agent graph.
|
|
84
|
+
"""
|
|
85
|
+
if release_new_items:
|
|
86
|
+
for item in self.new_items:
|
|
87
|
+
release = getattr(item, "release_agent", None)
|
|
88
|
+
if callable(release):
|
|
89
|
+
release()
|
|
90
|
+
self._release_last_agent_reference()
|
|
91
|
+
|
|
92
|
+
def __del__(self) -> None:
|
|
93
|
+
try:
|
|
94
|
+
# Fall back to releasing agents automatically in case the caller never invoked
|
|
95
|
+
# `release_agents()` explicitly so GC of the RunResult drops the last strong reference.
|
|
96
|
+
# We pass `release_new_items=False` so RunItems that the user intentionally keeps
|
|
97
|
+
# continue exposing their originating agent until that agent itself is collected.
|
|
98
|
+
self.release_agents(release_new_items=False)
|
|
99
|
+
except Exception:
|
|
100
|
+
# Avoid raising from __del__.
|
|
101
|
+
pass
|
|
102
|
+
|
|
103
|
+
@abc.abstractmethod
|
|
104
|
+
def _release_last_agent_reference(self) -> None:
|
|
105
|
+
"""Release stored agent reference specific to the concrete result type."""
|
|
106
|
+
|
|
70
107
|
def final_output_as(self, cls: type[T], raise_if_incorrect_type: bool = False) -> T:
|
|
71
108
|
"""A convenience method to cast the final output to a specific type. By default, the cast
|
|
72
109
|
is only for the typechecker. If you set `raise_if_incorrect_type` to True, we'll raise a
|
|
@@ -104,11 +141,34 @@ class RunResultBase(abc.ABC):
|
|
|
104
141
|
@dataclass
|
|
105
142
|
class RunResult(RunResultBase):
|
|
106
143
|
_last_agent: Agent[Any]
|
|
144
|
+
_last_agent_ref: weakref.ReferenceType[Agent[Any]] | None = field(
|
|
145
|
+
init=False,
|
|
146
|
+
repr=False,
|
|
147
|
+
default=None,
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
def __post_init__(self) -> None:
|
|
151
|
+
self._last_agent_ref = weakref.ref(self._last_agent)
|
|
107
152
|
|
|
108
153
|
@property
|
|
109
154
|
def last_agent(self) -> Agent[Any]:
|
|
110
155
|
"""The last agent that was run."""
|
|
111
|
-
|
|
156
|
+
agent = cast("Agent[Any] | None", self.__dict__.get("_last_agent"))
|
|
157
|
+
if agent is not None:
|
|
158
|
+
return agent
|
|
159
|
+
if self._last_agent_ref:
|
|
160
|
+
agent = self._last_agent_ref()
|
|
161
|
+
if agent is not None:
|
|
162
|
+
return agent
|
|
163
|
+
raise AgentsException("Last agent reference is no longer available.")
|
|
164
|
+
|
|
165
|
+
def _release_last_agent_reference(self) -> None:
|
|
166
|
+
agent = cast("Agent[Any] | None", self.__dict__.get("_last_agent"))
|
|
167
|
+
if agent is None:
|
|
168
|
+
return
|
|
169
|
+
self._last_agent_ref = weakref.ref(agent)
|
|
170
|
+
# Preserve dataclass field so repr/asdict continue to succeed.
|
|
171
|
+
self.__dict__["_last_agent"] = None
|
|
112
172
|
|
|
113
173
|
def __str__(self) -> str:
|
|
114
174
|
return pretty_print_result(self)
|
|
@@ -143,6 +203,15 @@ class RunResultStreaming(RunResultBase):
|
|
|
143
203
|
is_complete: bool = False
|
|
144
204
|
"""Whether the agent has finished running."""
|
|
145
205
|
|
|
206
|
+
_current_agent_ref: weakref.ReferenceType[Agent[Any]] | None = field(
|
|
207
|
+
init=False,
|
|
208
|
+
repr=False,
|
|
209
|
+
default=None,
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
_model_input_items: list[RunItem] = field(default_factory=list, repr=False)
|
|
213
|
+
"""Filtered items used to build model input between streaming turns."""
|
|
214
|
+
|
|
146
215
|
# Queues that the background run_loop writes to
|
|
147
216
|
_event_queue: asyncio.Queue[StreamEvent | QueueCompleteSentinel] = field(
|
|
148
217
|
default_factory=asyncio.Queue, repr=False
|
|
@@ -157,24 +226,79 @@ class RunResultStreaming(RunResultBase):
|
|
|
157
226
|
_output_guardrails_task: asyncio.Task[Any] | None = field(default=None, repr=False)
|
|
158
227
|
_stored_exception: Exception | None = field(default=None, repr=False)
|
|
159
228
|
|
|
229
|
+
# Soft cancel state
|
|
230
|
+
_cancel_mode: Literal["none", "immediate", "after_turn"] = field(default="none", repr=False)
|
|
231
|
+
|
|
232
|
+
def __post_init__(self) -> None:
|
|
233
|
+
self._current_agent_ref = weakref.ref(self.current_agent)
|
|
234
|
+
|
|
160
235
|
@property
|
|
161
236
|
def last_agent(self) -> Agent[Any]:
|
|
162
237
|
"""The last agent that was run. Updates as the agent run progresses, so the true last agent
|
|
163
238
|
is only available after the agent run is complete.
|
|
164
239
|
"""
|
|
165
|
-
|
|
240
|
+
agent = cast("Agent[Any] | None", self.__dict__.get("current_agent"))
|
|
241
|
+
if agent is not None:
|
|
242
|
+
return agent
|
|
243
|
+
if self._current_agent_ref:
|
|
244
|
+
agent = self._current_agent_ref()
|
|
245
|
+
if agent is not None:
|
|
246
|
+
return agent
|
|
247
|
+
raise AgentsException("Last agent reference is no longer available.")
|
|
248
|
+
|
|
249
|
+
def _release_last_agent_reference(self) -> None:
|
|
250
|
+
agent = cast("Agent[Any] | None", self.__dict__.get("current_agent"))
|
|
251
|
+
if agent is None:
|
|
252
|
+
return
|
|
253
|
+
self._current_agent_ref = weakref.ref(agent)
|
|
254
|
+
# Preserve dataclass field so repr/asdict continue to succeed.
|
|
255
|
+
self.__dict__["current_agent"] = None
|
|
256
|
+
|
|
257
|
+
def cancel(self, mode: Literal["immediate", "after_turn"] = "immediate") -> None:
|
|
258
|
+
"""Cancel the streaming run.
|
|
166
259
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
260
|
+
Args:
|
|
261
|
+
mode: Cancellation strategy:
|
|
262
|
+
- "immediate": Stop immediately, cancel all tasks, clear queues (default)
|
|
263
|
+
- "after_turn": Complete current turn gracefully before stopping
|
|
264
|
+
* Allows LLM response to finish
|
|
265
|
+
* Executes pending tool calls
|
|
266
|
+
* Saves session state properly
|
|
267
|
+
* Tracks usage accurately
|
|
268
|
+
* Stops before next turn begins
|
|
269
|
+
|
|
270
|
+
Example:
|
|
271
|
+
```python
|
|
272
|
+
result = Runner.run_streamed(agent, "Task", session=session)
|
|
273
|
+
|
|
274
|
+
async for event in result.stream_events():
|
|
275
|
+
if user_interrupted():
|
|
276
|
+
result.cancel(mode="after_turn") # Graceful
|
|
277
|
+
# result.cancel() # Immediate (default)
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
Note: After calling cancel(), you should continue consuming stream_events()
|
|
281
|
+
to allow the cancellation to complete properly.
|
|
282
|
+
"""
|
|
283
|
+
# Store the cancel mode for the background task to check
|
|
284
|
+
self._cancel_mode = mode
|
|
285
|
+
|
|
286
|
+
if mode == "immediate":
|
|
287
|
+
# Existing behavior - immediate shutdown
|
|
288
|
+
self._cleanup_tasks() # Cancel all running tasks
|
|
289
|
+
self.is_complete = True # Mark the run as complete to stop event streaming
|
|
290
|
+
|
|
291
|
+
# Optionally, clear the event queue to prevent processing stale events
|
|
292
|
+
while not self._event_queue.empty():
|
|
293
|
+
self._event_queue.get_nowait()
|
|
294
|
+
while not self._input_guardrail_queue.empty():
|
|
295
|
+
self._input_guardrail_queue.get_nowait()
|
|
296
|
+
|
|
297
|
+
elif mode == "after_turn":
|
|
298
|
+
# Soft cancel - just set the flag
|
|
299
|
+
# The streaming loop will check this and stop gracefully
|
|
300
|
+
# Don't call _cleanup_tasks() or clear queues yet
|
|
301
|
+
pass
|
|
178
302
|
|
|
179
303
|
async def stream_events(self) -> AsyncIterator[StreamEvent]:
|
|
180
304
|
"""Stream deltas for new items as they are generated. We're using the types from the
|
|
@@ -185,31 +309,50 @@ class RunResultStreaming(RunResultBase):
|
|
|
185
309
|
- A MaxTurnsExceeded exception if the agent exceeds the max_turns limit.
|
|
186
310
|
- A GuardrailTripwireTriggered exception if a guardrail is tripped.
|
|
187
311
|
"""
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
logger.debug("Breaking due to stored exception")
|
|
192
|
-
self.is_complete = True
|
|
193
|
-
break
|
|
194
|
-
|
|
195
|
-
if self.is_complete and self._event_queue.empty():
|
|
196
|
-
break
|
|
197
|
-
|
|
198
|
-
try:
|
|
199
|
-
item = await self._event_queue.get()
|
|
200
|
-
except asyncio.CancelledError:
|
|
201
|
-
break
|
|
202
|
-
|
|
203
|
-
if isinstance(item, QueueCompleteSentinel):
|
|
204
|
-
self._event_queue.task_done()
|
|
205
|
-
# Check for errors, in case the queue was completed due to an exception
|
|
312
|
+
cancelled = False
|
|
313
|
+
try:
|
|
314
|
+
while True:
|
|
206
315
|
self._check_errors()
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
316
|
+
if self._stored_exception:
|
|
317
|
+
logger.debug("Breaking due to stored exception")
|
|
318
|
+
self.is_complete = True
|
|
319
|
+
break
|
|
320
|
+
|
|
321
|
+
if self.is_complete and self._event_queue.empty():
|
|
322
|
+
break
|
|
323
|
+
|
|
324
|
+
try:
|
|
325
|
+
item = await self._event_queue.get()
|
|
326
|
+
except asyncio.CancelledError:
|
|
327
|
+
cancelled = True
|
|
328
|
+
self.cancel()
|
|
329
|
+
raise
|
|
330
|
+
|
|
331
|
+
if isinstance(item, QueueCompleteSentinel):
|
|
332
|
+
# Await input guardrails if they are still running, so late
|
|
333
|
+
# exceptions are captured.
|
|
334
|
+
await self._await_task_safely(self._input_guardrails_task)
|
|
335
|
+
|
|
336
|
+
self._event_queue.task_done()
|
|
337
|
+
|
|
338
|
+
# Check for errors, in case the queue was completed
|
|
339
|
+
# due to an exception
|
|
340
|
+
self._check_errors()
|
|
341
|
+
break
|
|
342
|
+
|
|
343
|
+
yield item
|
|
344
|
+
self._event_queue.task_done()
|
|
345
|
+
finally:
|
|
346
|
+
if cancelled:
|
|
347
|
+
# Cancellation should return promptly, so avoid waiting on long-running tasks.
|
|
348
|
+
# Tasks have already been cancelled above.
|
|
349
|
+
self._cleanup_tasks()
|
|
350
|
+
else:
|
|
351
|
+
# Ensure main execution completes before cleanup to avoid race conditions
|
|
352
|
+
# with session operations
|
|
353
|
+
await self._await_task_safely(self._run_impl_task)
|
|
354
|
+
# Safely terminate all background tasks after main execution has finished
|
|
355
|
+
self._cleanup_tasks()
|
|
213
356
|
|
|
214
357
|
if self._stored_exception:
|
|
215
358
|
raise self._stored_exception
|
|
@@ -274,3 +417,19 @@ class RunResultStreaming(RunResultBase):
|
|
|
274
417
|
|
|
275
418
|
def __str__(self) -> str:
|
|
276
419
|
return pretty_print_run_result_streaming(self)
|
|
420
|
+
|
|
421
|
+
async def _await_task_safely(self, task: asyncio.Task[Any] | None) -> None:
|
|
422
|
+
"""Await a task if present, ignoring cancellation and storing exceptions elsewhere.
|
|
423
|
+
|
|
424
|
+
This ensures we do not lose late guardrail exceptions while not surfacing
|
|
425
|
+
CancelledError to callers of stream_events.
|
|
426
|
+
"""
|
|
427
|
+
if task and not task.done():
|
|
428
|
+
try:
|
|
429
|
+
await task
|
|
430
|
+
except asyncio.CancelledError:
|
|
431
|
+
# Task was cancelled (e.g., due to result.cancel()). Nothing to do here.
|
|
432
|
+
pass
|
|
433
|
+
except Exception:
|
|
434
|
+
# The exception will be surfaced via _check_errors() if needed.
|
|
435
|
+
pass
|