openai-agents 0.0.3__py3-none-any.whl → 0.0.5__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/__init__.py CHANGED
@@ -5,7 +5,7 @@ from typing import Literal
5
5
  from openai import AsyncOpenAI
6
6
 
7
7
  from . import _config
8
- from .agent import Agent
8
+ from .agent import Agent, ToolsToFinalOutputFunction, ToolsToFinalOutputResult
9
9
  from .agent_output import AgentOutputSchema
10
10
  from .computer import AsyncComputer, Button, Computer, Environment
11
11
  from .exceptions import (
@@ -57,6 +57,7 @@ from .tool import (
57
57
  ComputerTool,
58
58
  FileSearchTool,
59
59
  FunctionTool,
60
+ FunctionToolResult,
60
61
  Tool,
61
62
  WebSearchTool,
62
63
  default_tool_error_function,
@@ -73,6 +74,7 @@ from .tracing import (
73
74
  SpanData,
74
75
  SpanError,
75
76
  Trace,
77
+ TracingProcessor,
76
78
  add_trace_processor,
77
79
  agent_span,
78
80
  custom_span,
@@ -92,13 +94,19 @@ from .tracing import (
92
94
  from .usage import Usage
93
95
 
94
96
 
95
- def set_default_openai_key(key: str) -> None:
96
- """Set the default OpenAI API key to use for LLM requests and tracing. This is only necessary if
97
- the OPENAI_API_KEY environment variable is not already set.
97
+ def set_default_openai_key(key: str, use_for_tracing: bool = True) -> None:
98
+ """Set the default OpenAI API key to use for LLM requests (and optionally tracing(). This is
99
+ only necessary if the OPENAI_API_KEY environment variable is not already set.
98
100
 
99
101
  If provided, this key will be used instead of the OPENAI_API_KEY environment variable.
102
+
103
+ Args:
104
+ key: The OpenAI key to use.
105
+ use_for_tracing: Whether to also use this key to send traces to OpenAI. Defaults to True
106
+ If False, you'll either need to set the OPENAI_API_KEY environment variable or call
107
+ set_tracing_export_api_key() with the API key you want to use for tracing.
100
108
  """
101
- _config.set_default_openai_key(key)
109
+ _config.set_default_openai_key(key, use_for_tracing)
102
110
 
103
111
 
104
112
  def set_default_openai_client(client: AsyncOpenAI, use_for_tracing: bool = True) -> None:
@@ -123,14 +131,15 @@ def set_default_openai_api(api: Literal["chat_completions", "responses"]) -> Non
123
131
 
124
132
  def enable_verbose_stdout_logging():
125
133
  """Enables verbose logging to stdout. This is useful for debugging."""
126
- for name in ["openai.agents", "openai.agents.tracing"]:
127
- logger = logging.getLogger(name)
128
- logger.setLevel(logging.DEBUG)
129
- logger.addHandler(logging.StreamHandler(sys.stdout))
134
+ logger = logging.getLogger("openai.agents")
135
+ logger.setLevel(logging.DEBUG)
136
+ logger.addHandler(logging.StreamHandler(sys.stdout))
130
137
 
131
138
 
132
139
  __all__ = [
133
140
  "Agent",
141
+ "ToolsToFinalOutputFunction",
142
+ "ToolsToFinalOutputResult",
134
143
  "Runner",
135
144
  "Model",
136
145
  "ModelProvider",
@@ -184,6 +193,7 @@ __all__ = [
184
193
  "AgentUpdatedStreamEvent",
185
194
  "StreamEvent",
186
195
  "FunctionTool",
196
+ "FunctionToolResult",
187
197
  "ComputerTool",
188
198
  "FileSearchTool",
189
199
  "Tool",
@@ -203,6 +213,7 @@ __all__ = [
203
213
  "set_tracing_disabled",
204
214
  "trace",
205
215
  "Trace",
216
+ "TracingProcessor",
206
217
  "SpanError",
207
218
  "Span",
208
219
  "SpanData",
agents/_config.py CHANGED
@@ -5,15 +5,18 @@ from .models import _openai_shared
5
5
  from .tracing import set_tracing_export_api_key
6
6
 
7
7
 
8
- def set_default_openai_key(key: str) -> None:
9
- set_tracing_export_api_key(key)
8
+ def set_default_openai_key(key: str, use_for_tracing: bool) -> None:
10
9
  _openai_shared.set_default_openai_key(key)
11
10
 
11
+ if use_for_tracing:
12
+ set_tracing_export_api_key(key)
13
+
12
14
 
13
15
  def set_default_openai_client(client: AsyncOpenAI, use_for_tracing: bool) -> None:
16
+ _openai_shared.set_default_openai_client(client)
17
+
14
18
  if use_for_tracing:
15
19
  set_tracing_export_api_key(client.api_key)
16
- _openai_shared.set_default_openai_client(client)
17
20
 
18
21
 
19
22
  def set_default_openai_api(api: Literal["chat_completions", "responses"]) -> None:
agents/_run_impl.py CHANGED
@@ -1,8 +1,10 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import asyncio
4
+ import inspect
5
+ from collections.abc import Awaitable
4
6
  from dataclasses import dataclass
5
- from typing import TYPE_CHECKING, Any
7
+ from typing import TYPE_CHECKING, Any, cast
6
8
 
7
9
  from openai.types.responses import (
8
10
  ResponseComputerToolCall,
@@ -25,8 +27,7 @@ from openai.types.responses.response_computer_tool_call import (
25
27
  from openai.types.responses.response_input_param import ComputerCallOutput
26
28
  from openai.types.responses.response_reasoning_item import ResponseReasoningItem
27
29
 
28
- from . import _utils
29
- from .agent import Agent
30
+ from .agent import Agent, ToolsToFinalOutputResult
30
31
  from .agent_output import AgentOutputSchema
31
32
  from .computer import AsyncComputer, Computer
32
33
  from .exceptions import AgentsException, ModelBehaviorError, UserError
@@ -49,7 +50,7 @@ from .logger import logger
49
50
  from .models.interface import ModelTracing
50
51
  from .run_context import RunContextWrapper, TContext
51
52
  from .stream_events import RunItemStreamEvent, StreamEvent
52
- from .tool import ComputerTool, FunctionTool
53
+ from .tool import ComputerTool, FunctionTool, FunctionToolResult
53
54
  from .tracing import (
54
55
  SpanError,
55
56
  Trace,
@@ -59,6 +60,7 @@ from .tracing import (
59
60
  handoff_span,
60
61
  trace,
61
62
  )
63
+ from .util import _coro, _error_tracing
62
64
 
63
65
  if TYPE_CHECKING:
64
66
  from .run import RunConfig
@@ -70,6 +72,8 @@ class QueueCompleteSentinel:
70
72
 
71
73
  QUEUE_COMPLETE_SENTINEL = QueueCompleteSentinel()
72
74
 
75
+ _NOT_FINAL_OUTPUT = ToolsToFinalOutputResult(is_final_output=False, final_output=None)
76
+
73
77
 
74
78
  @dataclass
75
79
  class ToolRunHandoff:
@@ -167,7 +171,7 @@ class RunImpl:
167
171
  agent: Agent[TContext],
168
172
  # The original input to the Runner
169
173
  original_input: str | list[TResponseInputItem],
170
- # Eveything generated by Runner since the original input, but before the current step
174
+ # Everything generated by Runner since the original input, but before the current step
171
175
  pre_step_items: list[RunItem],
172
176
  new_response: ModelResponse,
173
177
  processed_response: ProcessedResponse,
@@ -199,7 +203,7 @@ class RunImpl:
199
203
  config=run_config,
200
204
  ),
201
205
  )
202
- new_step_items.extend(function_results)
206
+ new_step_items.extend([result.run_item for result in function_results])
203
207
  new_step_items.extend(computer_results)
204
208
 
205
209
  # Second, check if there are any handoffs
@@ -216,6 +220,36 @@ class RunImpl:
216
220
  run_config=run_config,
217
221
  )
218
222
 
223
+ # Third, we'll check if the tool use should result in a final output
224
+ check_tool_use = await cls._check_for_final_output_from_tools(
225
+ agent=agent,
226
+ tool_results=function_results,
227
+ context_wrapper=context_wrapper,
228
+ config=run_config,
229
+ )
230
+
231
+ if check_tool_use.is_final_output:
232
+ # If the output type is str, then let's just stringify it
233
+ if not agent.output_type or agent.output_type is str:
234
+ check_tool_use.final_output = str(check_tool_use.final_output)
235
+
236
+ if check_tool_use.final_output is None:
237
+ logger.error(
238
+ "Model returned a final output of None. Not raising an error because we assume"
239
+ "you know what you're doing."
240
+ )
241
+
242
+ return await cls.execute_final_output(
243
+ agent=agent,
244
+ original_input=original_input,
245
+ new_response=new_response,
246
+ pre_step_items=pre_step_items,
247
+ new_step_items=new_step_items,
248
+ final_output=check_tool_use.final_output,
249
+ hooks=hooks,
250
+ context_wrapper=context_wrapper,
251
+ )
252
+
219
253
  # Now we can check if the model also produced a final output
220
254
  message_items = [item for item in new_step_items if isinstance(item, MessageOutputItem)]
221
255
 
@@ -293,7 +327,7 @@ class RunImpl:
293
327
  elif isinstance(output, ResponseComputerToolCall):
294
328
  items.append(ToolCallItem(raw_item=output, agent=agent))
295
329
  if not computer_tool:
296
- _utils.attach_error_to_current_span(
330
+ _error_tracing.attach_error_to_current_span(
297
331
  SpanError(
298
332
  message="Computer tool not found",
299
333
  data={},
@@ -324,7 +358,7 @@ class RunImpl:
324
358
  # Regular function tool call
325
359
  else:
326
360
  if output.name not in function_map:
327
- _utils.attach_error_to_current_span(
361
+ _error_tracing.attach_error_to_current_span(
328
362
  SpanError(
329
363
  message="Tool not found",
330
364
  data={"tool_name": output.name},
@@ -355,10 +389,10 @@ class RunImpl:
355
389
  hooks: RunHooks[TContext],
356
390
  context_wrapper: RunContextWrapper[TContext],
357
391
  config: RunConfig,
358
- ) -> list[RunItem]:
392
+ ) -> list[FunctionToolResult]:
359
393
  async def run_single_tool(
360
394
  func_tool: FunctionTool, tool_call: ResponseFunctionToolCall
361
- ) -> str:
395
+ ) -> Any:
362
396
  with function_span(func_tool.name) as span_fn:
363
397
  if config.trace_include_sensitive_data:
364
398
  span_fn.span_data.input = tool_call.arguments
@@ -368,7 +402,7 @@ class RunImpl:
368
402
  (
369
403
  agent.hooks.on_tool_start(context_wrapper, agent, func_tool)
370
404
  if agent.hooks
371
- else _utils.noop_coroutine()
405
+ else _coro.noop_coroutine()
372
406
  ),
373
407
  func_tool.on_invoke_tool(context_wrapper, tool_call.arguments),
374
408
  )
@@ -378,11 +412,11 @@ class RunImpl:
378
412
  (
379
413
  agent.hooks.on_tool_end(context_wrapper, agent, func_tool, result)
380
414
  if agent.hooks
381
- else _utils.noop_coroutine()
415
+ else _coro.noop_coroutine()
382
416
  ),
383
417
  )
384
418
  except Exception as e:
385
- _utils.attach_error_to_current_span(
419
+ _error_tracing.attach_error_to_current_span(
386
420
  SpanError(
387
421
  message="Error running tool",
388
422
  data={"tool_name": func_tool.name, "error": str(e)},
@@ -404,10 +438,14 @@ class RunImpl:
404
438
  results = await asyncio.gather(*tasks)
405
439
 
406
440
  return [
407
- ToolCallOutputItem(
408
- output=str(result),
409
- raw_item=ItemHelpers.tool_call_output_item(tool_run.tool_call, str(result)),
410
- agent=agent,
441
+ FunctionToolResult(
442
+ tool=tool_run.function_tool,
443
+ output=result,
444
+ run_item=ToolCallOutputItem(
445
+ output=result,
446
+ raw_item=ItemHelpers.tool_call_output_item(tool_run.tool_call, str(result)),
447
+ agent=agent,
448
+ ),
411
449
  )
412
450
  for tool_run, result in zip(tool_runs, results)
413
451
  ]
@@ -502,7 +540,7 @@ class RunImpl:
502
540
  source=agent,
503
541
  )
504
542
  if agent.hooks
505
- else _utils.noop_coroutine()
543
+ else _coro.noop_coroutine()
506
544
  ),
507
545
  )
508
546
 
@@ -520,7 +558,7 @@ class RunImpl:
520
558
  new_items=tuple(new_step_items),
521
559
  )
522
560
  if not callable(input_filter):
523
- _utils.attach_error_to_span(
561
+ _error_tracing.attach_error_to_span(
524
562
  span_handoff,
525
563
  SpanError(
526
564
  message="Invalid input filter",
@@ -530,7 +568,7 @@ class RunImpl:
530
568
  raise UserError(f"Invalid input filter: {input_filter}")
531
569
  filtered = input_filter(handoff_input_data)
532
570
  if not isinstance(filtered, HandoffInputData):
533
- _utils.attach_error_to_span(
571
+ _error_tracing.attach_error_to_span(
534
572
  span_handoff,
535
573
  SpanError(
536
574
  message="Invalid input filter result",
@@ -591,7 +629,7 @@ class RunImpl:
591
629
  hooks.on_agent_end(context_wrapper, agent, final_output),
592
630
  agent.hooks.on_end(context_wrapper, agent, final_output)
593
631
  if agent.hooks
594
- else _utils.noop_coroutine(),
632
+ else _coro.noop_coroutine(),
595
633
  )
596
634
 
597
635
  @classmethod
@@ -646,6 +684,47 @@ class RunImpl:
646
684
  if event:
647
685
  queue.put_nowait(event)
648
686
 
687
+ @classmethod
688
+ async def _check_for_final_output_from_tools(
689
+ cls,
690
+ *,
691
+ agent: Agent[TContext],
692
+ tool_results: list[FunctionToolResult],
693
+ context_wrapper: RunContextWrapper[TContext],
694
+ config: RunConfig,
695
+ ) -> ToolsToFinalOutputResult:
696
+ """Returns (i, final_output)."""
697
+ if not tool_results:
698
+ return _NOT_FINAL_OUTPUT
699
+
700
+ if agent.tool_use_behavior == "run_llm_again":
701
+ return _NOT_FINAL_OUTPUT
702
+ elif agent.tool_use_behavior == "stop_on_first_tool":
703
+ return ToolsToFinalOutputResult(
704
+ is_final_output=True, final_output=tool_results[0].output
705
+ )
706
+ elif isinstance(agent.tool_use_behavior, dict):
707
+ names = agent.tool_use_behavior.get("stop_at_tool_names", [])
708
+ for tool_result in tool_results:
709
+ if tool_result.tool.name in names:
710
+ return ToolsToFinalOutputResult(
711
+ is_final_output=True, final_output=tool_result.output
712
+ )
713
+ return ToolsToFinalOutputResult(is_final_output=False, final_output=None)
714
+ elif callable(agent.tool_use_behavior):
715
+ if inspect.iscoroutinefunction(agent.tool_use_behavior):
716
+ return await cast(
717
+ Awaitable[ToolsToFinalOutputResult],
718
+ agent.tool_use_behavior(context_wrapper, tool_results),
719
+ )
720
+ else:
721
+ return cast(
722
+ ToolsToFinalOutputResult, agent.tool_use_behavior(context_wrapper, tool_results)
723
+ )
724
+
725
+ logger.error(f"Invalid tool_use_behavior: {agent.tool_use_behavior}")
726
+ raise UserError(f"Invalid tool_use_behavior: {agent.tool_use_behavior}")
727
+
649
728
 
650
729
  class TraceCtxManager:
651
730
  """Creates a trace only if there is no current trace, and manages the trace lifecycle."""
@@ -706,7 +785,7 @@ class ComputerAction:
706
785
  (
707
786
  agent.hooks.on_tool_start(context_wrapper, agent, action.computer_tool)
708
787
  if agent.hooks
709
- else _utils.noop_coroutine()
788
+ else _coro.noop_coroutine()
710
789
  ),
711
790
  output_func,
712
791
  )
@@ -716,7 +795,7 @@ class ComputerAction:
716
795
  (
717
796
  agent.hooks.on_tool_end(context_wrapper, agent, action.computer_tool, output)
718
797
  if agent.hooks
719
- else _utils.noop_coroutine()
798
+ else _coro.noop_coroutine()
720
799
  ),
721
800
  )
722
801
 
agents/agent.py CHANGED
@@ -4,10 +4,10 @@ import dataclasses
4
4
  import inspect
5
5
  from collections.abc import Awaitable
6
6
  from dataclasses import dataclass, field
7
- from typing import TYPE_CHECKING, Any, Callable, Generic, cast
7
+ from typing import TYPE_CHECKING, Any, Callable, Generic, Literal, cast
8
+
9
+ from typing_extensions import TypeAlias, TypedDict
8
10
 
9
- from . import _utils
10
- from ._utils import MaybeAwaitable
11
11
  from .guardrail import InputGuardrail, OutputGuardrail
12
12
  from .handoffs import Handoff
13
13
  from .items import ItemHelpers
@@ -15,20 +15,49 @@ from .logger import logger
15
15
  from .model_settings import ModelSettings
16
16
  from .models.interface import Model
17
17
  from .run_context import RunContextWrapper, TContext
18
- from .tool import Tool, function_tool
18
+ from .tool import FunctionToolResult, Tool, function_tool
19
+ from .util import _transforms
20
+ from .util._types import MaybeAwaitable
19
21
 
20
22
  if TYPE_CHECKING:
21
23
  from .lifecycle import AgentHooks
22
24
  from .result import RunResult
23
25
 
24
26
 
27
+ @dataclass
28
+ class ToolsToFinalOutputResult:
29
+ is_final_output: bool
30
+ """Whether this is the final output. If False, the LLM will run again and receive the tool call
31
+ output.
32
+ """
33
+
34
+ final_output: Any | None = None
35
+ """The final output. Can be None if `is_final_output` is False, otherwise must match the
36
+ `output_type` of the agent.
37
+ """
38
+
39
+
40
+ ToolsToFinalOutputFunction: TypeAlias = Callable[
41
+ [RunContextWrapper[TContext], list[FunctionToolResult]],
42
+ MaybeAwaitable[ToolsToFinalOutputResult],
43
+ ]
44
+ """A function that takes a run context and a list of tool results, and returns a
45
+ `ToolToFinalOutputResult`.
46
+ """
47
+
48
+
49
+ class StopAtTools(TypedDict):
50
+ stop_at_tool_names: list[str]
51
+ """A list of tool names, any of which will stop the agent from running further."""
52
+
53
+
25
54
  @dataclass
26
55
  class Agent(Generic[TContext]):
27
56
  """An agent is an AI model configured with instructions, tools, guardrails, handoffs and more.
28
57
 
29
58
  We strongly recommend passing `instructions`, which is the "system prompt" for the agent. In
30
- addition, you can pass `description`, which is a human-readable description of the agent, used
31
- when the agent is used inside tools/handoffs.
59
+ addition, you can pass `handoff_description`, which is a human-readable description of the
60
+ agent, used when the agent is used inside tools/handoffs.
32
61
 
33
62
  Agents are generic on the context type. The context is a (mutable) object you create. It is
34
63
  passed to tool functions, handoffs, guardrails, etc.
@@ -95,6 +124,25 @@ class Agent(Generic[TContext]):
95
124
  """A class that receives callbacks on various lifecycle events for this agent.
96
125
  """
97
126
 
127
+ tool_use_behavior: (
128
+ Literal["run_llm_again", "stop_on_first_tool"] | StopAtTools | ToolsToFinalOutputFunction
129
+ ) = "run_llm_again"
130
+ """This lets you configure how tool use is handled.
131
+ - "run_llm_again": The default behavior. Tools are run, and then the LLM receives the results
132
+ and gets to respond.
133
+ - "stop_on_first_tool": The output of the first tool call is used as the final output. This
134
+ means that the LLM does not process the result of the tool call.
135
+ - A list of tool names: The agent will stop running if any of the tools in the list are called.
136
+ The final output will be the output of the first matching tool call. The LLM does not
137
+ process the result of the tool call.
138
+ - A function: If you pass a function, it will be called with the run context and the list of
139
+ tool results. It must return a `ToolToFinalOutputResult`, which determines whether the tool
140
+ calls result in a final output.
141
+
142
+ NOTE: This configuration is specific to FunctionTools. Hosted tools, such as file search,
143
+ web search, etc are always processed by the LLM.
144
+ """
145
+
98
146
  def clone(self, **kwargs: Any) -> Agent[TContext]:
99
147
  """Make a copy of the agent, with the given arguments changed. For example, you could do:
100
148
  ```
@@ -126,7 +174,7 @@ class Agent(Generic[TContext]):
126
174
  """
127
175
 
128
176
  @function_tool(
129
- name_override=tool_name or _utils.transform_string_function_style(self.name),
177
+ name_override=tool_name or _transforms.transform_string_function_style(self.name),
130
178
  description_override=tool_description or "",
131
179
  )
132
180
  async def run_agent(context: RunContextWrapper, input: str) -> str:
agents/agent_output.py CHANGED
@@ -4,10 +4,10 @@ from typing import Any
4
4
  from pydantic import BaseModel, TypeAdapter
5
5
  from typing_extensions import TypedDict, get_args, get_origin
6
6
 
7
- from . import _utils
8
7
  from .exceptions import ModelBehaviorError, UserError
9
8
  from .strict_schema import ensure_strict_json_schema
10
9
  from .tracing import SpanError
10
+ from .util import _error_tracing, _json
11
11
 
12
12
  _WRAPPER_DICT_KEY = "response"
13
13
 
@@ -87,10 +87,10 @@ class AgentOutputSchema:
87
87
  """Validate a JSON string against the output type. Returns the validated object, or raises
88
88
  a `ModelBehaviorError` if the JSON is invalid.
89
89
  """
90
- validated = _utils.validate_json(json_str, self._type_adapter, partial)
90
+ validated = _json.validate_json(json_str, self._type_adapter, partial)
91
91
  if self._is_wrapped:
92
92
  if not isinstance(validated, dict):
93
- _utils.attach_error_to_current_span(
93
+ _error_tracing.attach_error_to_current_span(
94
94
  SpanError(
95
95
  message="Invalid JSON",
96
96
  data={"details": f"Expected a dict, got {type(validated)}"},
@@ -101,7 +101,7 @@ class AgentOutputSchema:
101
101
  )
102
102
 
103
103
  if _WRAPPER_DICT_KEY not in validated:
104
- _utils.attach_error_to_current_span(
104
+ _error_tracing.attach_error_to_current_span(
105
105
  SpanError(
106
106
  message="Invalid JSON",
107
107
  data={"details": f"Could not find key {_WRAPPER_DICT_KEY} in JSON"},
agents/function_schema.py CHANGED
@@ -33,6 +33,9 @@ class FuncSchema:
33
33
  """The signature of the function."""
34
34
  takes_context: bool = False
35
35
  """Whether the function takes a RunContextWrapper argument (must be the first argument)."""
36
+ strict_json_schema: bool = True
37
+ """Whether the JSON schema is in strict mode. We **strongly** recommend setting this to True,
38
+ as it increases the likelihood of correct JSON input."""
36
39
 
37
40
  def to_call_args(self, data: BaseModel) -> tuple[list[Any], dict[str, Any]]:
38
41
  """
@@ -337,4 +340,5 @@ def function_schema(
337
340
  params_json_schema=json_schema,
338
341
  signature=sig,
339
342
  takes_context=takes_context,
343
+ strict_json_schema=strict_json_schema,
340
344
  )
agents/guardrail.py CHANGED
@@ -7,10 +7,10 @@ from typing import TYPE_CHECKING, Any, Callable, Generic, Union, overload
7
7
 
8
8
  from typing_extensions import TypeVar
9
9
 
10
- from ._utils import MaybeAwaitable
11
10
  from .exceptions import UserError
12
11
  from .items import TResponseInputItem
13
12
  from .run_context import RunContextWrapper, TContext
13
+ from .util._types import MaybeAwaitable
14
14
 
15
15
  if TYPE_CHECKING:
16
16
  from .agent import Agent
@@ -86,7 +86,7 @@ class InputGuardrail(Generic[TContext]):
86
86
  [RunContextWrapper[TContext], Agent[Any], str | list[TResponseInputItem]],
87
87
  MaybeAwaitable[GuardrailFunctionOutput],
88
88
  ]
89
- """A function that receives the the agent input and the context, and returns a
89
+ """A function that receives the agent input and the context, and returns a
90
90
  `GuardrailResult`. The result marks whether the tripwire was triggered, and can optionally
91
91
  include information about the guardrail's output.
92
92
  """
agents/handoffs.py CHANGED
@@ -8,12 +8,12 @@ from typing import TYPE_CHECKING, Any, Callable, Generic, cast, overload
8
8
  from pydantic import TypeAdapter
9
9
  from typing_extensions import TypeAlias, TypeVar
10
10
 
11
- from . import _utils
12
11
  from .exceptions import ModelBehaviorError, UserError
13
12
  from .items import RunItem, TResponseInputItem
14
13
  from .run_context import RunContextWrapper, TContext
15
14
  from .strict_schema import ensure_strict_json_schema
16
15
  from .tracing.spans import SpanError
16
+ from .util import _error_tracing, _json, _transforms
17
17
 
18
18
  if TYPE_CHECKING:
19
19
  from .agent import Agent
@@ -104,7 +104,7 @@ class Handoff(Generic[TContext]):
104
104
 
105
105
  @classmethod
106
106
  def default_tool_name(cls, agent: Agent[Any]) -> str:
107
- return _utils.transform_string_function_style(f"transfer_to_{agent.name}")
107
+ return _transforms.transform_string_function_style(f"transfer_to_{agent.name}")
108
108
 
109
109
  @classmethod
110
110
  def default_tool_description(cls, agent: Agent[Any]) -> str:
@@ -192,7 +192,7 @@ def handoff(
192
192
  ) -> Agent[Any]:
193
193
  if input_type is not None and type_adapter is not None:
194
194
  if input_json is None:
195
- _utils.attach_error_to_current_span(
195
+ _error_tracing.attach_error_to_current_span(
196
196
  SpanError(
197
197
  message="Handoff function expected non-null input, but got None",
198
198
  data={"details": "input_json is None"},
@@ -200,7 +200,7 @@ def handoff(
200
200
  )
201
201
  raise ModelBehaviorError("Handoff function expected non-null input, but got None")
202
202
 
203
- validated_input = _utils.validate_json(
203
+ validated_input = _json.validate_json(
204
204
  json_str=input_json,
205
205
  type_adapter=type_adapter,
206
206
  partial=False,
agents/items.py CHANGED
@@ -129,8 +129,10 @@ class ToolCallOutputItem(RunItemBase[Union[FunctionCallOutput, ComputerCallOutpu
129
129
  raw_item: FunctionCallOutput | ComputerCallOutput
130
130
  """The raw item from the model."""
131
131
 
132
- output: str
133
- """The output of the tool call."""
132
+ output: Any
133
+ """The output of the tool call. This is whatever the tool call returned; the `raw_item`
134
+ contains a string representation of the output.
135
+ """
134
136
 
135
137
  type: Literal["tool_call_output_item"] = "tool_call_output_item"
136
138
 
agents/model_settings.py CHANGED
@@ -10,15 +10,34 @@ class ModelSettings:
10
10
 
11
11
  This class holds optional model configuration parameters (e.g. temperature,
12
12
  top_p, penalties, truncation, etc.).
13
+
14
+ Not all models/providers support all of these parameters, so please check the API documentation
15
+ for the specific model and provider you are using.
13
16
  """
14
17
 
15
18
  temperature: float | None = None
19
+ """The temperature to use when calling the model."""
20
+
16
21
  top_p: float | None = None
22
+ """The top_p to use when calling the model."""
23
+
17
24
  frequency_penalty: float | None = None
25
+ """The frequency penalty to use when calling the model."""
26
+
18
27
  presence_penalty: float | None = None
28
+ """The presence penalty to use when calling the model."""
29
+
19
30
  tool_choice: Literal["auto", "required", "none"] | str | None = None
31
+ """The tool choice to use when calling the model."""
32
+
20
33
  parallel_tool_calls: bool | None = False
34
+ """Whether to use parallel tool calls when calling the model."""
35
+
21
36
  truncation: Literal["auto", "disabled"] | None = None
37
+ """The truncation strategy to use when calling the model."""
38
+
39
+ max_tokens: int | None = None
40
+ """The maximum number of output tokens to generate."""
22
41
 
23
42
  def resolve(self, override: ModelSettings | None) -> ModelSettings:
24
43
  """Produce a new ModelSettings by overlaying any non-None values from the
@@ -33,4 +52,5 @@ class ModelSettings:
33
52
  tool_choice=override.tool_choice or self.tool_choice,
34
53
  parallel_tool_calls=override.parallel_tool_calls or self.parallel_tool_calls,
35
54
  truncation=override.truncation or self.truncation,
55
+ max_tokens=override.max_tokens or self.max_tokens,
36
56
  )