chore: import upstream snapshot with attribution
This commit is contained in:
+99
@@ -0,0 +1,99 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
from unittest.mock import AsyncMock, MagicMock
|
||||
|
||||
from semantic_kernel.agents.strategies.termination.aggregator_termination_strategy import (
|
||||
AggregateTerminationCondition,
|
||||
AggregatorTerminationStrategy,
|
||||
)
|
||||
from semantic_kernel.agents.strategies.termination.termination_strategy import TerminationStrategy
|
||||
from semantic_kernel.contents.chat_message_content import ChatMessageContent
|
||||
from tests.unit.agents.test_agent import MockAgent
|
||||
|
||||
|
||||
async def test_aggregate_termination_condition_all_true():
|
||||
agent = MockAgent(id="test-agent-id")
|
||||
history = [MagicMock(spec=ChatMessageContent)]
|
||||
|
||||
# Mocking two strategies that return True
|
||||
strategy1 = AsyncMock(spec=TerminationStrategy)
|
||||
strategy1.should_terminate.return_value = True
|
||||
|
||||
strategy2 = AsyncMock(spec=TerminationStrategy)
|
||||
strategy2.should_terminate.return_value = True
|
||||
|
||||
strategy = AggregatorTerminationStrategy(
|
||||
strategies=[strategy1, strategy2], condition=AggregateTerminationCondition.ALL
|
||||
)
|
||||
|
||||
result = await strategy.should_terminate_async(agent, history)
|
||||
|
||||
assert result is True
|
||||
strategy1.should_terminate.assert_awaited_once()
|
||||
strategy2.should_terminate.assert_awaited_once()
|
||||
|
||||
|
||||
async def test_aggregate_termination_condition_all_false():
|
||||
agent = MockAgent(id="test-agent-id")
|
||||
history = [MagicMock(spec=ChatMessageContent)]
|
||||
|
||||
# Mocking two strategies, one returns True, the other False
|
||||
strategy1 = AsyncMock(spec=TerminationStrategy)
|
||||
strategy1.should_terminate.return_value = True
|
||||
|
||||
strategy2 = AsyncMock(spec=TerminationStrategy)
|
||||
strategy2.should_terminate.return_value = False
|
||||
|
||||
strategy = AggregatorTerminationStrategy(
|
||||
strategies=[strategy1, strategy2], condition=AggregateTerminationCondition.ALL
|
||||
)
|
||||
|
||||
result = await strategy.should_terminate_async(agent, history)
|
||||
|
||||
assert result is False
|
||||
strategy1.should_terminate.assert_awaited_once()
|
||||
strategy2.should_terminate.assert_awaited_once()
|
||||
|
||||
|
||||
async def test_aggregate_termination_condition_any_true():
|
||||
agent = MockAgent(id="test-agent-id")
|
||||
history = [MagicMock(spec=ChatMessageContent)]
|
||||
|
||||
# Mocking two strategies, one returns False, the other True
|
||||
strategy1 = AsyncMock(spec=TerminationStrategy)
|
||||
strategy1.should_terminate.return_value = False
|
||||
|
||||
strategy2 = AsyncMock(spec=TerminationStrategy)
|
||||
strategy2.should_terminate.return_value = True
|
||||
|
||||
strategy = AggregatorTerminationStrategy(
|
||||
strategies=[strategy1, strategy2], condition=AggregateTerminationCondition.ANY
|
||||
)
|
||||
|
||||
result = await strategy.should_terminate_async(agent, history)
|
||||
|
||||
assert result is True
|
||||
strategy1.should_terminate.assert_awaited_once()
|
||||
strategy2.should_terminate.assert_awaited_once()
|
||||
|
||||
|
||||
async def test_aggregate_termination_condition_any_false():
|
||||
agent = MockAgent(id="test-agent-id")
|
||||
history = [MagicMock(spec=ChatMessageContent)]
|
||||
|
||||
# Mocking two strategies that return False
|
||||
strategy1 = AsyncMock(spec=TerminationStrategy)
|
||||
strategy1.should_terminate.return_value = False
|
||||
|
||||
strategy2 = AsyncMock(spec=TerminationStrategy)
|
||||
strategy2.should_terminate.return_value = False
|
||||
|
||||
strategy = AggregatorTerminationStrategy(
|
||||
strategies=[strategy1, strategy2], condition=AggregateTerminationCondition.ANY
|
||||
)
|
||||
|
||||
result = await strategy.should_terminate_async(agent, history)
|
||||
|
||||
assert result is False
|
||||
strategy1.should_terminate.assert_awaited_once()
|
||||
strategy2.should_terminate.assert_awaited_once()
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
|
||||
from semantic_kernel.agents.strategies.termination.default_termination_strategy import DefaultTerminationStrategy
|
||||
|
||||
|
||||
async def test_should_agent_terminate_():
|
||||
strategy = DefaultTerminationStrategy(maximum_iterations=2)
|
||||
result = await strategy.should_agent_terminate(None, [])
|
||||
assert not result
|
||||
+106
@@ -0,0 +1,106 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
from unittest.mock import AsyncMock, MagicMock
|
||||
|
||||
import pytest
|
||||
|
||||
from semantic_kernel.agents.strategies.selection.kernel_function_selection_strategy import (
|
||||
KernelFunctionSelectionStrategy,
|
||||
)
|
||||
from semantic_kernel.contents.chat_message_content import ChatMessageContent
|
||||
from semantic_kernel.exceptions.agent_exceptions import AgentExecutionException
|
||||
from semantic_kernel.functions.kernel_function import KernelFunction
|
||||
from semantic_kernel.kernel import Kernel
|
||||
from tests.unit.agents.test_agent import MockAgent
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def agents():
|
||||
"""Fixture that provides a list of mock agents."""
|
||||
return [MockAgent(id=f"agent-{i}", name=f"Agent_{i}") for i in range(3)]
|
||||
|
||||
|
||||
async def test_kernel_function_selection_next_success(agents):
|
||||
history = [MagicMock(spec=ChatMessageContent)]
|
||||
mock_function = AsyncMock(spec=KernelFunction)
|
||||
mock_function.invoke.return_value = MagicMock(value="Agent_1")
|
||||
mock_kernel = MagicMock(spec=Kernel)
|
||||
|
||||
strategy = KernelFunctionSelectionStrategy(
|
||||
function=mock_function, kernel=mock_kernel, result_parser=lambda result: result.value
|
||||
)
|
||||
|
||||
selected_agent = await strategy.next(agents, history)
|
||||
|
||||
assert selected_agent.name == "Agent_1"
|
||||
mock_function.invoke.assert_awaited_once()
|
||||
|
||||
|
||||
async def test_kernel_function_selection_next_agent_not_found(agents):
|
||||
history = [MagicMock(spec=ChatMessageContent)]
|
||||
mock_function = AsyncMock(spec=KernelFunction)
|
||||
mock_function.invoke.return_value = MagicMock(value="Nonexistent-Agent")
|
||||
mock_kernel = MagicMock(spec=Kernel)
|
||||
|
||||
strategy = KernelFunctionSelectionStrategy(
|
||||
function=mock_function, kernel=mock_kernel, result_parser=lambda result: result.value
|
||||
)
|
||||
|
||||
with pytest.raises(AgentExecutionException) as excinfo:
|
||||
await strategy.next(agents, history)
|
||||
|
||||
assert "Strategy unable to select next agent" in str(excinfo.value)
|
||||
mock_function.invoke.assert_awaited_once()
|
||||
|
||||
|
||||
async def test_kernel_function_selection_next_result_is_none(agents):
|
||||
history = [MagicMock(spec=ChatMessageContent)]
|
||||
mock_function = AsyncMock(spec=KernelFunction)
|
||||
mock_function.invoke.return_value = None
|
||||
mock_kernel = MagicMock(spec=Kernel)
|
||||
|
||||
strategy = KernelFunctionSelectionStrategy(
|
||||
function=mock_function, kernel=mock_kernel, result_parser=lambda result: result.value if result else None
|
||||
)
|
||||
|
||||
with pytest.raises(AgentExecutionException) as excinfo:
|
||||
await strategy.next(agents, history)
|
||||
|
||||
assert "Strategy unable to determine next agent" in str(excinfo.value)
|
||||
mock_function.invoke.assert_awaited_once()
|
||||
|
||||
|
||||
async def test_kernel_function_selection_next_exception_during_invoke(agents):
|
||||
history = [MagicMock(spec=ChatMessageContent)]
|
||||
mock_function = AsyncMock(spec=KernelFunction)
|
||||
mock_function.invoke.side_effect = Exception("Test exception")
|
||||
mock_kernel = MagicMock(spec=Kernel)
|
||||
|
||||
strategy = KernelFunctionSelectionStrategy(
|
||||
function=mock_function, kernel=mock_kernel, result_parser=lambda result: result.value
|
||||
)
|
||||
|
||||
with pytest.raises(AgentExecutionException) as excinfo:
|
||||
await strategy.next(agents, history)
|
||||
|
||||
assert "Strategy failed to execute function" in str(excinfo.value)
|
||||
mock_function.invoke.assert_awaited_once()
|
||||
|
||||
|
||||
async def test_kernel_function_selection_result_parser_is_async(agents):
|
||||
history = [MagicMock(spec=ChatMessageContent)]
|
||||
mock_function = AsyncMock(spec=KernelFunction)
|
||||
mock_function.invoke.return_value = MagicMock(value="Agent_2")
|
||||
mock_kernel = MagicMock(spec=Kernel)
|
||||
|
||||
async def async_result_parser(result):
|
||||
return result.value
|
||||
|
||||
strategy = KernelFunctionSelectionStrategy(
|
||||
function=mock_function, kernel=mock_kernel, result_parser=async_result_parser
|
||||
)
|
||||
|
||||
selected_agent = await strategy.next(agents, history)
|
||||
|
||||
assert selected_agent.name == "Agent_2"
|
||||
mock_function.invoke.assert_awaited_once()
|
||||
+114
@@ -0,0 +1,114 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
|
||||
from semantic_kernel.agents.strategies import KernelFunctionTerminationStrategy
|
||||
from semantic_kernel.contents.chat_message_content import ChatMessageContent
|
||||
from semantic_kernel.functions.kernel_arguments import KernelArguments
|
||||
from semantic_kernel.functions.kernel_function import KernelFunction
|
||||
from semantic_kernel.kernel import Kernel
|
||||
from tests.unit.agents.test_agent import MockAgent
|
||||
|
||||
|
||||
async def test_should_agent_terminate_with_result_true():
|
||||
agent = MockAgent(id="test-agent-id")
|
||||
history = [MagicMock(spec=ChatMessageContent)]
|
||||
|
||||
mock_function = AsyncMock(spec=KernelFunction)
|
||||
mock_function.invoke.return_value = MagicMock(value=True)
|
||||
mock_kernel = MagicMock(spec=Kernel)
|
||||
|
||||
strategy = KernelFunctionTerminationStrategy(
|
||||
agents=[agent], function=mock_function, kernel=mock_kernel, result_parser=lambda result: result.value
|
||||
)
|
||||
|
||||
result = await strategy.should_agent_terminate(agent, history)
|
||||
|
||||
assert result is True
|
||||
mock_function.invoke.assert_awaited_once()
|
||||
|
||||
|
||||
async def test_should_agent_terminate_with_result_false():
|
||||
agent = MockAgent(id="test-agent-id")
|
||||
history = [MagicMock(spec=ChatMessageContent)]
|
||||
|
||||
mock_function = AsyncMock(spec=KernelFunction)
|
||||
mock_function.invoke.return_value = MagicMock(value=False)
|
||||
mock_kernel = MagicMock(spec=Kernel)
|
||||
|
||||
strategy = KernelFunctionTerminationStrategy(
|
||||
agents=[agent], function=mock_function, kernel=mock_kernel, result_parser=lambda result: result.value
|
||||
)
|
||||
|
||||
result = await strategy.should_agent_terminate(agent, history)
|
||||
|
||||
assert result is False
|
||||
mock_function.invoke.assert_awaited_once()
|
||||
|
||||
|
||||
async def test_should_agent_terminate_with_none_result():
|
||||
agent = MockAgent(id="test-agent-id")
|
||||
history = [MagicMock(spec=ChatMessageContent)]
|
||||
|
||||
mock_function = AsyncMock(spec=KernelFunction)
|
||||
mock_function.invoke.return_value = None
|
||||
mock_kernel = MagicMock(spec=Kernel)
|
||||
|
||||
strategy = KernelFunctionTerminationStrategy(
|
||||
agents=[agent],
|
||||
function=mock_function,
|
||||
kernel=mock_kernel,
|
||||
result_parser=lambda result: result.value if result else False,
|
||||
)
|
||||
|
||||
result = await strategy.should_agent_terminate(agent, history)
|
||||
|
||||
assert result is False
|
||||
mock_function.invoke.assert_awaited_once()
|
||||
|
||||
|
||||
async def test_should_agent_terminate_custom_arguments():
|
||||
agent = MockAgent(id="test-agent-id")
|
||||
history = [MagicMock(spec=ChatMessageContent)]
|
||||
|
||||
mock_function = AsyncMock(spec=KernelFunction)
|
||||
mock_function.invoke.return_value = MagicMock(value=True)
|
||||
mock_kernel = MagicMock(spec=Kernel)
|
||||
|
||||
custom_args = KernelArguments(execution_settings={"some_setting": MagicMock(model_dump=lambda: {"key": "value"})})
|
||||
|
||||
strategy = KernelFunctionTerminationStrategy(
|
||||
agents=[agent],
|
||||
function=mock_function,
|
||||
kernel=mock_kernel,
|
||||
arguments=custom_args,
|
||||
result_parser=lambda result: result.value,
|
||||
)
|
||||
|
||||
with patch.object(KernelArguments, "__init__", return_value=None) as mock_init:
|
||||
result = await strategy.should_agent_terminate(agent, history)
|
||||
mock_init.assert_called_once()
|
||||
|
||||
assert result is True
|
||||
mock_function.invoke.assert_awaited_once()
|
||||
|
||||
|
||||
async def test_should_agent_terminate_result_parser_awaitable():
|
||||
agent = MockAgent(id="test-agent-id")
|
||||
history = [MagicMock(spec=ChatMessageContent)]
|
||||
|
||||
mock_function = AsyncMock(spec=KernelFunction)
|
||||
mock_function.invoke.return_value = MagicMock(value=True)
|
||||
mock_kernel = MagicMock(spec=Kernel)
|
||||
|
||||
async def mock_result_parser(result):
|
||||
return result.value
|
||||
|
||||
strategy = KernelFunctionTerminationStrategy(
|
||||
agents=[agent], function=mock_function, kernel=mock_kernel, result_parser=mock_result_parser
|
||||
)
|
||||
|
||||
result = await strategy.should_agent_terminate(agent, history)
|
||||
|
||||
assert result is True
|
||||
mock_function.invoke.assert_awaited_once()
|
||||
+108
@@ -0,0 +1,108 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
import pytest
|
||||
|
||||
from semantic_kernel.agents.agent import Agent
|
||||
from semantic_kernel.agents.strategies.selection.sequential_selection_strategy import SequentialSelectionStrategy
|
||||
from semantic_kernel.exceptions.agent_exceptions import AgentExecutionException
|
||||
from tests.unit.agents.test_agent import MockAgent
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def agents():
|
||||
"""Fixture that provides a list of mock agents."""
|
||||
return [MockAgent(id=f"agent-{i}") for i in range(3)]
|
||||
|
||||
|
||||
async def test_sequential_selection_next(agents):
|
||||
strategy = SequentialSelectionStrategy()
|
||||
|
||||
# Test the sequence of selections
|
||||
selected_agent_1 = await strategy.next(agents, [])
|
||||
selected_agent_2 = await strategy.next(agents, [])
|
||||
selected_agent_3 = await strategy.next(agents, [])
|
||||
|
||||
assert selected_agent_1.id == "agent-0"
|
||||
assert selected_agent_2.id == "agent-1"
|
||||
assert selected_agent_3.id == "agent-2"
|
||||
|
||||
|
||||
async def test_sequential_selection_wraps_around(agents):
|
||||
strategy = SequentialSelectionStrategy()
|
||||
|
||||
for _ in range(3):
|
||||
await strategy.next(agents, [])
|
||||
|
||||
selected_agent = await strategy.next(agents, [])
|
||||
assert selected_agent.id == "agent-0"
|
||||
|
||||
|
||||
async def test_sequential_selection_reset(agents):
|
||||
strategy = SequentialSelectionStrategy()
|
||||
|
||||
# Move the index to the middle of the list
|
||||
await strategy.next(agents, [])
|
||||
await strategy.next(agents, [])
|
||||
|
||||
strategy.reset()
|
||||
|
||||
selected_agent = await strategy.next(agents, [])
|
||||
assert selected_agent.id == "agent-0"
|
||||
|
||||
|
||||
async def test_sequential_selection_exceeds_length(agents):
|
||||
strategy = SequentialSelectionStrategy()
|
||||
|
||||
strategy._index = len(agents)
|
||||
|
||||
selected_agent = await strategy.next(agents, [])
|
||||
|
||||
assert selected_agent.id == "agent-0"
|
||||
assert strategy._index == 0
|
||||
|
||||
selected_agent = await strategy.next(agents, [])
|
||||
|
||||
assert selected_agent.id == "agent-1"
|
||||
assert strategy._index == 1
|
||||
|
||||
|
||||
async def test_sequential_selection_empty_agents():
|
||||
strategy = SequentialSelectionStrategy()
|
||||
|
||||
with pytest.raises(AgentExecutionException) as excinfo:
|
||||
await strategy.next([], [])
|
||||
|
||||
assert "Agent Failure - No agents present to select." in str(excinfo.value)
|
||||
|
||||
|
||||
async def test_sequential_selection_avoid_selecting_same_agent_twice():
|
||||
# Arrange
|
||||
agent_0 = MagicMock(spec=Agent)
|
||||
agent_0.id = "agent-0"
|
||||
agent_0.name = "Agent0"
|
||||
agent_0.plugins = []
|
||||
|
||||
agent_1 = MagicMock(spec=Agent)
|
||||
agent_1.id = "agent-1"
|
||||
agent_1.name = "Agent1"
|
||||
agent_1.plugins = []
|
||||
|
||||
agents = [agent_0, agent_1]
|
||||
|
||||
strategy = SequentialSelectionStrategy()
|
||||
# Simulate that we've already selected an agent once:
|
||||
strategy.has_selected = True
|
||||
# Set the initial agent to the first agent
|
||||
strategy.initial_agent = agent_0
|
||||
# Ensure the internal index is set to -1
|
||||
strategy._index = -1
|
||||
|
||||
# Act
|
||||
selected_agent = await strategy.next(agents, [])
|
||||
|
||||
# Assert
|
||||
# According to the condition, we should skip selecting agent_0 again
|
||||
assert selected_agent.id == "agent-1"
|
||||
assert strategy._index == 1
|
||||
@@ -0,0 +1,63 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
import pytest
|
||||
|
||||
from semantic_kernel.agents import Agent
|
||||
from semantic_kernel.agents.strategies.termination.termination_strategy import TerminationStrategy
|
||||
from semantic_kernel.contents.chat_message_content import ChatMessageContent
|
||||
from tests.unit.agents.test_agent import MockAgent
|
||||
|
||||
|
||||
class TerminationStrategyTest(TerminationStrategy):
|
||||
"""A test implementation of TerminationStrategy for testing purposes."""
|
||||
|
||||
async def should_agent_terminate(self, agent: "Agent", history: list[ChatMessageContent]) -> bool:
|
||||
"""Simple test implementation that always returns True."""
|
||||
return True
|
||||
|
||||
|
||||
async def test_should_terminate_with_matching_agent():
|
||||
agent = MockAgent(id="test-agent-id")
|
||||
strategy = TerminationStrategyTest(agents=[agent])
|
||||
|
||||
# Assuming history is a list of ChatMessageContent; can be mocked or made minimal
|
||||
history = [MagicMock(spec=ChatMessageContent)]
|
||||
|
||||
result = await strategy.should_terminate(agent, history)
|
||||
assert result is True
|
||||
|
||||
|
||||
async def test_should_terminate_with_non_matching_agent():
|
||||
agent = MockAgent(id="test-agent-id")
|
||||
non_matching_agent = MockAgent(id="non-matching-agent-id")
|
||||
strategy = TerminationStrategyTest(agents=[non_matching_agent])
|
||||
|
||||
# Assuming history is a list of ChatMessageContent; can be mocked or made minimal
|
||||
history = [MagicMock(spec=ChatMessageContent)]
|
||||
|
||||
result = await strategy.should_terminate(agent, history)
|
||||
assert result is False
|
||||
|
||||
|
||||
async def test_should_terminate_no_agents_in_strategy():
|
||||
agent = MockAgent(id="test-agent-id")
|
||||
strategy = TerminationStrategyTest()
|
||||
|
||||
# Assuming history is a list of ChatMessageContent; can be mocked or made minimal
|
||||
history = [MagicMock(spec=ChatMessageContent)]
|
||||
|
||||
result = await strategy.should_terminate(agent, history)
|
||||
assert result is True
|
||||
|
||||
|
||||
async def test_should_agent_terminate_not_implemented():
|
||||
agent = MockAgent(id="test-agent-id")
|
||||
strategy = TerminationStrategy(agents=[agent])
|
||||
|
||||
# Assuming history is a list of ChatMessageContent; can be mocked or made minimal
|
||||
history = [MagicMock(spec=ChatMessageContent)]
|
||||
|
||||
with pytest.raises(NotImplementedError):
|
||||
await strategy.should_agent_terminate(agent, history)
|
||||
Reference in New Issue
Block a user