Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,7 @@ async def invoke(
kernel.invoke_function_call(
function_call=function_call,
chat_history=override_history,
arguments=kwargs.get("arguments"),
arguments=arguments,
execution_settings=None,
function_call_count=fc_count,
request_index=request_index,
Expand Down Expand Up @@ -561,7 +561,7 @@ async def invoke_stream(
kernel.invoke_function_call(
function_call=function_call,
chat_history=override_history,
arguments=kwargs.get("arguments"),
arguments=arguments,
is_streaming=True,
execution_settings=None,
function_call_count=fc_count,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
from semantic_kernel.contents.streaming_chat_message_content import StreamingChatMessageContent
from semantic_kernel.contents.streaming_text_content import StreamingTextContent
from semantic_kernel.contents.utils.author_role import AuthorRole
from semantic_kernel.functions import KernelArguments


@pytest.fixture
Expand Down Expand Up @@ -67,7 +68,6 @@ def mock_thread():
return thread


@pytest.mark.asyncio
async def test_invoke_no_function_calls(mock_agent, mock_response, mock_chat_history, mock_thread):
async def mock_get_response(*args, **kwargs):
return mock_response
Expand All @@ -89,7 +89,6 @@ async def mock_get_response(*args, **kwargs):
assert final_msg.role == AuthorRole.ASSISTANT


@pytest.mark.asyncio
async def test_invoke_raises_on_failed_response(mock_agent, mock_chat_history, mock_thread):
mock_failed_response = MagicMock(spec=Response)
mock_failed_response.status = "failed"
Expand All @@ -115,7 +114,6 @@ async def mock_get_response(*args, **kwargs):
pass


@pytest.mark.asyncio
async def test_invoke_reaches_maximum_attempts(mock_agent, mock_chat_history, mock_thread):
call_counter = 0

Expand Down Expand Up @@ -173,7 +171,6 @@ async def mock_get_response(*args, **kwargs):
assert messages is not None


@pytest.mark.asyncio
async def test_invoke_with_function_calls(mock_agent, mock_chat_history, mock_thread):
initial_response = MagicMock(spec=Response)
initial_response.status = "completed"
Expand Down Expand Up @@ -227,6 +224,138 @@ async def mock_get_response(*args, **kwargs):
assert len(messages) == 3, f"Expected exactly 3 messages, got {len(messages)}"


async def test_invoke_passes_kernel_arguments_to_kernel(mock_agent, mock_chat_history, mock_thread):
# Prepare a response that triggers a function call
initial_response = MagicMock(spec=Response)
initial_response.status = "completed"
initial_response.id = "fake-response-id"
initial_response.output = [
ResponseFunctionToolCall(
id="tool_call_id",
call_id="call_id",
name="test_function",
arguments='{"some_arg": 123}',
type="function_call",
)
]
initial_response.error = None
initial_response.incomplete_details = None
initial_response.created_at = 123456
initial_response.usage = None
initial_response.role = "assistant"

final_response = MagicMock(spec=Response)
final_response.status = "completed"
final_response.id = "fake-final-response-id"
final_response.output = []
final_response.error = None
final_response.incomplete_details = None
final_response.created_at = 123456
final_response.usage = None
final_response.role = "assistant"

responses = [initial_response, final_response]

async def mock_invoke_fc(*args, **kwargs):
# Assert that KernelArguments were forwarded
assert isinstance(kwargs.get("arguments"), KernelArguments)
assert kwargs["arguments"].get("foo") == "bar"
return MagicMock(terminate=False)

mock_agent.kernel.invoke_function_call = MagicMock(side_effect=mock_invoke_fc)

async def mock_get_response(*args, **kwargs):
return responses.pop(0)

with patch.object(ResponsesAgentThreadActions, "_get_response", new=mock_get_response):
args = KernelArguments(foo="bar")
# Run invoke and ensure no assertion fails inside mock_invoke_fc
collected = []
async for _, msg in ResponsesAgentThreadActions.invoke(
agent=mock_agent,
chat_history=mock_chat_history,
thread=mock_thread,
store_enabled=True,
function_choice_behavior=MagicMock(maximum_auto_invoke_attempts=1),
arguments=args,
):
collected.append(msg)
assert len(collected) >= 2


async def test_invoke_stream_passes_kernel_arguments_to_kernel(mock_agent, mock_chat_history, mock_thread):
class MockStream(AsyncStream[ResponseStreamEvent]):
def __init__(self, events):
self._events = events

async def __aenter__(self):
return self

async def __aexit__(self, exc_type, exc_val, exc_tb):
pass

def __aiter__(self):
return self

async def __anext__(self):
if not self._events:
raise StopAsyncIteration
return self._events.pop(0)

# Event that includes a function call
mock_tool_call_event = ResponseOutputItemAddedEvent(
item=ResponseFunctionToolCall(
id="fake-tool-call-id",
call_id="fake-call-id",
name="test_function",
arguments='{"arg": 123}',
type="function_call",
),
output_index=0,
type="response.output_item.added",
sequence_number=0,
)

mock_stream_event_end = ResponseOutputItemDoneEvent(
item=ResponseOutputMessage(
role="assistant",
status="completed",
id="fake-item-id",
content=[ResponseOutputText(text="Final message after tool call", type="output_text", annotations=[])],
type="message",
),
output_index=0,
sequence_number=0,
type="response.output_item.done",
)

async def mock_get_response(*args, **kwargs):
return MockStream([mock_tool_call_event, mock_stream_event_end])

async def mock_invoke_function_call(*args, **kwargs):
assert isinstance(kwargs.get("arguments"), KernelArguments)
assert kwargs["arguments"].get("foo") == "bar"
return MagicMock(terminate=False)

mock_agent.kernel.invoke_function_call = MagicMock(side_effect=mock_invoke_function_call)

with patch.object(ResponsesAgentThreadActions, "_get_response", new=mock_get_response):
args = KernelArguments(foo="bar")
collected_stream_messages = []
async for _ in ResponsesAgentThreadActions.invoke_stream(
agent=mock_agent,
chat_history=mock_chat_history,
thread=mock_thread,
store_enabled=True,
function_choice_behavior=MagicMock(maximum_auto_invoke_attempts=1),
output_messages=collected_stream_messages,
arguments=args,
):
pass
# If assertions passed in mock, arguments were forwarded
assert len(collected_stream_messages) >= 1


async def test_invoke_stream_no_function_calls(mock_agent, mock_chat_history, mock_thread):
class MockStream(AsyncStream[ResponseStreamEvent]):
def __init__(self, events):
Expand Down Expand Up @@ -294,7 +423,6 @@ async def mock_get_response(*args, **kwargs):
assert collected_stream_messages[0].role == AuthorRole.ASSISTANT


@pytest.mark.asyncio
async def test_invoke_stream_with_tool_calls(mock_agent, mock_chat_history, mock_thread):
class MockStream(AsyncStream[ResponseStreamEvent]):
def __init__(self, events):
Expand Down
Loading