diff --git a/python/semantic_kernel/agents/strategies/termination/aggregator_termination_strategy.py b/python/semantic_kernel/agents/strategies/termination/aggregator_termination_strategy.py index 5ee26f5f6042..853ca5d171bf 100644 --- a/python/semantic_kernel/agents/strategies/termination/aggregator_termination_strategy.py +++ b/python/semantic_kernel/agents/strategies/termination/aggregator_termination_strategy.py @@ -8,7 +8,6 @@ from semantic_kernel.agents.strategies.termination.termination_strategy import TerminationStrategy from semantic_kernel.contents.chat_message_content import ChatMessageContent -from semantic_kernel.kernel_pydantic import KernelBaseModel from semantic_kernel.utils.feature_stage_decorator import experimental if TYPE_CHECKING: @@ -24,13 +23,13 @@ class AggregateTerminationCondition(str, Enum): @experimental -class AggregatorTerminationStrategy(KernelBaseModel): +class AggregatorTerminationStrategy(TerminationStrategy): """A strategy that aggregates multiple termination strategies.""" strategies: list[TerminationStrategy] condition: AggregateTerminationCondition = Field(default=AggregateTerminationCondition.ALL) - async def should_terminate_async( + async def should_agent_terminate( self, agent: "Agent", history: list[ChatMessageContent], @@ -50,3 +49,19 @@ async def should_terminate_async( if self.condition == AggregateTerminationCondition.ALL: return all(results) return any(results) + + async def should_terminate_async( + self, + agent: "Agent", + history: list[ChatMessageContent], + ) -> bool: + """Check if the agent should terminate. + + Args: + agent: The agent to check. + history: The history of messages in the conversation. + + Returns: + True if the agent should terminate, False otherwise + """ + return await self.should_agent_terminate(agent=agent, history=history) diff --git a/python/tests/unit/agents/test_group_chat_strategies/test_aggregator_termination_strategy.py b/python/tests/unit/agents/test_group_chat_strategies/test_aggregator_termination_strategy.py index bb659c417ebd..9a382b73896d 100644 --- a/python/tests/unit/agents/test_group_chat_strategies/test_aggregator_termination_strategy.py +++ b/python/tests/unit/agents/test_group_chat_strategies/test_aggregator_termination_strategy.py @@ -97,3 +97,61 @@ async def test_aggregate_termination_condition_any_false(): assert result is False strategy1.should_terminate.assert_awaited_once() strategy2.should_terminate.assert_awaited_once() + + +def test_aggregator_is_termination_strategy(): + strategy1 = AsyncMock(spec=TerminationStrategy) + strategy = AggregatorTerminationStrategy(strategies=[strategy1]) + assert isinstance(strategy, TerminationStrategy) + assert issubclass(AggregatorTerminationStrategy, TerminationStrategy) + + +async def test_aggregator_should_agent_terminate(): + agent = MockAgent(id="test-agent-id") + history = [MagicMock(spec=ChatMessageContent)] + + strategy1 = AsyncMock(spec=TerminationStrategy) + strategy1.should_terminate.return_value = True + + strategy = AggregatorTerminationStrategy(strategies=[strategy1], condition=AggregateTerminationCondition.ALL) + + result = await strategy.should_agent_terminate(agent, history) + + assert result is True + strategy1.should_terminate.assert_awaited_once_with(agent, history) + + +async def test_aggregator_should_terminate_agent_filtering(): + agent1 = MockAgent(id="agent-1") + agent2 = MockAgent(id="agent-2") + history = [MagicMock(spec=ChatMessageContent)] + + sub_strategy = AsyncMock(spec=TerminationStrategy) + sub_strategy.should_terminate.return_value = True + + # Aggregator scoped only to agent1 + strategy = AggregatorTerminationStrategy(strategies=[sub_strategy], agents=[agent1]) + + # Calling should_terminate for agent2 should be False and not invoke sub_strategy + result = await strategy.should_terminate(agent2, history) + assert result is False + sub_strategy.should_terminate.assert_not_awaited() + + # Calling should_terminate for agent1 should evaluate sub_strategy + result = await strategy.should_terminate(agent1, history) + assert result is True + sub_strategy.should_terminate.assert_awaited_once_with(agent1, history) + + +def test_aggregator_termination_strategy_with_agent_group_chat(): + from semantic_kernel.agents.group_chat.agent_group_chat import AgentGroupChat + + sub_strategy = AsyncMock(spec=TerminationStrategy) + aggregator = AggregatorTerminationStrategy( + strategies=[sub_strategy], + maximum_iterations=10, + ) + + chat = AgentGroupChat(termination_strategy=aggregator) + assert chat.termination_strategy == aggregator + assert chat.termination_strategy.maximum_iterations == 10