openai-agents 0.3.0__py3-none-any.whl → 0.3.2__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.

Potentially problematic release.


This version of openai-agents might be problematic. Click here for more details.

agents/run.py CHANGED
@@ -45,6 +45,7 @@ from .guardrail import (
45
45
  )
46
46
  from .handoffs import Handoff, HandoffInputFilter, handoff
47
47
  from .items import (
48
+ HandoffCallItem,
48
49
  ItemHelpers,
49
50
  ModelResponse,
50
51
  RunItem,
@@ -52,7 +53,7 @@ from .items import (
52
53
  ToolCallItemTypes,
53
54
  TResponseInputItem,
54
55
  )
55
- from .lifecycle import RunHooks
56
+ from .lifecycle import AgentHooksBase, RunHooks, RunHooksBase
56
57
  from .logger import logger
57
58
  from .memory import Session, SessionInputCallback
58
59
  from .model_settings import ModelSettings
@@ -60,7 +61,12 @@ from .models.interface import Model, ModelProvider
60
61
  from .models.multi_provider import MultiProvider
61
62
  from .result import RunResult, RunResultStreaming
62
63
  from .run_context import RunContextWrapper, TContext
63
- from .stream_events import AgentUpdatedStreamEvent, RawResponsesStreamEvent, RunItemStreamEvent
64
+ from .stream_events import (
65
+ AgentUpdatedStreamEvent,
66
+ RawResponsesStreamEvent,
67
+ RunItemStreamEvent,
68
+ StreamEvent,
69
+ )
64
70
  from .tool import Tool
65
71
  from .tracing import Span, SpanError, agent_span, get_current_trace, trace
66
72
  from .tracing.span_data import AgentSpanData
@@ -237,39 +243,54 @@ class Runner:
237
243
  conversation_id: str | None = None,
238
244
  session: Session | None = None,
239
245
  ) -> RunResult:
240
- """Run a workflow starting at the given agent. The agent will run in a loop until a final
241
- output is generated. The loop runs like so:
242
- 1. The agent is invoked with the given input.
243
- 2. If there is a final output (i.e. the agent produces something of type
244
- `agent.output_type`, the loop terminates.
245
- 3. If there's a handoff, we run the loop again, with the new agent.
246
- 4. Else, we run tool calls (if any), and re-run the loop.
246
+ """
247
+ Run a workflow starting at the given agent.
248
+
249
+ The agent will run in a loop until a final output is generated. The loop runs like so:
250
+
251
+ 1. The agent is invoked with the given input.
252
+ 2. If there is a final output (i.e. the agent produces something of type
253
+ `agent.output_type`), the loop terminates.
254
+ 3. If there's a handoff, we run the loop again, with the new agent.
255
+ 4. Else, we run tool calls (if any), and re-run the loop.
256
+
247
257
  In two cases, the agent may raise an exception:
248
- 1. If the max_turns is exceeded, a MaxTurnsExceeded exception is raised.
249
- 2. If a guardrail tripwire is triggered, a GuardrailTripwireTriggered exception is raised.
250
- Note that only the first agent's input guardrails are run.
258
+
259
+ 1. If the max_turns is exceeded, a MaxTurnsExceeded exception is raised.
260
+ 2. If a guardrail tripwire is triggered, a GuardrailTripwireTriggered
261
+ exception is raised.
262
+
263
+ Note:
264
+ Only the first agent's input guardrails are run.
265
+
251
266
  Args:
252
267
  starting_agent: The starting agent to run.
253
- input: The initial input to the agent. You can pass a single string for a user message,
254
- or a list of input items.
268
+ input: The initial input to the agent. You can pass a single string for a
269
+ user message, or a list of input items.
255
270
  context: The context to run the agent with.
256
- max_turns: The maximum number of turns to run the agent for. A turn is defined as one
257
- AI invocation (including any tool calls that might occur).
271
+ max_turns: The maximum number of turns to run the agent for. A turn is
272
+ defined as one AI invocation (including any tool calls that might occur).
258
273
  hooks: An object that receives callbacks on various lifecycle events.
259
274
  run_config: Global settings for the entire agent run.
260
- previous_response_id: The ID of the previous response, if using OpenAI models via the
261
- Responses API, this allows you to skip passing in input from the previous turn.
262
- conversation_id: The conversation ID (https://platform.openai.com/docs/guides/conversation-state?api-mode=responses).
275
+ previous_response_id: The ID of the previous response. If using OpenAI
276
+ models via the Responses API, this allows you to skip passing in input
277
+ from the previous turn.
278
+ conversation_id: The conversation ID
279
+ (https://platform.openai.com/docs/guides/conversation-state?api-mode=responses).
263
280
  If provided, the conversation will be used to read and write items.
264
281
  Every agent will have access to the conversation history so far,
265
- and it's output items will be written to the conversation.
282
+ and its output items will be written to the conversation.
266
283
  We recommend only using this if you are exclusively using OpenAI models;
267
284
  other model providers don't write to the Conversation object,
268
285
  so you'll end up having partial conversations stored.
286
+ session: A session for automatic conversation history management.
287
+
269
288
  Returns:
270
- A run result containing all the inputs, guardrail results and the output of the last
271
- agent. Agents may perform handoffs, so we don't know the specific type of the output.
289
+ A run result containing all the inputs, guardrail results and the output of
290
+ the last agent. Agents may perform handoffs, so we don't know the specific
291
+ type of the output.
272
292
  """
293
+
273
294
  runner = DEFAULT_AGENT_RUNNER
274
295
  return await runner.run(
275
296
  starting_agent,
@@ -297,36 +318,52 @@ class Runner:
297
318
  conversation_id: str | None = None,
298
319
  session: Session | None = None,
299
320
  ) -> RunResult:
300
- """Run a workflow synchronously, starting at the given agent. Note that this just wraps the
301
- `run` method, so it will not work if there's already an event loop (e.g. inside an async
302
- function, or in a Jupyter notebook or async context like FastAPI). For those cases, use
303
- the `run` method instead.
304
- The agent will run in a loop until a final output is generated. The loop runs like so:
305
- 1. The agent is invoked with the given input.
306
- 2. If there is a final output (i.e. the agent produces something of type
307
- `agent.output_type`, the loop terminates.
308
- 3. If there's a handoff, we run the loop again, with the new agent.
309
- 4. Else, we run tool calls (if any), and re-run the loop.
321
+ """
322
+ Run a workflow synchronously, starting at the given agent.
323
+
324
+ Note:
325
+ This just wraps the `run` method, so it will not work if there's already an
326
+ event loop (e.g. inside an async function, or in a Jupyter notebook or async
327
+ context like FastAPI). For those cases, use the `run` method instead.
328
+
329
+ The agent will run in a loop until a final output is generated. The loop runs:
330
+
331
+ 1. The agent is invoked with the given input.
332
+ 2. If there is a final output (i.e. the agent produces something of type
333
+ `agent.output_type`), the loop terminates.
334
+ 3. If there's a handoff, we run the loop again, with the new agent.
335
+ 4. Else, we run tool calls (if any), and re-run the loop.
336
+
310
337
  In two cases, the agent may raise an exception:
311
- 1. If the max_turns is exceeded, a MaxTurnsExceeded exception is raised.
312
- 2. If a guardrail tripwire is triggered, a GuardrailTripwireTriggered exception is raised.
313
- Note that only the first agent's input guardrails are run.
338
+
339
+ 1. If the max_turns is exceeded, a MaxTurnsExceeded exception is raised.
340
+ 2. If a guardrail tripwire is triggered, a GuardrailTripwireTriggered
341
+ exception is raised.
342
+
343
+ Note:
344
+ Only the first agent's input guardrails are run.
345
+
314
346
  Args:
315
347
  starting_agent: The starting agent to run.
316
- input: The initial input to the agent. You can pass a single string for a user message,
317
- or a list of input items.
348
+ input: The initial input to the agent. You can pass a single string for a
349
+ user message, or a list of input items.
318
350
  context: The context to run the agent with.
319
- max_turns: The maximum number of turns to run the agent for. A turn is defined as one
320
- AI invocation (including any tool calls that might occur).
351
+ max_turns: The maximum number of turns to run the agent for. A turn is
352
+ defined as one AI invocation (including any tool calls that might occur).
321
353
  hooks: An object that receives callbacks on various lifecycle events.
322
354
  run_config: Global settings for the entire agent run.
323
- previous_response_id: The ID of the previous response, if using OpenAI models via the
324
- Responses API, this allows you to skip passing in input from the previous turn.
355
+ previous_response_id: The ID of the previous response, if using OpenAI
356
+ models via the Responses API, this allows you to skip passing in input
357
+ from the previous turn.
325
358
  conversation_id: The ID of the stored conversation, if any.
359
+ session: A session for automatic conversation history management.
360
+
326
361
  Returns:
327
- A run result containing all the inputs, guardrail results and the output of the last
328
- agent. Agents may perform handoffs, so we don't know the specific type of the output.
362
+ A run result containing all the inputs, guardrail results and the output of
363
+ the last agent. Agents may perform handoffs, so we don't know the specific
364
+ type of the output.
329
365
  """
366
+
330
367
  runner = DEFAULT_AGENT_RUNNER
331
368
  return runner.run_sync(
332
369
  starting_agent,
@@ -353,33 +390,49 @@ class Runner:
353
390
  conversation_id: str | None = None,
354
391
  session: Session | None = None,
355
392
  ) -> RunResultStreaming:
356
- """Run a workflow starting at the given agent in streaming mode. The returned result object
357
- contains a method you can use to stream semantic events as they are generated.
393
+ """
394
+ Run a workflow starting at the given agent in streaming mode.
395
+
396
+ The returned result object contains a method you can use to stream semantic
397
+ events as they are generated.
398
+
358
399
  The agent will run in a loop until a final output is generated. The loop runs like so:
359
- 1. The agent is invoked with the given input.
360
- 2. If there is a final output (i.e. the agent produces something of type
361
- `agent.output_type`, the loop terminates.
362
- 3. If there's a handoff, we run the loop again, with the new agent.
363
- 4. Else, we run tool calls (if any), and re-run the loop.
400
+
401
+ 1. The agent is invoked with the given input.
402
+ 2. If there is a final output (i.e. the agent produces something of type
403
+ `agent.output_type`), the loop terminates.
404
+ 3. If there's a handoff, we run the loop again, with the new agent.
405
+ 4. Else, we run tool calls (if any), and re-run the loop.
406
+
364
407
  In two cases, the agent may raise an exception:
365
- 1. If the max_turns is exceeded, a MaxTurnsExceeded exception is raised.
366
- 2. If a guardrail tripwire is triggered, a GuardrailTripwireTriggered exception is raised.
367
- Note that only the first agent's input guardrails are run.
408
+
409
+ 1. If the max_turns is exceeded, a MaxTurnsExceeded exception is raised.
410
+ 2. If a guardrail tripwire is triggered, a GuardrailTripwireTriggered
411
+ exception is raised.
412
+
413
+ Note:
414
+ Only the first agent's input guardrails are run.
415
+
368
416
  Args:
369
417
  starting_agent: The starting agent to run.
370
- input: The initial input to the agent. You can pass a single string for a user message,
371
- or a list of input items.
418
+ input: The initial input to the agent. You can pass a single string for a
419
+ user message, or a list of input items.
372
420
  context: The context to run the agent with.
373
- max_turns: The maximum number of turns to run the agent for. A turn is defined as one
374
- AI invocation (including any tool calls that might occur).
421
+ max_turns: The maximum number of turns to run the agent for. A turn is
422
+ defined as one AI invocation (including any tool calls that might occur).
375
423
  hooks: An object that receives callbacks on various lifecycle events.
376
424
  run_config: Global settings for the entire agent run.
377
- previous_response_id: The ID of the previous response, if using OpenAI models via the
378
- Responses API, this allows you to skip passing in input from the previous turn.
425
+ previous_response_id: The ID of the previous response, if using OpenAI
426
+ models via the Responses API, this allows you to skip passing in input
427
+ from the previous turn.
379
428
  conversation_id: The ID of the stored conversation, if any.
429
+ session: A session for automatic conversation history management.
430
+
380
431
  Returns:
381
- A result object that contains data about the run, as well as a method to stream events.
432
+ A result object that contains data about the run, as well as a method to
433
+ stream events.
382
434
  """
435
+
383
436
  runner = DEFAULT_AGENT_RUNNER
384
437
  return runner.run_streamed(
385
438
  starting_agent,
@@ -408,13 +461,11 @@ class AgentRunner:
408
461
  ) -> RunResult:
409
462
  context = kwargs.get("context")
410
463
  max_turns = kwargs.get("max_turns", DEFAULT_MAX_TURNS)
411
- hooks = kwargs.get("hooks")
464
+ hooks = cast(RunHooks[TContext], self._validate_run_hooks(kwargs.get("hooks")))
412
465
  run_config = kwargs.get("run_config")
413
466
  previous_response_id = kwargs.get("previous_response_id")
414
467
  conversation_id = kwargs.get("conversation_id")
415
468
  session = kwargs.get("session")
416
- if hooks is None:
417
- hooks = RunHooks[Any]()
418
469
  if run_config is None:
419
470
  run_config = RunConfig()
420
471
 
@@ -615,14 +666,12 @@ class AgentRunner:
615
666
  ) -> RunResultStreaming:
616
667
  context = kwargs.get("context")
617
668
  max_turns = kwargs.get("max_turns", DEFAULT_MAX_TURNS)
618
- hooks = kwargs.get("hooks")
669
+ hooks = cast(RunHooks[TContext], self._validate_run_hooks(kwargs.get("hooks")))
619
670
  run_config = kwargs.get("run_config")
620
671
  previous_response_id = kwargs.get("previous_response_id")
621
672
  conversation_id = kwargs.get("conversation_id")
622
673
  session = kwargs.get("session")
623
674
 
624
- if hooks is None:
625
- hooks = RunHooks[Any]()
626
675
  if run_config is None:
627
676
  run_config = RunConfig()
628
677
 
@@ -679,6 +728,23 @@ class AgentRunner:
679
728
  )
680
729
  return streamed_result
681
730
 
731
+ @staticmethod
732
+ def _validate_run_hooks(
733
+ hooks: RunHooksBase[Any, Agent[Any]] | AgentHooksBase[Any, Agent[Any]] | Any | None,
734
+ ) -> RunHooks[Any]:
735
+ if hooks is None:
736
+ return RunHooks[Any]()
737
+ input_hook_type = type(hooks).__name__
738
+ if isinstance(hooks, AgentHooksBase):
739
+ raise TypeError(
740
+ "Run hooks must be instances of RunHooks. "
741
+ f"Received agent-scoped hooks ({input_hook_type}). "
742
+ "Attach AgentHooks to an Agent via Agent(..., hooks=...)."
743
+ )
744
+ if not isinstance(hooks, RunHooksBase):
745
+ raise TypeError(f"Run hooks must be instances of RunHooks. Received {input_hook_type}.")
746
+ return hooks
747
+
682
748
  @classmethod
683
749
  async def _maybe_filter_model_input(
684
750
  cls,
@@ -1095,14 +1161,19 @@ class AgentRunner:
1095
1161
  context_wrapper=context_wrapper,
1096
1162
  run_config=run_config,
1097
1163
  tool_use_tracker=tool_use_tracker,
1164
+ event_queue=streamed_result._event_queue,
1098
1165
  )
1099
1166
 
1100
- if emitted_tool_call_ids:
1101
- import dataclasses as _dc
1167
+ import dataclasses as _dc
1168
+
1169
+ # Filter out items that have already been sent to avoid duplicates
1170
+ items_to_filter = single_step_result.new_step_items
1102
1171
 
1103
- filtered_items = [
1172
+ if emitted_tool_call_ids:
1173
+ # Filter out tool call items that were already emitted during streaming
1174
+ items_to_filter = [
1104
1175
  item
1105
- for item in single_step_result.new_step_items
1176
+ for item in items_to_filter
1106
1177
  if not (
1107
1178
  isinstance(item, ToolCallItem)
1108
1179
  and (
@@ -1114,15 +1185,14 @@ class AgentRunner:
1114
1185
  )
1115
1186
  ]
1116
1187
 
1117
- single_step_result_filtered = _dc.replace(
1118
- single_step_result, new_step_items=filtered_items
1119
- )
1188
+ # Filter out HandoffCallItem to avoid duplicates (already sent earlier)
1189
+ items_to_filter = [
1190
+ item for item in items_to_filter if not isinstance(item, HandoffCallItem)
1191
+ ]
1120
1192
 
1121
- RunImpl.stream_step_result_to_queue(
1122
- single_step_result_filtered, streamed_result._event_queue
1123
- )
1124
- else:
1125
- RunImpl.stream_step_result_to_queue(single_step_result, streamed_result._event_queue)
1193
+ # Create filtered result and send to queue
1194
+ filtered_result = _dc.replace(single_step_result, new_step_items=items_to_filter)
1195
+ RunImpl.stream_step_result_to_queue(filtered_result, streamed_result._event_queue)
1126
1196
  return single_step_result
1127
1197
 
1128
1198
  @classmethod
@@ -1207,6 +1277,7 @@ class AgentRunner:
1207
1277
  context_wrapper: RunContextWrapper[TContext],
1208
1278
  run_config: RunConfig,
1209
1279
  tool_use_tracker: AgentToolUseTracker,
1280
+ event_queue: asyncio.Queue[StreamEvent | QueueCompleteSentinel] | None = None,
1210
1281
  ) -> SingleStepResult:
1211
1282
  processed_response = RunImpl.process_model_response(
1212
1283
  agent=agent,
@@ -1218,6 +1289,14 @@ class AgentRunner:
1218
1289
 
1219
1290
  tool_use_tracker.add_tool_use(agent, processed_response.tools_used)
1220
1291
 
1292
+ # Send handoff items immediately for streaming, but avoid duplicates
1293
+ if event_queue is not None and processed_response.new_items:
1294
+ handoff_items = [
1295
+ item for item in processed_response.new_items if isinstance(item, HandoffCallItem)
1296
+ ]
1297
+ if handoff_items:
1298
+ RunImpl.stream_step_items_to_queue(cast(list[RunItem], handoff_items), event_queue)
1299
+
1221
1300
  return await RunImpl.execute_tools_and_side_effects(
1222
1301
  agent=agent,
1223
1302
  original_input=original_input,
agents/tool_context.py CHANGED
@@ -14,6 +14,10 @@ def _assert_must_pass_tool_name() -> str:
14
14
  raise ValueError("tool_name must be passed to ToolContext")
15
15
 
16
16
 
17
+ def _assert_must_pass_tool_arguments() -> str:
18
+ raise ValueError("tool_arguments must be passed to ToolContext")
19
+
20
+
17
21
  @dataclass
18
22
  class ToolContext(RunContextWrapper[TContext]):
19
23
  """The context of a tool call."""
@@ -24,6 +28,9 @@ class ToolContext(RunContextWrapper[TContext]):
24
28
  tool_call_id: str = field(default_factory=_assert_must_pass_tool_call_id)
25
29
  """The ID of the tool call."""
26
30
 
31
+ tool_arguments: str = field(default_factory=_assert_must_pass_tool_arguments)
32
+ """The raw arguments string of the tool call."""
33
+
27
34
  @classmethod
28
35
  def from_agent_context(
29
36
  cls,
@@ -39,4 +46,10 @@ class ToolContext(RunContextWrapper[TContext]):
39
46
  f.name: getattr(context, f.name) for f in fields(RunContextWrapper) if f.init
40
47
  }
41
48
  tool_name = tool_call.name if tool_call is not None else _assert_must_pass_tool_name()
42
- return cls(tool_name=tool_name, tool_call_id=tool_call_id, **base_values)
49
+ tool_args = (
50
+ tool_call.arguments if tool_call is not None else _assert_must_pass_tool_arguments()
51
+ )
52
+
53
+ return cls(
54
+ tool_name=tool_name, tool_call_id=tool_call_id, tool_arguments=tool_args, **base_values
55
+ )
@@ -7,52 +7,125 @@ if TYPE_CHECKING:
7
7
 
8
8
 
9
9
  class TracingProcessor(abc.ABC):
10
- """Interface for processing spans."""
10
+ """Interface for processing and monitoring traces and spans in the OpenAI Agents system.
11
+
12
+ This abstract class defines the interface that all tracing processors must implement.
13
+ Processors receive notifications when traces and spans start and end, allowing them
14
+ to collect, process, and export tracing data.
15
+
16
+ Example:
17
+ ```python
18
+ class CustomProcessor(TracingProcessor):
19
+ def __init__(self):
20
+ self.active_traces = {}
21
+ self.active_spans = {}
22
+
23
+ def on_trace_start(self, trace):
24
+ self.active_traces[trace.trace_id] = trace
25
+
26
+ def on_trace_end(self, trace):
27
+ # Process completed trace
28
+ del self.active_traces[trace.trace_id]
29
+
30
+ def on_span_start(self, span):
31
+ self.active_spans[span.span_id] = span
32
+
33
+ def on_span_end(self, span):
34
+ # Process completed span
35
+ del self.active_spans[span.span_id]
36
+
37
+ def shutdown(self):
38
+ # Clean up resources
39
+ self.active_traces.clear()
40
+ self.active_spans.clear()
41
+
42
+ def force_flush(self):
43
+ # Force processing of any queued items
44
+ pass
45
+ ```
46
+
47
+ Notes:
48
+ - All methods should be thread-safe
49
+ - Methods should not block for long periods
50
+ - Handle errors gracefully to prevent disrupting agent execution
51
+ """
11
52
 
12
53
  @abc.abstractmethod
13
54
  def on_trace_start(self, trace: "Trace") -> None:
14
- """Called when a trace is started.
55
+ """Called when a new trace begins execution.
15
56
 
16
57
  Args:
17
- trace: The trace that started.
58
+ trace: The trace that started. Contains workflow name and metadata.
59
+
60
+ Notes:
61
+ - Called synchronously on trace start
62
+ - Should return quickly to avoid blocking execution
63
+ - Any errors should be caught and handled internally
18
64
  """
19
65
  pass
20
66
 
21
67
  @abc.abstractmethod
22
68
  def on_trace_end(self, trace: "Trace") -> None:
23
- """Called when a trace is finished.
69
+ """Called when a trace completes execution.
24
70
 
25
71
  Args:
26
- trace: The trace that finished.
72
+ trace: The completed trace containing all spans and results.
73
+
74
+ Notes:
75
+ - Called synchronously when trace finishes
76
+ - Good time to export/process the complete trace
77
+ - Should handle cleanup of any trace-specific resources
27
78
  """
28
79
  pass
29
80
 
30
81
  @abc.abstractmethod
31
82
  def on_span_start(self, span: "Span[Any]") -> None:
32
- """Called when a span is started.
83
+ """Called when a new span begins execution.
33
84
 
34
85
  Args:
35
- span: The span that started.
86
+ span: The span that started. Contains operation details and context.
87
+
88
+ Notes:
89
+ - Called synchronously on span start
90
+ - Should return quickly to avoid blocking execution
91
+ - Spans are automatically nested under current trace/span
36
92
  """
37
93
  pass
38
94
 
39
95
  @abc.abstractmethod
40
96
  def on_span_end(self, span: "Span[Any]") -> None:
41
- """Called when a span is finished. Should not block or raise exceptions.
97
+ """Called when a span completes execution.
42
98
 
43
99
  Args:
44
- span: The span that finished.
100
+ span: The completed span containing execution results.
101
+
102
+ Notes:
103
+ - Called synchronously when span finishes
104
+ - Should not block or raise exceptions
105
+ - Good time to export/process the individual span
45
106
  """
46
107
  pass
47
108
 
48
109
  @abc.abstractmethod
49
110
  def shutdown(self) -> None:
50
- """Called when the application stops."""
111
+ """Called when the application stops to clean up resources.
112
+
113
+ Should perform any necessary cleanup like:
114
+ - Flushing queued traces/spans
115
+ - Closing connections
116
+ - Releasing resources
117
+ """
51
118
  pass
52
119
 
53
120
  @abc.abstractmethod
54
121
  def force_flush(self) -> None:
55
- """Forces an immediate flush of all queued spans/traces."""
122
+ """Forces immediate processing of any queued traces/spans.
123
+
124
+ Notes:
125
+ - Should process all queued items before returning
126
+ - Useful before shutdown or when immediate processing is needed
127
+ - May block while processing completes
128
+ """
56
129
  pass
57
130
 
58
131