1
1
from unittest .mock import AsyncMock , Mock , patch
2
2
3
3
import pytest
4
- from inline_snapshot import snapshot
5
4
6
5
from agents .realtime .agent import RealtimeAgent
7
6
from agents .realtime .config import RealtimeRunConfig , RealtimeSessionModelSettings
8
7
from agents .realtime .model import RealtimeModel , RealtimeModelConfig
9
8
from agents .realtime .runner import RealtimeRunner
10
9
from agents .realtime .session import RealtimeSession
10
+ from agents .tool import function_tool
11
11
12
12
13
13
class MockRealtimeModel (RealtimeModel ):
14
+ def __init__ (self ):
15
+ self .connect_args = None
16
+
14
17
async def connect (self , options = None ):
15
- pass
18
+ self . connect_args = options
16
19
17
20
def add_listener (self , listener ):
18
21
pass
@@ -53,7 +56,9 @@ def mock_model():
53
56
54
57
55
58
@pytest .mark .asyncio
56
- async def test_run_creates_session_with_no_settings (mock_agent , mock_model ):
59
+ async def test_run_creates_session_with_no_settings (
60
+ mock_agent : Mock , mock_model : MockRealtimeModel
61
+ ):
57
62
"""Test that run() creates a session correctly if no settings are provided"""
58
63
runner = RealtimeRunner (mock_agent , model = mock_model )
59
64
@@ -71,22 +76,17 @@ async def test_run_creates_session_with_no_settings(mock_agent, mock_model):
71
76
assert call_args [1 ]["agent" ] == mock_agent
72
77
assert call_args [1 ]["context" ] is None
73
78
74
- # Verify model_config contains expected settings from agent
79
+ # With no settings provided, model_config should be None
75
80
model_config = call_args [1 ]["model_config" ]
76
- assert model_config == snapshot (
77
- {
78
- "initial_model_settings" : {
79
- "instructions" : "Test instructions" ,
80
- "tools" : [{"type" : "function" , "name" : "test_tool" }],
81
- }
82
- }
83
- )
81
+ assert model_config is None
84
82
85
83
assert session == mock_session
86
84
87
85
88
86
@pytest .mark .asyncio
89
- async def test_run_creates_session_with_settings_only_in_init (mock_agent , mock_model ):
87
+ async def test_run_creates_session_with_settings_only_in_init (
88
+ mock_agent : Mock , mock_model : MockRealtimeModel
89
+ ):
90
90
"""Test that it creates a session with the right settings if they are provided only in init"""
91
91
config = RealtimeRunConfig (
92
92
model_settings = RealtimeSessionModelSettings (model_name = "gpt-4o-realtime" , voice = "nova" )
@@ -99,28 +99,19 @@ async def test_run_creates_session_with_settings_only_in_init(mock_agent, mock_m
99
99
100
100
_ = await runner .run ()
101
101
102
- # Verify session was created with config overrides
102
+ # Verify session was created - runner no longer processes settings
103
103
call_args = mock_session_class .call_args
104
104
model_config = call_args [1 ]["model_config" ]
105
105
106
- # Should have agent settings plus config overrides
107
- assert model_config == snapshot (
108
- {
109
- "initial_model_settings" : {
110
- "instructions" : "Test instructions" ,
111
- "tools" : [{"type" : "function" , "name" : "test_tool" }],
112
- "model_name" : "gpt-4o-realtime" ,
113
- "voice" : "nova" ,
114
- }
115
- }
116
- )
106
+ # Runner should pass None for model_config when none provided to run()
107
+ assert model_config is None
117
108
118
109
119
110
@pytest .mark .asyncio
120
111
async def test_run_creates_session_with_settings_in_both_init_and_run_overrides (
121
- mock_agent , mock_model
112
+ mock_agent : Mock , mock_model : MockRealtimeModel
122
113
):
123
- """Test settings in both init and run() - init should override run() """
114
+ """Test settings provided in run() parameter are passed through """
124
115
init_config = RealtimeRunConfig (
125
116
model_settings = RealtimeSessionModelSettings (model_name = "gpt-4o-realtime" , voice = "nova" )
126
117
)
@@ -138,26 +129,18 @@ async def test_run_creates_session_with_settings_in_both_init_and_run_overrides(
138
129
139
130
_ = await runner .run (model_config = run_model_config )
140
131
141
- # Verify run() settings override init settings
132
+ # Verify run() model_config is passed through as-is
142
133
call_args = mock_session_class .call_args
143
134
model_config = call_args [1 ]["model_config" ]
144
135
145
- # Should have agent settings, then init config, then run config overrides
146
- assert model_config == snapshot (
147
- {
148
- "initial_model_settings" : {
149
- "voice" : "nova" ,
150
- "input_audio_format" : "pcm16" ,
151
- "instructions" : "Test instructions" ,
152
- "tools" : [{"type" : "function" , "name" : "test_tool" }],
153
- "model_name" : "gpt-4o-realtime" ,
154
- }
155
- }
156
- )
136
+ # Runner should pass the model_config from run() parameter directly
137
+ assert model_config == run_model_config
157
138
158
139
159
140
@pytest .mark .asyncio
160
- async def test_run_creates_session_with_settings_only_in_run (mock_agent , mock_model ):
141
+ async def test_run_creates_session_with_settings_only_in_run (
142
+ mock_agent : Mock , mock_model : MockRealtimeModel
143
+ ):
161
144
"""Test settings provided only in run()"""
162
145
runner = RealtimeRunner (mock_agent , model = mock_model )
163
146
@@ -173,26 +156,16 @@ async def test_run_creates_session_with_settings_only_in_run(mock_agent, mock_mo
173
156
174
157
_ = await runner .run (model_config = run_model_config )
175
158
176
- # Verify run() settings are applied
159
+ # Verify run() model_config is passed through as-is
177
160
call_args = mock_session_class .call_args
178
161
model_config = call_args [1 ]["model_config" ]
179
162
180
- # Should have agent settings plus run() settings
181
- assert model_config == snapshot (
182
- {
183
- "initial_model_settings" : {
184
- "model_name" : "gpt-4o-realtime-preview" ,
185
- "voice" : "shimmer" ,
186
- "modalities" : ["text" , "audio" ],
187
- "instructions" : "Test instructions" ,
188
- "tools" : [{"type" : "function" , "name" : "test_tool" }],
189
- }
190
- }
191
- )
163
+ # Runner should pass the model_config from run() parameter directly
164
+ assert model_config == run_model_config
192
165
193
166
194
167
@pytest .mark .asyncio
195
- async def test_run_with_context_parameter (mock_agent , mock_model ):
168
+ async def test_run_with_context_parameter (mock_agent : Mock , mock_model : MockRealtimeModel ):
196
169
"""Test that context parameter is passed through to session"""
197
170
runner = RealtimeRunner (mock_agent , model = mock_model )
198
171
test_context = {"user_id" : "test123" }
@@ -208,17 +181,69 @@ async def test_run_with_context_parameter(mock_agent, mock_model):
208
181
209
182
210
183
@pytest .mark .asyncio
211
- async def test_get_model_settings_with_none_values (mock_model ):
212
- """Test _get_model_settings handles None values from agent properly """
184
+ async def test_run_with_none_values_from_agent_does_not_crash (mock_model : MockRealtimeModel ):
185
+ """Test that runner handles agents with None values without crashing """
213
186
agent = Mock (spec = RealtimeAgent )
214
187
agent .get_system_prompt = AsyncMock (return_value = None )
215
188
agent .get_all_tools = AsyncMock (return_value = None )
216
189
217
190
runner = RealtimeRunner (agent , model = mock_model )
218
191
219
- with patch ("agents.realtime.runner.RealtimeSession" ):
220
- await runner .run ()
192
+ with patch ("agents.realtime.runner.RealtimeSession" ) as mock_session_class :
193
+ mock_session = Mock (spec = RealtimeSession )
194
+ mock_session_class .return_value = mock_session
195
+
196
+ session = await runner .run ()
197
+
198
+ # Should not crash and return session
199
+ assert session == mock_session
200
+ # Runner no longer calls agent methods directly - session does that
201
+ agent .get_system_prompt .assert_not_called ()
202
+ agent .get_all_tools .assert_not_called ()
203
+
204
+
205
+ @pytest .mark .asyncio
206
+ async def test_tool_and_handoffs_are_correct (mock_model : MockRealtimeModel ):
207
+ @function_tool
208
+ def tool_one ():
209
+ return "result_one"
210
+
211
+ agent_1 = RealtimeAgent (
212
+ name = "one" ,
213
+ instructions = "instr_one" ,
214
+ )
215
+ agent_2 = RealtimeAgent (
216
+ name = "two" ,
217
+ instructions = "instr_two" ,
218
+ tools = [tool_one ],
219
+ handoffs = [agent_1 ],
220
+ )
221
+
222
+ session = RealtimeSession (
223
+ model = mock_model ,
224
+ agent = agent_2 ,
225
+ context = None ,
226
+ model_config = None ,
227
+ run_config = None ,
228
+ )
229
+
230
+ async with session :
231
+ pass
221
232
222
- # Should not crash and agent methods should be called
223
- agent .get_system_prompt .assert_called_once ()
224
- agent .get_all_tools .assert_called_once ()
233
+ # Assert that the model.connect() was called with the correct settings
234
+ connect_args = mock_model .connect_args
235
+ assert connect_args is not None
236
+ assert isinstance (connect_args , dict )
237
+ initial_model_settings = connect_args ["initial_model_settings" ]
238
+ assert initial_model_settings is not None
239
+ assert isinstance (initial_model_settings , dict )
240
+ assert initial_model_settings ["instructions" ] == "instr_two"
241
+ assert len (initial_model_settings ["tools" ]) == 1
242
+ tool = initial_model_settings ["tools" ][0 ]
243
+ assert tool .name == "tool_one"
244
+
245
+ handoffs = initial_model_settings ["handoffs" ]
246
+ assert len (handoffs ) == 1
247
+ handoff = handoffs [0 ]
248
+ assert handoff .tool_name == "transfer_to_one"
249
+ assert handoff .agent_name == "one"
0 commit comments