openai-agents 0.0.4__py3-none-any.whl → 0.0.6__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 +22 -5
- agents/_run_impl.py +101 -22
- agents/agent.py +55 -7
- agents/agent_output.py +4 -4
- agents/function_schema.py +4 -0
- agents/guardrail.py +1 -1
- agents/handoffs.py +4 -4
- agents/items.py +4 -2
- agents/models/openai_chatcompletions.py +6 -1
- agents/models/openai_provider.py +13 -0
- agents/result.py +7 -0
- agents/run.py +10 -10
- agents/tool.py +34 -10
- agents/tracing/__init__.py +12 -0
- agents/tracing/create.py +122 -2
- agents/tracing/processors.py +2 -2
- agents/tracing/scope.py +1 -1
- agents/tracing/setup.py +1 -1
- agents/tracing/span_data.py +98 -2
- agents/tracing/spans.py +1 -1
- agents/tracing/traces.py +1 -1
- agents/tracing/util.py +5 -0
- 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
- agents/voice/__init__.py +51 -0
- agents/voice/events.py +47 -0
- agents/voice/exceptions.py +8 -0
- agents/voice/imports.py +11 -0
- agents/voice/input.py +88 -0
- agents/voice/model.py +193 -0
- agents/voice/models/__init__.py +0 -0
- agents/voice/models/openai_model_provider.py +97 -0
- agents/voice/models/openai_stt.py +457 -0
- agents/voice/models/openai_tts.py +54 -0
- agents/voice/pipeline.py +151 -0
- agents/voice/pipeline_config.py +46 -0
- agents/voice/result.py +287 -0
- agents/voice/utils.py +37 -0
- agents/voice/workflow.py +93 -0
- {openai_agents-0.0.4.dist-info → openai_agents-0.0.6.dist-info}/METADATA +9 -4
- openai_agents-0.0.6.dist-info/RECORD +70 -0
- agents/_utils.py +0 -61
- openai_agents-0.0.4.dist-info/RECORD +0 -49
- {openai_agents-0.0.4.dist-info → openai_agents-0.0.6.dist-info}/WHEEL +0 -0
- {openai_agents-0.0.4.dist-info → openai_agents-0.0.6.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,
|
|
@@ -72,7 +73,11 @@ from .tracing import (
|
|
|
72
73
|
Span,
|
|
73
74
|
SpanData,
|
|
74
75
|
SpanError,
|
|
76
|
+
SpeechGroupSpanData,
|
|
77
|
+
SpeechSpanData,
|
|
75
78
|
Trace,
|
|
79
|
+
TracingProcessor,
|
|
80
|
+
TranscriptionSpanData,
|
|
76
81
|
add_trace_processor,
|
|
77
82
|
agent_span,
|
|
78
83
|
custom_span,
|
|
@@ -87,7 +92,10 @@ from .tracing import (
|
|
|
87
92
|
set_trace_processors,
|
|
88
93
|
set_tracing_disabled,
|
|
89
94
|
set_tracing_export_api_key,
|
|
95
|
+
speech_group_span,
|
|
96
|
+
speech_span,
|
|
90
97
|
trace,
|
|
98
|
+
transcription_span,
|
|
91
99
|
)
|
|
92
100
|
from .usage import Usage
|
|
93
101
|
|
|
@@ -129,14 +137,15 @@ def set_default_openai_api(api: Literal["chat_completions", "responses"]) -> Non
|
|
|
129
137
|
|
|
130
138
|
def enable_verbose_stdout_logging():
|
|
131
139
|
"""Enables verbose logging to stdout. This is useful for debugging."""
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
logger.addHandler(logging.StreamHandler(sys.stdout))
|
|
140
|
+
logger = logging.getLogger("openai.agents")
|
|
141
|
+
logger.setLevel(logging.DEBUG)
|
|
142
|
+
logger.addHandler(logging.StreamHandler(sys.stdout))
|
|
136
143
|
|
|
137
144
|
|
|
138
145
|
__all__ = [
|
|
139
146
|
"Agent",
|
|
147
|
+
"ToolsToFinalOutputFunction",
|
|
148
|
+
"ToolsToFinalOutputResult",
|
|
140
149
|
"Runner",
|
|
141
150
|
"Model",
|
|
142
151
|
"ModelProvider",
|
|
@@ -190,6 +199,7 @@ __all__ = [
|
|
|
190
199
|
"AgentUpdatedStreamEvent",
|
|
191
200
|
"StreamEvent",
|
|
192
201
|
"FunctionTool",
|
|
202
|
+
"FunctionToolResult",
|
|
193
203
|
"ComputerTool",
|
|
194
204
|
"FileSearchTool",
|
|
195
205
|
"Tool",
|
|
@@ -207,8 +217,12 @@ __all__ = [
|
|
|
207
217
|
"handoff_span",
|
|
208
218
|
"set_trace_processors",
|
|
209
219
|
"set_tracing_disabled",
|
|
220
|
+
"speech_group_span",
|
|
221
|
+
"transcription_span",
|
|
222
|
+
"speech_span",
|
|
210
223
|
"trace",
|
|
211
224
|
"Trace",
|
|
225
|
+
"TracingProcessor",
|
|
212
226
|
"SpanError",
|
|
213
227
|
"Span",
|
|
214
228
|
"SpanData",
|
|
@@ -218,6 +232,9 @@ __all__ = [
|
|
|
218
232
|
"GenerationSpanData",
|
|
219
233
|
"GuardrailSpanData",
|
|
220
234
|
"HandoffSpanData",
|
|
235
|
+
"SpeechGroupSpanData",
|
|
236
|
+
"SpeechSpanData",
|
|
237
|
+
"TranscriptionSpanData",
|
|
221
238
|
"set_default_openai_key",
|
|
222
239
|
"set_default_openai_client",
|
|
223
240
|
"set_default_openai_api",
|
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:
|
|
@@ -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
|
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
|
|
|
@@ -54,7 +54,7 @@ from openai.types.responses import (
|
|
|
54
54
|
ResponseUsage,
|
|
55
55
|
)
|
|
56
56
|
from openai.types.responses.response_input_param import FunctionCallOutput, ItemReference, Message
|
|
57
|
-
from openai.types.responses.response_usage import OutputTokensDetails
|
|
57
|
+
from openai.types.responses.response_usage import InputTokensDetails, OutputTokensDetails
|
|
58
58
|
|
|
59
59
|
from .. import _debug
|
|
60
60
|
from ..agent_output import AgentOutputSchema
|
|
@@ -420,6 +420,11 @@ class OpenAIChatCompletionsModel(Model):
|
|
|
420
420
|
and usage.completion_tokens_details.reasoning_tokens
|
|
421
421
|
else 0
|
|
422
422
|
),
|
|
423
|
+
input_tokens_details=InputTokensDetails(
|
|
424
|
+
cached_tokens=usage.prompt_tokens_details.cached_tokens
|
|
425
|
+
if usage.prompt_tokens_details and usage.prompt_tokens_details.cached_tokens
|
|
426
|
+
else 0
|
|
427
|
+
),
|
|
423
428
|
)
|
|
424
429
|
if usage
|
|
425
430
|
else None
|
agents/models/openai_provider.py
CHANGED
|
@@ -34,6 +34,19 @@ class OpenAIProvider(ModelProvider):
|
|
|
34
34
|
project: str | None = None,
|
|
35
35
|
use_responses: bool | None = None,
|
|
36
36
|
) -> None:
|
|
37
|
+
"""Create a new OpenAI provider.
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
api_key: The API key to use for the OpenAI client. If not provided, we will use the
|
|
41
|
+
default API key.
|
|
42
|
+
base_url: The base URL to use for the OpenAI client. If not provided, we will use the
|
|
43
|
+
default base URL.
|
|
44
|
+
openai_client: An optional OpenAI client to use. If not provided, we will create a new
|
|
45
|
+
OpenAI client using the api_key and base_url.
|
|
46
|
+
organization: The organization to use for the OpenAI client.
|
|
47
|
+
project: The project to use for the OpenAI client.
|
|
48
|
+
use_responses: Whether to use the OpenAI responses API.
|
|
49
|
+
"""
|
|
37
50
|
if openai_client is not None:
|
|
38
51
|
assert api_key is None and base_url is None, (
|
|
39
52
|
"Don't provide api_key or base_url if you provide openai_client"
|
agents/result.py
CHANGED
|
@@ -17,6 +17,7 @@ from .items import ItemHelpers, ModelResponse, RunItem, TResponseInputItem
|
|
|
17
17
|
from .logger import logger
|
|
18
18
|
from .stream_events import StreamEvent
|
|
19
19
|
from .tracing import Trace
|
|
20
|
+
from .util._pretty_print import pretty_print_result, pretty_print_run_result_streaming
|
|
20
21
|
|
|
21
22
|
if TYPE_CHECKING:
|
|
22
23
|
from ._run_impl import QueueCompleteSentinel
|
|
@@ -89,6 +90,9 @@ class RunResult(RunResultBase):
|
|
|
89
90
|
"""The last agent that was run."""
|
|
90
91
|
return self._last_agent
|
|
91
92
|
|
|
93
|
+
def __str__(self) -> str:
|
|
94
|
+
return pretty_print_result(self)
|
|
95
|
+
|
|
92
96
|
|
|
93
97
|
@dataclass
|
|
94
98
|
class RunResultStreaming(RunResultBase):
|
|
@@ -216,3 +220,6 @@ class RunResultStreaming(RunResultBase):
|
|
|
216
220
|
|
|
217
221
|
if self._output_guardrails_task and not self._output_guardrails_task.done():
|
|
218
222
|
self._output_guardrails_task.cancel()
|
|
223
|
+
|
|
224
|
+
def __str__(self) -> str:
|
|
225
|
+
return pretty_print_run_result_streaming(self)
|