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 +20 -9
- agents/_config.py +6 -3
- agents/_run_impl.py +102 -23
- agents/agent.py +55 -7
- agents/agent_output.py +4 -4
- agents/function_schema.py +4 -0
- agents/guardrail.py +2 -2
- agents/handoffs.py +4 -4
- agents/items.py +4 -2
- agents/model_settings.py +20 -0
- agents/models/openai_chatcompletions.py +32 -1
- agents/models/openai_provider.py +24 -11
- agents/models/openai_responses.py +4 -2
- agents/result.py +7 -2
- agents/run.py +10 -10
- agents/tool.py +34 -12
- agents/tracing/create.py +1 -1
- agents/tracing/processors.py +3 -6
- agents/tracing/scope.py +1 -1
- agents/tracing/setup.py +1 -1
- agents/tracing/span_data.py +2 -2
- agents/tracing/spans.py +1 -1
- agents/tracing/traces.py +1 -1
- agents/util/__init__.py +0 -0
- agents/util/_coro.py +2 -0
- agents/util/_error_tracing.py +16 -0
- agents/util/_json.py +31 -0
- agents/util/_pretty_print.py +56 -0
- agents/util/_transforms.py +11 -0
- agents/util/_types.py +7 -0
- {openai_agents-0.0.3.dist-info → openai_agents-0.0.5.dist-info}/METADATA +9 -7
- openai_agents-0.0.5.dist-info/RECORD +55 -0
- agents/_utils.py +0 -61
- openai_agents-0.0.3.dist-info/RECORD +0 -49
- {openai_agents-0.0.3.dist-info → openai_agents-0.0.5.dist-info}/WHEEL +0 -0
- {openai_agents-0.0.3.dist-info → openai_agents-0.0.5.dist-info}/licenses/LICENSE +0 -0
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
|
|
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
|
-
|
|
127
|
-
|
|
128
|
-
|
|
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
|
|
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
|
-
#
|
|
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
|
-
|
|
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
|
-
|
|
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[
|
|
392
|
+
) -> list[FunctionToolResult]:
|
|
359
393
|
async def run_single_tool(
|
|
360
394
|
func_tool: FunctionTool, tool_call: ResponseFunctionToolCall
|
|
361
|
-
) ->
|
|
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
|
|
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
|
|
415
|
+
else _coro.noop_coroutine()
|
|
382
416
|
),
|
|
383
417
|
)
|
|
384
418
|
except Exception as e:
|
|
385
|
-
|
|
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
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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 `
|
|
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
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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 =
|
|
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:
|
|
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
|
)
|