openai-agents 0.2.6__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.
Files changed (96) hide show
  1. agents/__init__.py +105 -4
  2. agents/_debug.py +15 -4
  3. agents/_run_impl.py +1203 -96
  4. agents/agent.py +294 -21
  5. agents/apply_diff.py +329 -0
  6. agents/editor.py +47 -0
  7. agents/exceptions.py +35 -0
  8. agents/extensions/experimental/__init__.py +6 -0
  9. agents/extensions/experimental/codex/__init__.py +92 -0
  10. agents/extensions/experimental/codex/codex.py +89 -0
  11. agents/extensions/experimental/codex/codex_options.py +35 -0
  12. agents/extensions/experimental/codex/codex_tool.py +1142 -0
  13. agents/extensions/experimental/codex/events.py +162 -0
  14. agents/extensions/experimental/codex/exec.py +263 -0
  15. agents/extensions/experimental/codex/items.py +245 -0
  16. agents/extensions/experimental/codex/output_schema_file.py +50 -0
  17. agents/extensions/experimental/codex/payloads.py +31 -0
  18. agents/extensions/experimental/codex/thread.py +214 -0
  19. agents/extensions/experimental/codex/thread_options.py +54 -0
  20. agents/extensions/experimental/codex/turn_options.py +36 -0
  21. agents/extensions/handoff_filters.py +13 -1
  22. agents/extensions/memory/__init__.py +120 -0
  23. agents/extensions/memory/advanced_sqlite_session.py +1285 -0
  24. agents/extensions/memory/async_sqlite_session.py +239 -0
  25. agents/extensions/memory/dapr_session.py +423 -0
  26. agents/extensions/memory/encrypt_session.py +185 -0
  27. agents/extensions/memory/redis_session.py +261 -0
  28. agents/extensions/memory/sqlalchemy_session.py +334 -0
  29. agents/extensions/models/litellm_model.py +449 -36
  30. agents/extensions/models/litellm_provider.py +3 -1
  31. agents/function_schema.py +47 -5
  32. agents/guardrail.py +16 -2
  33. agents/{handoffs.py → handoffs/__init__.py} +89 -47
  34. agents/handoffs/history.py +268 -0
  35. agents/items.py +238 -13
  36. agents/lifecycle.py +75 -14
  37. agents/mcp/server.py +280 -37
  38. agents/mcp/util.py +24 -3
  39. agents/memory/__init__.py +22 -2
  40. agents/memory/openai_conversations_session.py +91 -0
  41. agents/memory/openai_responses_compaction_session.py +249 -0
  42. agents/memory/session.py +19 -261
  43. agents/memory/sqlite_session.py +275 -0
  44. agents/memory/util.py +20 -0
  45. agents/model_settings.py +18 -3
  46. agents/models/__init__.py +13 -0
  47. agents/models/chatcmpl_converter.py +303 -50
  48. agents/models/chatcmpl_helpers.py +63 -0
  49. agents/models/chatcmpl_stream_handler.py +290 -68
  50. agents/models/default_models.py +58 -0
  51. agents/models/interface.py +4 -0
  52. agents/models/openai_chatcompletions.py +103 -48
  53. agents/models/openai_provider.py +10 -4
  54. agents/models/openai_responses.py +167 -46
  55. agents/realtime/__init__.py +4 -0
  56. agents/realtime/_util.py +14 -3
  57. agents/realtime/agent.py +7 -0
  58. agents/realtime/audio_formats.py +53 -0
  59. agents/realtime/config.py +78 -10
  60. agents/realtime/events.py +18 -0
  61. agents/realtime/handoffs.py +2 -2
  62. agents/realtime/items.py +17 -1
  63. agents/realtime/model.py +13 -0
  64. agents/realtime/model_events.py +12 -0
  65. agents/realtime/model_inputs.py +18 -1
  66. agents/realtime/openai_realtime.py +700 -151
  67. agents/realtime/session.py +309 -32
  68. agents/repl.py +7 -3
  69. agents/result.py +197 -38
  70. agents/run.py +1053 -178
  71. agents/run_context.py +13 -2
  72. agents/stream_events.py +1 -0
  73. agents/strict_schema.py +14 -0
  74. agents/tool.py +413 -15
  75. agents/tool_context.py +22 -1
  76. agents/tool_guardrails.py +279 -0
  77. agents/tracing/__init__.py +2 -0
  78. agents/tracing/config.py +9 -0
  79. agents/tracing/create.py +4 -0
  80. agents/tracing/processor_interface.py +84 -11
  81. agents/tracing/processors.py +65 -54
  82. agents/tracing/provider.py +64 -7
  83. agents/tracing/spans.py +105 -0
  84. agents/tracing/traces.py +116 -16
  85. agents/usage.py +134 -12
  86. agents/util/_json.py +19 -1
  87. agents/util/_transforms.py +12 -2
  88. agents/voice/input.py +5 -4
  89. agents/voice/models/openai_stt.py +17 -9
  90. agents/voice/pipeline.py +2 -0
  91. agents/voice/pipeline_config.py +4 -0
  92. {openai_agents-0.2.6.dist-info → openai_agents-0.6.8.dist-info}/METADATA +44 -19
  93. openai_agents-0.6.8.dist-info/RECORD +134 -0
  94. {openai_agents-0.2.6.dist-info → openai_agents-0.6.8.dist-info}/WHEEL +1 -1
  95. openai_agents-0.2.6.dist-info/RECORD +0 -103
  96. {openai_agents-0.2.6.dist-info → openai_agents-0.6.8.dist-info}/licenses/LICENSE +0 -0
agents/agent.py CHANGED
@@ -13,21 +13,38 @@ from typing_extensions import NotRequired, TypeAlias, TypedDict
13
13
  from .agent_output import AgentOutputSchemaBase
14
14
  from .guardrail import InputGuardrail, OutputGuardrail
15
15
  from .handoffs import Handoff
16
- from .items import ItemHelpers
17
16
  from .logger import logger
18
17
  from .mcp import MCPUtil
19
18
  from .model_settings import ModelSettings
19
+ from .models.default_models import (
20
+ get_default_model_settings,
21
+ gpt_5_reasoning_settings_required,
22
+ is_gpt_5_default,
23
+ )
20
24
  from .models.interface import Model
21
25
  from .prompts import DynamicPromptFunction, Prompt, PromptUtil
22
26
  from .run_context import RunContextWrapper, TContext
23
- from .tool import FunctionTool, FunctionToolResult, Tool, function_tool
27
+ from .tool import (
28
+ FunctionTool,
29
+ FunctionToolResult,
30
+ Tool,
31
+ ToolErrorFunction,
32
+ default_tool_error_function,
33
+ function_tool,
34
+ )
35
+ from .tool_context import ToolContext
24
36
  from .util import _transforms
25
37
  from .util._types import MaybeAwaitable
26
38
 
27
39
  if TYPE_CHECKING:
28
- from .lifecycle import AgentHooks
40
+ from openai.types.responses.response_function_tool_call import ResponseFunctionToolCall
41
+
42
+ from .lifecycle import AgentHooks, RunHooks
29
43
  from .mcp import MCPServer
30
- from .result import RunResult
44
+ from .memory.session import Session
45
+ from .result import RunResult, RunResultStreaming
46
+ from .run import RunConfig
47
+ from .stream_events import StreamEvent
31
48
 
32
49
 
33
50
  @dataclass
@@ -52,6 +69,19 @@ ToolsToFinalOutputFunction: TypeAlias = Callable[
52
69
  """
53
70
 
54
71
 
72
+ class AgentToolStreamEvent(TypedDict):
73
+ """Streaming event emitted when an agent is invoked as a tool."""
74
+
75
+ event: StreamEvent
76
+ """The streaming event from the nested agent run."""
77
+
78
+ agent: Agent[Any]
79
+ """The nested agent emitting the event."""
80
+
81
+ tool_call: ResponseFunctionToolCall | None
82
+ """The originating tool call, if available."""
83
+
84
+
55
85
  class StopAtTools(TypedDict):
56
86
  stop_at_tool_names: list[str]
57
87
  """A list of tool names, any of which will stop the agent from running further."""
@@ -168,10 +198,10 @@ class Agent(AgentBase, Generic[TContext]):
168
198
  """The model implementation to use when invoking the LLM.
169
199
 
170
200
  By default, if not set, the agent will use the default model configured in
171
- `openai_provider.DEFAULT_MODEL` (currently "gpt-4o").
201
+ `agents.models.get_default_model()` (currently "gpt-4.1").
172
202
  """
173
203
 
174
- model_settings: ModelSettings = field(default_factory=ModelSettings)
204
+ model_settings: ModelSettings = field(default_factory=get_default_model_settings)
175
205
  """Configures model-specific tuning parameters (e.g. temperature, top_p).
176
206
  """
177
207
 
@@ -205,8 +235,9 @@ class Agent(AgentBase, Generic[TContext]):
205
235
  This lets you configure how tool use is handled.
206
236
  - "run_llm_again": The default behavior. Tools are run, and then the LLM receives the results
207
237
  and gets to respond.
208
- - "stop_on_first_tool": The output of the first tool call is used as the final output. This
209
- means that the LLM does not process the result of the tool call.
238
+ - "stop_on_first_tool": The output from the first tool call is treated as the final result.
239
+ In other words, it isn’t sent back to the LLM for further processing but is used directly
240
+ as the final output.
210
241
  - A StopAtTools object: The agent will stop running if any of the tools listed in
211
242
  `stop_at_tool_names` is called.
212
243
  The final output will be the output of the first matching tool call.
@@ -223,6 +254,139 @@ class Agent(AgentBase, Generic[TContext]):
223
254
  """Whether to reset the tool choice to the default value after a tool has been called. Defaults
224
255
  to True. This ensures that the agent doesn't enter an infinite loop of tool usage."""
225
256
 
257
+ def __post_init__(self):
258
+ from typing import get_origin
259
+
260
+ if not isinstance(self.name, str):
261
+ raise TypeError(f"Agent name must be a string, got {type(self.name).__name__}")
262
+
263
+ if self.handoff_description is not None and not isinstance(self.handoff_description, str):
264
+ raise TypeError(
265
+ f"Agent handoff_description must be a string or None, "
266
+ f"got {type(self.handoff_description).__name__}"
267
+ )
268
+
269
+ if not isinstance(self.tools, list):
270
+ raise TypeError(f"Agent tools must be a list, got {type(self.tools).__name__}")
271
+
272
+ if not isinstance(self.mcp_servers, list):
273
+ raise TypeError(
274
+ f"Agent mcp_servers must be a list, got {type(self.mcp_servers).__name__}"
275
+ )
276
+
277
+ if not isinstance(self.mcp_config, dict):
278
+ raise TypeError(
279
+ f"Agent mcp_config must be a dict, got {type(self.mcp_config).__name__}"
280
+ )
281
+
282
+ if (
283
+ self.instructions is not None
284
+ and not isinstance(self.instructions, str)
285
+ and not callable(self.instructions)
286
+ ):
287
+ raise TypeError(
288
+ f"Agent instructions must be a string, callable, or None, "
289
+ f"got {type(self.instructions).__name__}"
290
+ )
291
+
292
+ if (
293
+ self.prompt is not None
294
+ and not callable(self.prompt)
295
+ and not hasattr(self.prompt, "get")
296
+ ):
297
+ raise TypeError(
298
+ f"Agent prompt must be a Prompt, DynamicPromptFunction, or None, "
299
+ f"got {type(self.prompt).__name__}"
300
+ )
301
+
302
+ if not isinstance(self.handoffs, list):
303
+ raise TypeError(f"Agent handoffs must be a list, got {type(self.handoffs).__name__}")
304
+
305
+ if self.model is not None and not isinstance(self.model, str):
306
+ from .models.interface import Model
307
+
308
+ if not isinstance(self.model, Model):
309
+ raise TypeError(
310
+ f"Agent model must be a string, Model, or None, got {type(self.model).__name__}"
311
+ )
312
+
313
+ if not isinstance(self.model_settings, ModelSettings):
314
+ raise TypeError(
315
+ f"Agent model_settings must be a ModelSettings instance, "
316
+ f"got {type(self.model_settings).__name__}"
317
+ )
318
+
319
+ if (
320
+ # The user sets a non-default model
321
+ self.model is not None
322
+ and (
323
+ # The default model is gpt-5
324
+ is_gpt_5_default() is True
325
+ # However, the specified model is not a gpt-5 model
326
+ and (
327
+ isinstance(self.model, str) is False
328
+ or gpt_5_reasoning_settings_required(self.model) is False # type: ignore
329
+ )
330
+ # The model settings are not customized for the specified model
331
+ and self.model_settings == get_default_model_settings()
332
+ )
333
+ ):
334
+ # In this scenario, we should use a generic model settings
335
+ # because non-gpt-5 models are not compatible with the default gpt-5 model settings.
336
+ # This is a best-effort attempt to make the agent work with non-gpt-5 models.
337
+ self.model_settings = ModelSettings()
338
+
339
+ if not isinstance(self.input_guardrails, list):
340
+ raise TypeError(
341
+ f"Agent input_guardrails must be a list, got {type(self.input_guardrails).__name__}"
342
+ )
343
+
344
+ if not isinstance(self.output_guardrails, list):
345
+ raise TypeError(
346
+ f"Agent output_guardrails must be a list, "
347
+ f"got {type(self.output_guardrails).__name__}"
348
+ )
349
+
350
+ if self.output_type is not None:
351
+ from .agent_output import AgentOutputSchemaBase
352
+
353
+ if not (
354
+ isinstance(self.output_type, (type, AgentOutputSchemaBase))
355
+ or get_origin(self.output_type) is not None
356
+ ):
357
+ raise TypeError(
358
+ f"Agent output_type must be a type, AgentOutputSchemaBase, or None, "
359
+ f"got {type(self.output_type).__name__}"
360
+ )
361
+
362
+ if self.hooks is not None:
363
+ from .lifecycle import AgentHooksBase
364
+
365
+ if not isinstance(self.hooks, AgentHooksBase):
366
+ raise TypeError(
367
+ f"Agent hooks must be an AgentHooks instance or None, "
368
+ f"got {type(self.hooks).__name__}"
369
+ )
370
+
371
+ if (
372
+ not (
373
+ isinstance(self.tool_use_behavior, str)
374
+ and self.tool_use_behavior in ["run_llm_again", "stop_on_first_tool"]
375
+ )
376
+ and not isinstance(self.tool_use_behavior, dict)
377
+ and not callable(self.tool_use_behavior)
378
+ ):
379
+ raise TypeError(
380
+ f"Agent tool_use_behavior must be 'run_llm_again', 'stop_on_first_tool', "
381
+ f"StopAtTools dict, or callable, got {type(self.tool_use_behavior).__name__}"
382
+ )
383
+
384
+ if not isinstance(self.reset_tool_choice, bool):
385
+ raise TypeError(
386
+ f"Agent reset_tool_choice must be a boolean, "
387
+ f"got {type(self.reset_tool_choice).__name__}"
388
+ )
389
+
226
390
  def clone(self, **kwargs: Any) -> Agent[TContext]:
227
391
  """Make a copy of the agent, with the given arguments changed.
228
392
  Notes:
@@ -242,7 +406,19 @@ class Agent(AgentBase, Generic[TContext]):
242
406
  self,
243
407
  tool_name: str | None,
244
408
  tool_description: str | None,
245
- custom_output_extractor: Callable[[RunResult], Awaitable[str]] | None = None,
409
+ custom_output_extractor: (
410
+ Callable[[RunResult | RunResultStreaming], Awaitable[str]] | None
411
+ ) = None,
412
+ is_enabled: bool
413
+ | Callable[[RunContextWrapper[Any], AgentBase[Any]], MaybeAwaitable[bool]] = True,
414
+ on_stream: Callable[[AgentToolStreamEvent], MaybeAwaitable[None]] | None = None,
415
+ run_config: RunConfig | None = None,
416
+ max_turns: int | None = None,
417
+ hooks: RunHooks[TContext] | None = None,
418
+ previous_response_id: str | None = None,
419
+ conversation_id: str | None = None,
420
+ session: Session | None = None,
421
+ failure_error_function: ToolErrorFunction | None = default_tool_error_function,
246
422
  ) -> Tool:
247
423
  """Transform this agent into a tool, callable by other agents.
248
424
 
@@ -258,38 +434,135 @@ class Agent(AgentBase, Generic[TContext]):
258
434
  when to use it.
259
435
  custom_output_extractor: A function that extracts the output from the agent. If not
260
436
  provided, the last message from the agent will be used.
437
+ is_enabled: Whether the tool is enabled. Can be a bool or a callable that takes the run
438
+ context and agent and returns whether the tool is enabled. Disabled tools are hidden
439
+ from the LLM at runtime.
440
+ on_stream: Optional callback (sync or async) to receive streaming events from the nested
441
+ agent run. The callback receives an `AgentToolStreamEvent` containing the nested
442
+ agent, the originating tool call (when available), and each stream event. When
443
+ provided, the nested agent is executed in streaming mode.
444
+ failure_error_function: If provided, generate an error message when the tool (agent) run
445
+ fails. The message is sent to the LLM. If None, the exception is raised instead.
261
446
  """
262
447
 
263
448
  @function_tool(
264
449
  name_override=tool_name or _transforms.transform_string_function_style(self.name),
265
450
  description_override=tool_description or "",
451
+ is_enabled=is_enabled,
452
+ failure_error_function=failure_error_function,
266
453
  )
267
- async def run_agent(context: RunContextWrapper, input: str) -> str:
268
- from .run import Runner
269
-
270
- output = await Runner.run(
271
- starting_agent=self,
272
- input=input,
273
- context=context.context,
274
- )
454
+ async def run_agent(context: ToolContext, input: str) -> Any:
455
+ from .run import DEFAULT_MAX_TURNS, Runner
456
+
457
+ resolved_max_turns = max_turns if max_turns is not None else DEFAULT_MAX_TURNS
458
+ run_result: RunResult | RunResultStreaming
459
+
460
+ if on_stream is not None:
461
+ run_result = Runner.run_streamed(
462
+ starting_agent=self,
463
+ input=input,
464
+ context=context.context,
465
+ run_config=run_config,
466
+ max_turns=resolved_max_turns,
467
+ hooks=hooks,
468
+ previous_response_id=previous_response_id,
469
+ conversation_id=conversation_id,
470
+ session=session,
471
+ )
472
+ # Dispatch callbacks in the background so slow handlers do not block
473
+ # event consumption.
474
+ event_queue: asyncio.Queue[AgentToolStreamEvent | None] = asyncio.Queue()
475
+
476
+ async def _run_handler(payload: AgentToolStreamEvent) -> None:
477
+ """Execute the user callback while capturing exceptions."""
478
+ try:
479
+ maybe_result = on_stream(payload)
480
+ if inspect.isawaitable(maybe_result):
481
+ await maybe_result
482
+ except Exception:
483
+ logger.exception(
484
+ "Error while handling on_stream event for agent tool %s.",
485
+ self.name,
486
+ )
487
+
488
+ async def dispatch_stream_events() -> None:
489
+ while True:
490
+ payload = await event_queue.get()
491
+ is_sentinel = payload is None # None marks the end of the stream.
492
+ try:
493
+ if payload is not None:
494
+ await _run_handler(payload)
495
+ finally:
496
+ event_queue.task_done()
497
+
498
+ if is_sentinel:
499
+ break
500
+
501
+ dispatch_task = asyncio.create_task(dispatch_stream_events())
502
+
503
+ try:
504
+ from .stream_events import AgentUpdatedStreamEvent
505
+
506
+ current_agent = run_result.current_agent
507
+ async for event in run_result.stream_events():
508
+ if isinstance(event, AgentUpdatedStreamEvent):
509
+ current_agent = event.new_agent
510
+
511
+ payload: AgentToolStreamEvent = {
512
+ "event": event,
513
+ "agent": current_agent,
514
+ "tool_call": context.tool_call,
515
+ }
516
+ await event_queue.put(payload)
517
+ finally:
518
+ await event_queue.put(None)
519
+ await event_queue.join()
520
+ await dispatch_task
521
+ else:
522
+ run_result = await Runner.run(
523
+ starting_agent=self,
524
+ input=input,
525
+ context=context.context,
526
+ run_config=run_config,
527
+ max_turns=resolved_max_turns,
528
+ hooks=hooks,
529
+ previous_response_id=previous_response_id,
530
+ conversation_id=conversation_id,
531
+ session=session,
532
+ )
275
533
  if custom_output_extractor:
276
- return await custom_output_extractor(output)
534
+ return await custom_output_extractor(run_result)
277
535
 
278
- return ItemHelpers.text_message_outputs(output.new_items)
536
+ return run_result.final_output
279
537
 
280
538
  return run_agent
281
539
 
282
540
  async def get_system_prompt(self, run_context: RunContextWrapper[TContext]) -> str | None:
283
- """Get the system prompt for the agent."""
284
541
  if isinstance(self.instructions, str):
285
542
  return self.instructions
286
543
  elif callable(self.instructions):
544
+ # Inspect the signature of the instructions function
545
+ sig = inspect.signature(self.instructions)
546
+ params = list(sig.parameters.values())
547
+
548
+ # Enforce exactly 2 parameters
549
+ if len(params) != 2:
550
+ raise TypeError(
551
+ f"'instructions' callable must accept exactly 2 arguments (context, agent), "
552
+ f"but got {len(params)}: {[p.name for p in params]}"
553
+ )
554
+
555
+ # Call the instructions function properly
287
556
  if inspect.iscoroutinefunction(self.instructions):
288
557
  return await cast(Awaitable[str], self.instructions(run_context, self))
289
558
  else:
290
559
  return cast(str, self.instructions(run_context, self))
560
+
291
561
  elif self.instructions is not None:
292
- logger.error(f"Instructions must be a string or a function, got {self.instructions}")
562
+ logger.error(
563
+ f"Instructions must be a string or a callable function, "
564
+ f"got {type(self.instructions).__name__}"
565
+ )
293
566
 
294
567
  return None
295
568