19
19
from semantic_kernel .contents .streaming_chat_message_content import StreamingChatMessageContent
20
20
from semantic_kernel .contents .streaming_text_content import StreamingTextContent
21
21
from semantic_kernel .contents .utils .author_role import AuthorRole
22
+ from semantic_kernel .functions import KernelArguments
22
23
23
24
24
25
@pytest .fixture
@@ -67,7 +68,6 @@ def mock_thread():
67
68
return thread
68
69
69
70
70
- @pytest .mark .asyncio
71
71
async def test_invoke_no_function_calls (mock_agent , mock_response , mock_chat_history , mock_thread ):
72
72
async def mock_get_response (* args , ** kwargs ):
73
73
return mock_response
@@ -89,7 +89,6 @@ async def mock_get_response(*args, **kwargs):
89
89
assert final_msg .role == AuthorRole .ASSISTANT
90
90
91
91
92
- @pytest .mark .asyncio
93
92
async def test_invoke_raises_on_failed_response (mock_agent , mock_chat_history , mock_thread ):
94
93
mock_failed_response = MagicMock (spec = Response )
95
94
mock_failed_response .status = "failed"
@@ -115,7 +114,6 @@ async def mock_get_response(*args, **kwargs):
115
114
pass
116
115
117
116
118
- @pytest .mark .asyncio
119
117
async def test_invoke_reaches_maximum_attempts (mock_agent , mock_chat_history , mock_thread ):
120
118
call_counter = 0
121
119
@@ -173,7 +171,6 @@ async def mock_get_response(*args, **kwargs):
173
171
assert messages is not None
174
172
175
173
176
- @pytest .mark .asyncio
177
174
async def test_invoke_with_function_calls (mock_agent , mock_chat_history , mock_thread ):
178
175
initial_response = MagicMock (spec = Response )
179
176
initial_response .status = "completed"
@@ -227,6 +224,138 @@ async def mock_get_response(*args, **kwargs):
227
224
assert len (messages ) == 3 , f"Expected exactly 3 messages, got { len (messages )} "
228
225
229
226
227
+ async def test_invoke_passes_kernel_arguments_to_kernel (mock_agent , mock_chat_history , mock_thread ):
228
+ # Prepare a response that triggers a function call
229
+ initial_response = MagicMock (spec = Response )
230
+ initial_response .status = "completed"
231
+ initial_response .id = "fake-response-id"
232
+ initial_response .output = [
233
+ ResponseFunctionToolCall (
234
+ id = "tool_call_id" ,
235
+ call_id = "call_id" ,
236
+ name = "test_function" ,
237
+ arguments = '{"some_arg": 123}' ,
238
+ type = "function_call" ,
239
+ )
240
+ ]
241
+ initial_response .error = None
242
+ initial_response .incomplete_details = None
243
+ initial_response .created_at = 123456
244
+ initial_response .usage = None
245
+ initial_response .role = "assistant"
246
+
247
+ final_response = MagicMock (spec = Response )
248
+ final_response .status = "completed"
249
+ final_response .id = "fake-final-response-id"
250
+ final_response .output = []
251
+ final_response .error = None
252
+ final_response .incomplete_details = None
253
+ final_response .created_at = 123456
254
+ final_response .usage = None
255
+ final_response .role = "assistant"
256
+
257
+ responses = [initial_response , final_response ]
258
+
259
+ async def mock_invoke_fc (* args , ** kwargs ):
260
+ # Assert that KernelArguments were forwarded
261
+ assert isinstance (kwargs .get ("arguments" ), KernelArguments )
262
+ assert kwargs ["arguments" ].get ("foo" ) == "bar"
263
+ return MagicMock (terminate = False )
264
+
265
+ mock_agent .kernel .invoke_function_call = MagicMock (side_effect = mock_invoke_fc )
266
+
267
+ async def mock_get_response (* args , ** kwargs ):
268
+ return responses .pop (0 )
269
+
270
+ with patch .object (ResponsesAgentThreadActions , "_get_response" , new = mock_get_response ):
271
+ args = KernelArguments (foo = "bar" )
272
+ # Run invoke and ensure no assertion fails inside mock_invoke_fc
273
+ collected = []
274
+ async for _ , msg in ResponsesAgentThreadActions .invoke (
275
+ agent = mock_agent ,
276
+ chat_history = mock_chat_history ,
277
+ thread = mock_thread ,
278
+ store_enabled = True ,
279
+ function_choice_behavior = MagicMock (maximum_auto_invoke_attempts = 1 ),
280
+ arguments = args ,
281
+ ):
282
+ collected .append (msg )
283
+ assert len (collected ) >= 2
284
+
285
+
286
+ async def test_invoke_stream_passes_kernel_arguments_to_kernel (mock_agent , mock_chat_history , mock_thread ):
287
+ class MockStream (AsyncStream [ResponseStreamEvent ]):
288
+ def __init__ (self , events ):
289
+ self ._events = events
290
+
291
+ async def __aenter__ (self ):
292
+ return self
293
+
294
+ async def __aexit__ (self , exc_type , exc_val , exc_tb ):
295
+ pass
296
+
297
+ def __aiter__ (self ):
298
+ return self
299
+
300
+ async def __anext__ (self ):
301
+ if not self ._events :
302
+ raise StopAsyncIteration
303
+ return self ._events .pop (0 )
304
+
305
+ # Event that includes a function call
306
+ mock_tool_call_event = ResponseOutputItemAddedEvent (
307
+ item = ResponseFunctionToolCall (
308
+ id = "fake-tool-call-id" ,
309
+ call_id = "fake-call-id" ,
310
+ name = "test_function" ,
311
+ arguments = '{"arg": 123}' ,
312
+ type = "function_call" ,
313
+ ),
314
+ output_index = 0 ,
315
+ type = "response.output_item.added" ,
316
+ sequence_number = 0 ,
317
+ )
318
+
319
+ mock_stream_event_end = ResponseOutputItemDoneEvent (
320
+ item = ResponseOutputMessage (
321
+ role = "assistant" ,
322
+ status = "completed" ,
323
+ id = "fake-item-id" ,
324
+ content = [ResponseOutputText (text = "Final message after tool call" , type = "output_text" , annotations = [])],
325
+ type = "message" ,
326
+ ),
327
+ output_index = 0 ,
328
+ sequence_number = 0 ,
329
+ type = "response.output_item.done" ,
330
+ )
331
+
332
+ async def mock_get_response (* args , ** kwargs ):
333
+ return MockStream ([mock_tool_call_event , mock_stream_event_end ])
334
+
335
+ async def mock_invoke_function_call (* args , ** kwargs ):
336
+ assert isinstance (kwargs .get ("arguments" ), KernelArguments )
337
+ assert kwargs ["arguments" ].get ("foo" ) == "bar"
338
+ return MagicMock (terminate = False )
339
+
340
+ mock_agent .kernel .invoke_function_call = MagicMock (side_effect = mock_invoke_function_call )
341
+
342
+ with patch .object (ResponsesAgentThreadActions , "_get_response" , new = mock_get_response ):
343
+ args = KernelArguments (foo = "bar" )
344
+ collected_stream_messages = []
345
+ async for _ in ResponsesAgentThreadActions .invoke_stream (
346
+ agent = mock_agent ,
347
+ chat_history = mock_chat_history ,
348
+ thread = mock_thread ,
349
+ store_enabled = True ,
350
+ function_choice_behavior = MagicMock (maximum_auto_invoke_attempts = 1 ),
351
+ output_messages = collected_stream_messages ,
352
+ arguments = args ,
353
+ ):
354
+ pass
355
+ # If assertions passed in mock, arguments were forwarded
356
+ assert len (collected_stream_messages ) >= 1
357
+
358
+
230
359
async def test_invoke_stream_no_function_calls (mock_agent , mock_chat_history , mock_thread ):
231
360
class MockStream (AsyncStream [ResponseStreamEvent ]):
232
361
def __init__ (self , events ):
@@ -294,7 +423,6 @@ async def mock_get_response(*args, **kwargs):
294
423
assert collected_stream_messages [0 ].role == AuthorRole .ASSISTANT
295
424
296
425
297
- @pytest .mark .asyncio
298
426
async def test_invoke_stream_with_tool_calls (mock_agent , mock_chat_history , mock_thread ):
299
427
class MockStream (AsyncStream [ResponseStreamEvent ]):
300
428
def __init__ (self , events ):
0 commit comments