openai-agents 0.1.0__py3-none-any.whl → 0.2.1__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 +5 -1
- agents/_run_impl.py +5 -1
- agents/agent.py +62 -30
- agents/agent_output.py +2 -2
- agents/function_schema.py +11 -1
- agents/guardrail.py +5 -1
- agents/handoffs.py +32 -14
- agents/lifecycle.py +26 -17
- agents/mcp/server.py +82 -11
- agents/mcp/util.py +16 -9
- agents/memory/__init__.py +3 -0
- agents/memory/session.py +369 -0
- agents/model_settings.py +15 -7
- agents/models/chatcmpl_converter.py +20 -3
- agents/models/chatcmpl_stream_handler.py +134 -43
- agents/models/openai_responses.py +12 -5
- agents/realtime/README.md +3 -0
- agents/realtime/__init__.py +177 -0
- agents/realtime/agent.py +89 -0
- agents/realtime/config.py +188 -0
- agents/realtime/events.py +216 -0
- agents/realtime/handoffs.py +165 -0
- agents/realtime/items.py +184 -0
- agents/realtime/model.py +69 -0
- agents/realtime/model_events.py +159 -0
- agents/realtime/model_inputs.py +100 -0
- agents/realtime/openai_realtime.py +670 -0
- agents/realtime/runner.py +118 -0
- agents/realtime/session.py +535 -0
- agents/run.py +106 -4
- agents/tool.py +6 -7
- agents/tool_context.py +16 -3
- agents/voice/models/openai_stt.py +1 -1
- agents/voice/pipeline.py +6 -0
- agents/voice/workflow.py +8 -0
- {openai_agents-0.1.0.dist-info → openai_agents-0.2.1.dist-info}/METADATA +121 -4
- {openai_agents-0.1.0.dist-info → openai_agents-0.2.1.dist-info}/RECORD +39 -24
- {openai_agents-0.1.0.dist-info → openai_agents-0.2.1.dist-info}/WHEEL +0 -0
- {openai_agents-0.1.0.dist-info → openai_agents-0.2.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import (
|
|
4
|
+
Any,
|
|
5
|
+
Literal,
|
|
6
|
+
Union,
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
from typing_extensions import NotRequired, TypeAlias, TypedDict
|
|
10
|
+
|
|
11
|
+
from ..guardrail import OutputGuardrail
|
|
12
|
+
from ..handoffs import Handoff
|
|
13
|
+
from ..model_settings import ToolChoice
|
|
14
|
+
from ..tool import Tool
|
|
15
|
+
|
|
16
|
+
RealtimeModelName: TypeAlias = Union[
|
|
17
|
+
Literal[
|
|
18
|
+
"gpt-4o-realtime-preview",
|
|
19
|
+
"gpt-4o-mini-realtime-preview",
|
|
20
|
+
"gpt-4o-realtime-preview-2025-06-03",
|
|
21
|
+
"gpt-4o-realtime-preview-2024-12-17",
|
|
22
|
+
"gpt-4o-realtime-preview-2024-10-01",
|
|
23
|
+
"gpt-4o-mini-realtime-preview-2024-12-17",
|
|
24
|
+
],
|
|
25
|
+
str,
|
|
26
|
+
]
|
|
27
|
+
"""The name of a realtime model."""
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
RealtimeAudioFormat: TypeAlias = Union[Literal["pcm16", "g711_ulaw", "g711_alaw"], str]
|
|
31
|
+
"""The audio format for realtime audio streams."""
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class RealtimeClientMessage(TypedDict):
|
|
35
|
+
"""A raw message to be sent to the model."""
|
|
36
|
+
|
|
37
|
+
type: str # explicitly required
|
|
38
|
+
"""The type of the message."""
|
|
39
|
+
|
|
40
|
+
other_data: NotRequired[dict[str, Any]]
|
|
41
|
+
"""Merged into the message body."""
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class RealtimeInputAudioTranscriptionConfig(TypedDict):
|
|
45
|
+
"""Configuration for audio transcription in realtime sessions."""
|
|
46
|
+
|
|
47
|
+
language: NotRequired[str]
|
|
48
|
+
"""The language code for transcription."""
|
|
49
|
+
|
|
50
|
+
model: NotRequired[Literal["gpt-4o-transcribe", "gpt-4o-mini-transcribe", "whisper-1"] | str]
|
|
51
|
+
"""The transcription model to use."""
|
|
52
|
+
|
|
53
|
+
prompt: NotRequired[str]
|
|
54
|
+
"""An optional prompt to guide transcription."""
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class RealtimeTurnDetectionConfig(TypedDict):
|
|
58
|
+
"""Turn detection config. Allows extra vendor keys if needed."""
|
|
59
|
+
|
|
60
|
+
type: NotRequired[Literal["semantic_vad", "server_vad"]]
|
|
61
|
+
"""The type of voice activity detection to use."""
|
|
62
|
+
|
|
63
|
+
create_response: NotRequired[bool]
|
|
64
|
+
"""Whether to create a response when a turn is detected."""
|
|
65
|
+
|
|
66
|
+
eagerness: NotRequired[Literal["auto", "low", "medium", "high"]]
|
|
67
|
+
"""How eagerly to detect turn boundaries."""
|
|
68
|
+
|
|
69
|
+
interrupt_response: NotRequired[bool]
|
|
70
|
+
"""Whether to allow interrupting the assistant's response."""
|
|
71
|
+
|
|
72
|
+
prefix_padding_ms: NotRequired[int]
|
|
73
|
+
"""Padding time in milliseconds before turn detection."""
|
|
74
|
+
|
|
75
|
+
silence_duration_ms: NotRequired[int]
|
|
76
|
+
"""Duration of silence in milliseconds to trigger turn detection."""
|
|
77
|
+
|
|
78
|
+
threshold: NotRequired[float]
|
|
79
|
+
"""The threshold for voice activity detection."""
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class RealtimeSessionModelSettings(TypedDict):
|
|
83
|
+
"""Model settings for a realtime model session."""
|
|
84
|
+
|
|
85
|
+
model_name: NotRequired[RealtimeModelName]
|
|
86
|
+
"""The name of the realtime model to use."""
|
|
87
|
+
|
|
88
|
+
instructions: NotRequired[str]
|
|
89
|
+
"""System instructions for the model."""
|
|
90
|
+
|
|
91
|
+
modalities: NotRequired[list[Literal["text", "audio"]]]
|
|
92
|
+
"""The modalities the model should support."""
|
|
93
|
+
|
|
94
|
+
voice: NotRequired[str]
|
|
95
|
+
"""The voice to use for audio output."""
|
|
96
|
+
|
|
97
|
+
input_audio_format: NotRequired[RealtimeAudioFormat]
|
|
98
|
+
"""The format for input audio streams."""
|
|
99
|
+
|
|
100
|
+
output_audio_format: NotRequired[RealtimeAudioFormat]
|
|
101
|
+
"""The format for output audio streams."""
|
|
102
|
+
|
|
103
|
+
input_audio_transcription: NotRequired[RealtimeInputAudioTranscriptionConfig]
|
|
104
|
+
"""Configuration for transcribing input audio."""
|
|
105
|
+
|
|
106
|
+
turn_detection: NotRequired[RealtimeTurnDetectionConfig]
|
|
107
|
+
"""Configuration for detecting conversation turns."""
|
|
108
|
+
|
|
109
|
+
tool_choice: NotRequired[ToolChoice]
|
|
110
|
+
"""How the model should choose which tools to call."""
|
|
111
|
+
|
|
112
|
+
tools: NotRequired[list[Tool]]
|
|
113
|
+
"""List of tools available to the model."""
|
|
114
|
+
|
|
115
|
+
handoffs: NotRequired[list[Handoff]]
|
|
116
|
+
"""List of handoff configurations."""
|
|
117
|
+
|
|
118
|
+
tracing: NotRequired[RealtimeModelTracingConfig | None]
|
|
119
|
+
"""Configuration for request tracing."""
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
class RealtimeGuardrailsSettings(TypedDict):
|
|
123
|
+
"""Settings for output guardrails in realtime sessions."""
|
|
124
|
+
|
|
125
|
+
debounce_text_length: NotRequired[int]
|
|
126
|
+
"""
|
|
127
|
+
The minimum number of characters to accumulate before running guardrails on transcript
|
|
128
|
+
deltas. Defaults to 100. Guardrails run every time the accumulated text reaches
|
|
129
|
+
1x, 2x, 3x, etc. times this threshold.
|
|
130
|
+
"""
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
class RealtimeModelTracingConfig(TypedDict):
|
|
134
|
+
"""Configuration for tracing in realtime model sessions."""
|
|
135
|
+
|
|
136
|
+
workflow_name: NotRequired[str]
|
|
137
|
+
"""The workflow name to use for tracing."""
|
|
138
|
+
|
|
139
|
+
group_id: NotRequired[str]
|
|
140
|
+
"""A group identifier to use for tracing, to link multiple traces together."""
|
|
141
|
+
|
|
142
|
+
metadata: NotRequired[dict[str, Any]]
|
|
143
|
+
"""Additional metadata to include with the trace."""
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
class RealtimeRunConfig(TypedDict):
|
|
147
|
+
"""Configuration for running a realtime agent session."""
|
|
148
|
+
|
|
149
|
+
model_settings: NotRequired[RealtimeSessionModelSettings]
|
|
150
|
+
"""Settings for the realtime model session."""
|
|
151
|
+
|
|
152
|
+
output_guardrails: NotRequired[list[OutputGuardrail[Any]]]
|
|
153
|
+
"""List of output guardrails to run on the agent's responses."""
|
|
154
|
+
|
|
155
|
+
guardrails_settings: NotRequired[RealtimeGuardrailsSettings]
|
|
156
|
+
"""Settings for guardrail execution."""
|
|
157
|
+
|
|
158
|
+
tracing_disabled: NotRequired[bool]
|
|
159
|
+
"""Whether tracing is disabled for this run."""
|
|
160
|
+
|
|
161
|
+
# TODO (rm) Add history audio storage config
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
class RealtimeUserInputText(TypedDict):
|
|
165
|
+
"""A text input from the user."""
|
|
166
|
+
|
|
167
|
+
type: Literal["input_text"]
|
|
168
|
+
"""The type identifier for text input."""
|
|
169
|
+
|
|
170
|
+
text: str
|
|
171
|
+
"""The text content from the user."""
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
class RealtimeUserInputMessage(TypedDict):
|
|
175
|
+
"""A message input from the user."""
|
|
176
|
+
|
|
177
|
+
type: Literal["message"]
|
|
178
|
+
"""The type identifier for message inputs."""
|
|
179
|
+
|
|
180
|
+
role: Literal["user"]
|
|
181
|
+
"""The role identifier for user messages."""
|
|
182
|
+
|
|
183
|
+
content: list[RealtimeUserInputText]
|
|
184
|
+
"""List of text content items in the message."""
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
RealtimeUserInput: TypeAlias = Union[str, RealtimeUserInputMessage]
|
|
188
|
+
"""User input that can be a string or structured message."""
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import Any, Literal, Union
|
|
5
|
+
|
|
6
|
+
from typing_extensions import TypeAlias
|
|
7
|
+
|
|
8
|
+
from ..guardrail import OutputGuardrailResult
|
|
9
|
+
from ..run_context import RunContextWrapper
|
|
10
|
+
from ..tool import Tool
|
|
11
|
+
from .agent import RealtimeAgent
|
|
12
|
+
from .items import RealtimeItem
|
|
13
|
+
from .model_events import RealtimeModelAudioEvent, RealtimeModelEvent
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass
|
|
17
|
+
class RealtimeEventInfo:
|
|
18
|
+
context: RunContextWrapper
|
|
19
|
+
"""The context for the event."""
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass
|
|
23
|
+
class RealtimeAgentStartEvent:
|
|
24
|
+
"""A new agent has started."""
|
|
25
|
+
|
|
26
|
+
agent: RealtimeAgent
|
|
27
|
+
"""The new agent."""
|
|
28
|
+
|
|
29
|
+
info: RealtimeEventInfo
|
|
30
|
+
"""Common info for all events, such as the context."""
|
|
31
|
+
|
|
32
|
+
type: Literal["agent_start"] = "agent_start"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@dataclass
|
|
36
|
+
class RealtimeAgentEndEvent:
|
|
37
|
+
"""An agent has ended."""
|
|
38
|
+
|
|
39
|
+
agent: RealtimeAgent
|
|
40
|
+
"""The agent that ended."""
|
|
41
|
+
|
|
42
|
+
info: RealtimeEventInfo
|
|
43
|
+
"""Common info for all events, such as the context."""
|
|
44
|
+
|
|
45
|
+
type: Literal["agent_end"] = "agent_end"
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@dataclass
|
|
49
|
+
class RealtimeHandoffEvent:
|
|
50
|
+
"""An agent has handed off to another agent."""
|
|
51
|
+
|
|
52
|
+
from_agent: RealtimeAgent
|
|
53
|
+
"""The agent that handed off."""
|
|
54
|
+
|
|
55
|
+
to_agent: RealtimeAgent
|
|
56
|
+
"""The agent that was handed off to."""
|
|
57
|
+
|
|
58
|
+
info: RealtimeEventInfo
|
|
59
|
+
"""Common info for all events, such as the context."""
|
|
60
|
+
|
|
61
|
+
type: Literal["handoff"] = "handoff"
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@dataclass
|
|
65
|
+
class RealtimeToolStart:
|
|
66
|
+
"""An agent is starting a tool call."""
|
|
67
|
+
|
|
68
|
+
agent: RealtimeAgent
|
|
69
|
+
"""The agent that updated."""
|
|
70
|
+
|
|
71
|
+
tool: Tool
|
|
72
|
+
|
|
73
|
+
info: RealtimeEventInfo
|
|
74
|
+
"""Common info for all events, such as the context."""
|
|
75
|
+
|
|
76
|
+
type: Literal["tool_start"] = "tool_start"
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
@dataclass
|
|
80
|
+
class RealtimeToolEnd:
|
|
81
|
+
"""An agent has ended a tool call."""
|
|
82
|
+
|
|
83
|
+
agent: RealtimeAgent
|
|
84
|
+
"""The agent that ended the tool call."""
|
|
85
|
+
|
|
86
|
+
tool: Tool
|
|
87
|
+
"""The tool that was called."""
|
|
88
|
+
|
|
89
|
+
output: Any
|
|
90
|
+
"""The output of the tool call."""
|
|
91
|
+
|
|
92
|
+
info: RealtimeEventInfo
|
|
93
|
+
"""Common info for all events, such as the context."""
|
|
94
|
+
|
|
95
|
+
type: Literal["tool_end"] = "tool_end"
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
@dataclass
|
|
99
|
+
class RealtimeRawModelEvent:
|
|
100
|
+
"""Forwards raw events from the model layer."""
|
|
101
|
+
|
|
102
|
+
data: RealtimeModelEvent
|
|
103
|
+
"""The raw data from the model layer."""
|
|
104
|
+
|
|
105
|
+
info: RealtimeEventInfo
|
|
106
|
+
"""Common info for all events, such as the context."""
|
|
107
|
+
|
|
108
|
+
type: Literal["raw_model_event"] = "raw_model_event"
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
@dataclass
|
|
112
|
+
class RealtimeAudioEnd:
|
|
113
|
+
"""Triggered when the agent stops generating audio."""
|
|
114
|
+
|
|
115
|
+
info: RealtimeEventInfo
|
|
116
|
+
"""Common info for all events, such as the context."""
|
|
117
|
+
|
|
118
|
+
type: Literal["audio_end"] = "audio_end"
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
@dataclass
|
|
122
|
+
class RealtimeAudio:
|
|
123
|
+
"""Triggered when the agent generates new audio to be played."""
|
|
124
|
+
|
|
125
|
+
audio: RealtimeModelAudioEvent
|
|
126
|
+
"""The audio event from the model layer."""
|
|
127
|
+
|
|
128
|
+
info: RealtimeEventInfo
|
|
129
|
+
"""Common info for all events, such as the context."""
|
|
130
|
+
|
|
131
|
+
type: Literal["audio"] = "audio"
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
@dataclass
|
|
135
|
+
class RealtimeAudioInterrupted:
|
|
136
|
+
"""Triggered when the agent is interrupted. Can be listened to by the user to stop audio
|
|
137
|
+
playback or give visual indicators to the user.
|
|
138
|
+
"""
|
|
139
|
+
|
|
140
|
+
info: RealtimeEventInfo
|
|
141
|
+
"""Common info for all events, such as the context."""
|
|
142
|
+
|
|
143
|
+
type: Literal["audio_interrupted"] = "audio_interrupted"
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
@dataclass
|
|
147
|
+
class RealtimeError:
|
|
148
|
+
"""An error has occurred."""
|
|
149
|
+
|
|
150
|
+
error: Any
|
|
151
|
+
"""The error that occurred."""
|
|
152
|
+
|
|
153
|
+
info: RealtimeEventInfo
|
|
154
|
+
"""Common info for all events, such as the context."""
|
|
155
|
+
|
|
156
|
+
type: Literal["error"] = "error"
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
@dataclass
|
|
160
|
+
class RealtimeHistoryUpdated:
|
|
161
|
+
"""The history has been updated. Contains the full history of the session."""
|
|
162
|
+
|
|
163
|
+
history: list[RealtimeItem]
|
|
164
|
+
"""The full history of the session."""
|
|
165
|
+
|
|
166
|
+
info: RealtimeEventInfo
|
|
167
|
+
"""Common info for all events, such as the context."""
|
|
168
|
+
|
|
169
|
+
type: Literal["history_updated"] = "history_updated"
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
@dataclass
|
|
173
|
+
class RealtimeHistoryAdded:
|
|
174
|
+
"""A new item has been added to the history."""
|
|
175
|
+
|
|
176
|
+
item: RealtimeItem
|
|
177
|
+
"""The new item that was added to the history."""
|
|
178
|
+
|
|
179
|
+
info: RealtimeEventInfo
|
|
180
|
+
"""Common info for all events, such as the context."""
|
|
181
|
+
|
|
182
|
+
type: Literal["history_added"] = "history_added"
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
@dataclass
|
|
186
|
+
class RealtimeGuardrailTripped:
|
|
187
|
+
"""A guardrail has been tripped and the agent has been interrupted."""
|
|
188
|
+
|
|
189
|
+
guardrail_results: list[OutputGuardrailResult]
|
|
190
|
+
"""The results from all triggered guardrails."""
|
|
191
|
+
|
|
192
|
+
message: str
|
|
193
|
+
"""The message that was being generated when the guardrail was triggered."""
|
|
194
|
+
|
|
195
|
+
info: RealtimeEventInfo
|
|
196
|
+
"""Common info for all events, such as the context."""
|
|
197
|
+
|
|
198
|
+
type: Literal["guardrail_tripped"] = "guardrail_tripped"
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
RealtimeSessionEvent: TypeAlias = Union[
|
|
202
|
+
RealtimeAgentStartEvent,
|
|
203
|
+
RealtimeAgentEndEvent,
|
|
204
|
+
RealtimeHandoffEvent,
|
|
205
|
+
RealtimeToolStart,
|
|
206
|
+
RealtimeToolEnd,
|
|
207
|
+
RealtimeRawModelEvent,
|
|
208
|
+
RealtimeAudioEnd,
|
|
209
|
+
RealtimeAudio,
|
|
210
|
+
RealtimeAudioInterrupted,
|
|
211
|
+
RealtimeError,
|
|
212
|
+
RealtimeHistoryUpdated,
|
|
213
|
+
RealtimeHistoryAdded,
|
|
214
|
+
RealtimeGuardrailTripped,
|
|
215
|
+
]
|
|
216
|
+
"""An event emitted by the realtime session."""
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import inspect
|
|
4
|
+
from typing import TYPE_CHECKING, Any, Callable, cast, overload
|
|
5
|
+
|
|
6
|
+
from pydantic import TypeAdapter
|
|
7
|
+
from typing_extensions import TypeVar
|
|
8
|
+
|
|
9
|
+
from ..exceptions import ModelBehaviorError, UserError
|
|
10
|
+
from ..handoffs import Handoff
|
|
11
|
+
from ..run_context import RunContextWrapper, TContext
|
|
12
|
+
from ..strict_schema import ensure_strict_json_schema
|
|
13
|
+
from ..tracing.spans import SpanError
|
|
14
|
+
from ..util import _error_tracing, _json
|
|
15
|
+
from ..util._types import MaybeAwaitable
|
|
16
|
+
|
|
17
|
+
if TYPE_CHECKING:
|
|
18
|
+
from ..agent import AgentBase
|
|
19
|
+
from . import RealtimeAgent
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
# The handoff input type is the type of data passed when the agent is called via a handoff.
|
|
23
|
+
THandoffInput = TypeVar("THandoffInput", default=Any)
|
|
24
|
+
|
|
25
|
+
OnHandoffWithInput = Callable[[RunContextWrapper[Any], THandoffInput], Any]
|
|
26
|
+
OnHandoffWithoutInput = Callable[[RunContextWrapper[Any]], Any]
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@overload
|
|
30
|
+
def realtime_handoff(
|
|
31
|
+
agent: RealtimeAgent[TContext],
|
|
32
|
+
*,
|
|
33
|
+
tool_name_override: str | None = None,
|
|
34
|
+
tool_description_override: str | None = None,
|
|
35
|
+
is_enabled: bool
|
|
36
|
+
| Callable[[RunContextWrapper[Any], RealtimeAgent[Any]], MaybeAwaitable[bool]] = True,
|
|
37
|
+
) -> Handoff[TContext, RealtimeAgent[TContext]]: ...
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@overload
|
|
41
|
+
def realtime_handoff(
|
|
42
|
+
agent: RealtimeAgent[TContext],
|
|
43
|
+
*,
|
|
44
|
+
on_handoff: OnHandoffWithInput[THandoffInput],
|
|
45
|
+
input_type: type[THandoffInput],
|
|
46
|
+
tool_description_override: str | None = None,
|
|
47
|
+
tool_name_override: str | None = None,
|
|
48
|
+
is_enabled: bool
|
|
49
|
+
| Callable[[RunContextWrapper[Any], RealtimeAgent[Any]], MaybeAwaitable[bool]] = True,
|
|
50
|
+
) -> Handoff[TContext, RealtimeAgent[TContext]]: ...
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@overload
|
|
54
|
+
def realtime_handoff(
|
|
55
|
+
agent: RealtimeAgent[TContext],
|
|
56
|
+
*,
|
|
57
|
+
on_handoff: OnHandoffWithoutInput,
|
|
58
|
+
tool_description_override: str | None = None,
|
|
59
|
+
tool_name_override: str | None = None,
|
|
60
|
+
is_enabled: bool
|
|
61
|
+
| Callable[[RunContextWrapper[Any], RealtimeAgent[Any]], MaybeAwaitable[bool]] = True,
|
|
62
|
+
) -> Handoff[TContext, RealtimeAgent[TContext]]: ...
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def realtime_handoff(
|
|
66
|
+
agent: RealtimeAgent[TContext],
|
|
67
|
+
tool_name_override: str | None = None,
|
|
68
|
+
tool_description_override: str | None = None,
|
|
69
|
+
on_handoff: OnHandoffWithInput[THandoffInput] | OnHandoffWithoutInput | None = None,
|
|
70
|
+
input_type: type[THandoffInput] | None = None,
|
|
71
|
+
is_enabled: bool
|
|
72
|
+
| Callable[[RunContextWrapper[Any], RealtimeAgent[Any]], MaybeAwaitable[bool]] = True,
|
|
73
|
+
) -> Handoff[TContext, RealtimeAgent[TContext]]:
|
|
74
|
+
"""Create a handoff from a RealtimeAgent.
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
agent: The RealtimeAgent to handoff to, or a function that returns a RealtimeAgent.
|
|
78
|
+
tool_name_override: Optional override for the name of the tool that represents the handoff.
|
|
79
|
+
tool_description_override: Optional override for the description of the tool that
|
|
80
|
+
represents the handoff.
|
|
81
|
+
on_handoff: A function that runs when the handoff is invoked.
|
|
82
|
+
input_type: the type of the input to the handoff. If provided, the input will be validated
|
|
83
|
+
against this type. Only relevant if you pass a function that takes an input.
|
|
84
|
+
is_enabled: Whether the handoff is enabled. Can be a bool or a callable that takes the run
|
|
85
|
+
context and agent and returns whether the handoff is enabled. Disabled handoffs are
|
|
86
|
+
hidden from the LLM at runtime.
|
|
87
|
+
|
|
88
|
+
Note: input_filter is not supported for RealtimeAgent handoffs.
|
|
89
|
+
"""
|
|
90
|
+
assert (on_handoff and input_type) or not (on_handoff and input_type), (
|
|
91
|
+
"You must provide either both on_handoff and input_type, or neither"
|
|
92
|
+
)
|
|
93
|
+
type_adapter: TypeAdapter[Any] | None
|
|
94
|
+
if input_type is not None:
|
|
95
|
+
assert callable(on_handoff), "on_handoff must be callable"
|
|
96
|
+
sig = inspect.signature(on_handoff)
|
|
97
|
+
if len(sig.parameters) != 2:
|
|
98
|
+
raise UserError("on_handoff must take two arguments: context and input")
|
|
99
|
+
|
|
100
|
+
type_adapter = TypeAdapter(input_type)
|
|
101
|
+
input_json_schema = type_adapter.json_schema()
|
|
102
|
+
else:
|
|
103
|
+
type_adapter = None
|
|
104
|
+
input_json_schema = {}
|
|
105
|
+
if on_handoff is not None:
|
|
106
|
+
sig = inspect.signature(on_handoff)
|
|
107
|
+
if len(sig.parameters) != 1:
|
|
108
|
+
raise UserError("on_handoff must take one argument: context")
|
|
109
|
+
|
|
110
|
+
async def _invoke_handoff(
|
|
111
|
+
ctx: RunContextWrapper[Any], input_json: str | None = None
|
|
112
|
+
) -> RealtimeAgent[TContext]:
|
|
113
|
+
if input_type is not None and type_adapter is not None:
|
|
114
|
+
if input_json is None:
|
|
115
|
+
_error_tracing.attach_error_to_current_span(
|
|
116
|
+
SpanError(
|
|
117
|
+
message="Handoff function expected non-null input, but got None",
|
|
118
|
+
data={"details": "input_json is None"},
|
|
119
|
+
)
|
|
120
|
+
)
|
|
121
|
+
raise ModelBehaviorError("Handoff function expected non-null input, but got None")
|
|
122
|
+
|
|
123
|
+
validated_input = _json.validate_json(
|
|
124
|
+
json_str=input_json,
|
|
125
|
+
type_adapter=type_adapter,
|
|
126
|
+
partial=False,
|
|
127
|
+
)
|
|
128
|
+
input_func = cast(OnHandoffWithInput[THandoffInput], on_handoff)
|
|
129
|
+
if inspect.iscoroutinefunction(input_func):
|
|
130
|
+
await input_func(ctx, validated_input)
|
|
131
|
+
else:
|
|
132
|
+
input_func(ctx, validated_input)
|
|
133
|
+
elif on_handoff is not None:
|
|
134
|
+
no_input_func = cast(OnHandoffWithoutInput, on_handoff)
|
|
135
|
+
if inspect.iscoroutinefunction(no_input_func):
|
|
136
|
+
await no_input_func(ctx)
|
|
137
|
+
else:
|
|
138
|
+
no_input_func(ctx)
|
|
139
|
+
|
|
140
|
+
return agent
|
|
141
|
+
|
|
142
|
+
tool_name = tool_name_override or Handoff.default_tool_name(agent)
|
|
143
|
+
tool_description = tool_description_override or Handoff.default_tool_description(agent)
|
|
144
|
+
|
|
145
|
+
# Always ensure the input JSON schema is in strict mode
|
|
146
|
+
# If there is a need, we can make this configurable in the future
|
|
147
|
+
input_json_schema = ensure_strict_json_schema(input_json_schema)
|
|
148
|
+
|
|
149
|
+
async def _is_enabled(ctx: RunContextWrapper[Any], agent_base: AgentBase[Any]) -> bool:
|
|
150
|
+
assert callable(is_enabled), "is_enabled must be non-null here"
|
|
151
|
+
assert isinstance(agent_base, RealtimeAgent), "Can't handoff to a non-RealtimeAgent"
|
|
152
|
+
result = is_enabled(ctx, agent_base)
|
|
153
|
+
if inspect.isawaitable(result):
|
|
154
|
+
return await result
|
|
155
|
+
return result
|
|
156
|
+
|
|
157
|
+
return Handoff(
|
|
158
|
+
tool_name=tool_name,
|
|
159
|
+
tool_description=tool_description,
|
|
160
|
+
input_json_schema=input_json_schema,
|
|
161
|
+
on_invoke_handoff=_invoke_handoff,
|
|
162
|
+
input_filter=None, # Not supported for RealtimeAgent handoffs
|
|
163
|
+
agent_name=agent.name,
|
|
164
|
+
is_enabled=_is_enabled if callable(is_enabled) else is_enabled,
|
|
165
|
+
)
|