mirror of
https://github.com/microsoft/semantic-kernel.git
synced 2026-10-12 11:19:56 +00:00
### Motivation and Context In the next phase of the agent work in Python, we are to introduce the ability to have agents interact with each other. Agent group chat allows developers to create richer and more dynamic agent interactions. <!-- Thank you for your contribution to the semantic-kernel repo! Please help reviewers and future users, providing the following information: 1. Why is this change required? 2. What problem does it solve? 3. What scenario does it contribute to? 4. If it fixes an open issue, please link to the issue here. --> ### Description This PR introduces the following: - Agent group chat functionality via an `agent_chat` base class that forms the foundation for group chats. An `agent_group_chat` class extends that underlying functionality and provides an entry point to kick off the group chat either via invoking the chat for the defined agents, or specifying if a provided agent is to join the existing chat. - The required channels for the chat completion agent and assistant agent to properly chat together - Adds more concept samples and getting started with agent samples to enrich the onboarding experiencing for those wanting to see the capability of agents and agent group chat. - Adds unit test for near 100% coverage - Updates some existing imports for agents, which try and make the required import experience more intuitive. - Introduces assistant agent abstractions for working with files and vector stores so we don't directly expose the underlying client types. - Closes #7966 - Closes #7577 <!-- Describe your changes, the overall approach, the underlying design. These notes will help understanding how your code works. Thanks! --> ### Contribution Checklist <!-- Before submitting this PR, please make sure: --> - [X] The code builds clean without any errors or warnings - [X] The PR follows the [SK Contribution Guidelines](https://github.com/microsoft/semantic-kernel/blob/main/CONTRIBUTING.md) and the [pre-submission formatting script](https://github.com/microsoft/semantic-kernel/blob/main/CONTRIBUTING.md#development-scripts) raises no violations - [X] All unit tests pass, and I have added new tests where possible - [ ] I didn't break anyone 😄
142 lines
4.6 KiB
Python
142 lines
4.6 KiB
Python
# Copyright (c) Microsoft. All rights reserved.
|
|
|
|
from unittest.mock import AsyncMock, MagicMock, patch
|
|
|
|
import pytest
|
|
|
|
from semantic_kernel.agents.agent import Agent
|
|
from semantic_kernel.agents.channels.agent_channel import AgentChannel
|
|
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
|
|
|
|
|
|
class MockAgent(Agent):
|
|
"""A mock agent for testing purposes."""
|
|
|
|
def __init__(self, id: str = None, name: str = "Test Agent", description: str = "A test agent"):
|
|
args = {
|
|
"name": name,
|
|
"description": description,
|
|
}
|
|
if id is not None:
|
|
args["id"] = id
|
|
super().__init__(**args)
|
|
|
|
def get_channel_keys(self) -> list[str]:
|
|
return ["key1", "key2"]
|
|
|
|
async def create_channel(self) -> AgentChannel:
|
|
return AsyncMock(spec=AgentChannel)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
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()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
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()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
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()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
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()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
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()
|