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 @@ -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:
Expand All @@ -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],
Expand All @@ -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)
Original file line number Diff line number Diff line change
Expand Up @@ -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
Loading