autogen/test/agentchat/test_groupchat.py
Mark Sze 4451632784
Ability to add MessageTransforms to the GroupChat's Select Speaker nested chat (speaker_selection_method='auto') (#2719)
* Initial commit with ability to add transforms to GroupChat

* Added tests

* Tidy up

* Tidy up of variable names and commented out test

* Tidy up comment

* Update import to relative

* Added documentation topic for transform messages for speaker selection.

* Added handling of non-core module, transforms, in groupchat

* Adjusted parameter if module not loaded.

* Updated groupchat test which failed during CI/CD

---------

Co-authored-by: Li Jiang <bnujli@gmail.com>
2024-08-26 03:47:48 +00:00

2135 lines
79 KiB
Python
Executable File

#!/usr/bin/env python3 -m pytest
import builtins
import io
import json
import logging
from typing import Any, Dict, List, Optional
from unittest import TestCase, mock
import pytest
from test_assistant_agent import KEY_LOC, OAI_CONFIG_LIST
import autogen
from autogen import Agent, AssistantAgent, GroupChat, GroupChatManager
from autogen.agentchat.contrib.capabilities import transform_messages, transforms
from autogen.exception_utils import AgentNameConflict, UndefinedNextAgent
def test_func_call_groupchat():
agent1 = autogen.ConversableAgent(
"alice",
human_input_mode="NEVER",
llm_config=False,
default_auto_reply="This is alice speaking.",
)
agent2 = autogen.ConversableAgent(
"bob",
human_input_mode="NEVER",
llm_config=False,
default_auto_reply="This is bob speaking.",
function_map={"test_func": lambda x: x},
)
groupchat = autogen.GroupChat(agents=[agent1, agent2], messages=[], max_round=3)
group_chat_manager = autogen.GroupChatManager(groupchat=groupchat, llm_config=False)
agent2.initiate_chat(group_chat_manager, message={"function_call": {"name": "test_func", "arguments": '{"x": 1}'}})
assert len(groupchat.messages) == 3
assert (
groupchat.messages[-2]["role"] == "function"
and groupchat.messages[-2]["name"] == "test_func"
and groupchat.messages[-2]["content"] == "1"
)
assert groupchat.messages[-1]["name"] == "alice"
agent3 = autogen.ConversableAgent(
"carol",
human_input_mode="NEVER",
llm_config=False,
default_auto_reply="This is carol speaking.",
function_map={"test_func": lambda x: x + 1},
)
groupchat = autogen.GroupChat(agents=[agent1, agent2, agent3], messages=[], max_round=3)
group_chat_manager = autogen.GroupChatManager(groupchat=groupchat, llm_config=False)
agent3.initiate_chat(group_chat_manager, message={"function_call": {"name": "test_func", "arguments": '{"x": 1}'}})
assert (
groupchat.messages[-2]["role"] == "function"
and groupchat.messages[-2]["name"] == "test_func"
and groupchat.messages[-2]["content"] == "1"
)
assert groupchat.messages[-1]["name"] == "carol"
agent2.initiate_chat(group_chat_manager, message={"function_call": {"name": "func", "arguments": '{"x": 1}'}})
def test_chat_manager():
agent1 = autogen.ConversableAgent(
"alice",
max_consecutive_auto_reply=2,
human_input_mode="NEVER",
llm_config=False,
default_auto_reply="This is alice speaking.",
)
agent2 = autogen.ConversableAgent(
"bob",
max_consecutive_auto_reply=2,
human_input_mode="NEVER",
llm_config=False,
default_auto_reply="This is bob speaking.",
)
groupchat = autogen.GroupChat(agents=[agent1, agent2], messages=[], max_round=2)
group_chat_manager = autogen.GroupChatManager(groupchat=groupchat, llm_config=False)
agent1.initiate_chat(group_chat_manager, message="hello")
assert len(agent1.chat_messages[group_chat_manager]) == 2
assert len(groupchat.messages) == 2
group_chat_manager.reset()
assert len(groupchat.messages) == 0
agent1.reset()
agent2.reset()
agent2.initiate_chat(group_chat_manager, message="hello")
assert len(groupchat.messages) == 2
with pytest.raises(ValueError):
agent2.initiate_chat(group_chat_manager, message={"function_call": {"name": "func", "arguments": '{"x": 1}'}})
def _test_selection_method(method: str):
agent1 = autogen.ConversableAgent(
"alice",
max_consecutive_auto_reply=10,
human_input_mode="NEVER",
llm_config=False,
default_auto_reply="This is alice speaking.",
)
agent2 = autogen.ConversableAgent(
"bob",
max_consecutive_auto_reply=10,
human_input_mode="NEVER",
llm_config=False,
default_auto_reply="This is bob speaking.",
)
agent3 = autogen.ConversableAgent(
"charlie",
max_consecutive_auto_reply=10,
human_input_mode="NEVER",
llm_config=False,
default_auto_reply="This is charlie speaking.",
)
groupchat = autogen.GroupChat(
agents=[agent1, agent2, agent3],
messages=[],
max_round=6,
speaker_selection_method=method,
allow_repeat_speaker=False if method == "manual" else True,
)
group_chat_manager = autogen.GroupChatManager(groupchat=groupchat, llm_config=False)
if method == "round_robin":
agent1.initiate_chat(group_chat_manager, message="This is alice speaking.")
assert len(agent1.chat_messages[group_chat_manager]) == 6
assert len(groupchat.messages) == 6
assert [msg["content"] for msg in agent1.chat_messages[group_chat_manager]] == [
"This is alice speaking.",
"This is bob speaking.",
"This is charlie speaking.",
] * 2
elif method == "auto":
agent1.initiate_chat(group_chat_manager, message="This is alice speaking.")
assert len(agent1.chat_messages[group_chat_manager]) == 6
assert len(groupchat.messages) == 6
elif method == "random":
agent1.initiate_chat(group_chat_manager, message="This is alice speaking.")
assert len(agent1.chat_messages[group_chat_manager]) == 6
assert len(groupchat.messages) == 6
elif method == "manual":
for user_input in ["", "q", "x", "1", "10"]:
with mock.patch.object(builtins, "input", lambda _: user_input):
group_chat_manager.reset()
agent1.reset()
agent2.reset()
agent3.reset()
agent1.initiate_chat(group_chat_manager, message="This is alice speaking.")
if user_input == "1":
assert len(agent1.chat_messages[group_chat_manager]) == 6
assert len(groupchat.messages) == 6
assert [msg["content"] for msg in agent1.chat_messages[group_chat_manager]] == [
"This is alice speaking.",
"This is bob speaking.",
"This is alice speaking.",
"This is bob speaking.",
"This is alice speaking.",
"This is bob speaking.",
]
else:
assert len(agent1.chat_messages[group_chat_manager]) == 6
assert len(groupchat.messages) == 6
elif method == "wrong":
with pytest.raises(ValueError):
agent1.initiate_chat(group_chat_manager, message="This is alice speaking.")
def test_speaker_selection_method():
for method in ["auto", "round_robin", "random", "manual", "wrong", "RounD_roBin"]:
_test_selection_method(method)
def _test_n_agents_less_than_3(method):
agent1 = autogen.ConversableAgent(
"alice",
max_consecutive_auto_reply=10,
human_input_mode="NEVER",
llm_config=False,
default_auto_reply="This is alice speaking.",
)
agent2 = autogen.ConversableAgent(
"bob",
max_consecutive_auto_reply=10,
human_input_mode="NEVER",
llm_config=False,
default_auto_reply="This is bob speaking.",
)
# test two agents
groupchat = autogen.GroupChat(
agents=[agent1, agent2],
messages=[],
max_round=6,
speaker_selection_method=method,
allow_repeat_speaker=[agent1, agent2] if method == "random" else False,
)
group_chat_manager = autogen.GroupChatManager(groupchat=groupchat, llm_config=False)
agent1.initiate_chat(group_chat_manager, message="This is alice speaking.")
assert len(agent1.chat_messages[group_chat_manager]) == 6
assert len(groupchat.messages) == 6
if method != "random" or method.lower() == "round_robin":
assert [msg["content"] for msg in agent1.chat_messages[group_chat_manager]] == [
"This is alice speaking.",
"This is bob speaking.",
] * 3
# test zero agent
with pytest.raises(ValueError):
groupchat = autogen.GroupChat(
agents=[], messages=[], max_round=6, speaker_selection_method="round_robin", allow_repeat_speaker=False
)
group_chat_manager = autogen.GroupChatManager(groupchat=groupchat, llm_config=False)
agent1.initiate_chat(group_chat_manager, message="This is alice speaking.")
def test_invalid_allow_repeat_speaker():
agent1 = autogen.ConversableAgent(
"alice",
max_consecutive_auto_reply=10,
human_input_mode="NEVER",
llm_config=False,
default_auto_reply="This is alice speaking.",
)
agent2 = autogen.ConversableAgent(
"bob",
max_consecutive_auto_reply=10,
human_input_mode="NEVER",
llm_config=False,
default_auto_reply="This is bob speaking.",
)
# test invalid allow_repeat_speaker
with pytest.raises(ValueError) as e:
autogen.GroupChat(
agents=[agent1, agent2],
messages=[],
max_round=6,
speaker_selection_method="round_robin",
allow_repeat_speaker={},
)
assert str(e.value) == "GroupChat allow_repeat_speaker should be a bool or a list of Agents.", e.value
def test_n_agents_less_than_3():
for method in ["auto", "round_robin", "random", "RounD_roBin"]:
_test_n_agents_less_than_3(method)
def test_plugin():
# Give another Agent class ability to manage group chat
agent1 = autogen.ConversableAgent(
"alice",
max_consecutive_auto_reply=2,
human_input_mode="NEVER",
llm_config=False,
default_auto_reply="This is alice speaking.",
)
agent2 = autogen.ConversableAgent(
"bob",
max_consecutive_auto_reply=2,
human_input_mode="NEVER",
llm_config=False,
default_auto_reply="This is bob speaking.",
)
groupchat = autogen.GroupChat(agents=[agent1, agent2], messages=[], max_round=2)
group_chat_manager = autogen.ConversableAgent(name="deputy_manager", llm_config=False)
group_chat_manager.register_reply(
autogen.Agent,
reply_func=autogen.GroupChatManager.run_chat,
config=groupchat,
reset_config=autogen.GroupChat.reset,
)
agent1.initiate_chat(group_chat_manager, message="hello")
assert len(agent1.chat_messages[group_chat_manager]) == 2
assert len(groupchat.messages) == 2
def test_agent_mentions():
agent1 = autogen.ConversableAgent(
"alice",
max_consecutive_auto_reply=2,
human_input_mode="NEVER",
llm_config=False,
default_auto_reply="This is alice speaking.",
)
agent2 = autogen.ConversableAgent(
"bob",
max_consecutive_auto_reply=2,
human_input_mode="NEVER",
llm_config=False,
default_auto_reply="This is bob speaking.",
)
agent3 = autogen.ConversableAgent(
"sam",
max_consecutive_auto_reply=2,
human_input_mode="NEVER",
llm_config=False,
default_auto_reply="This is sam speaking.",
)
groupchat = autogen.GroupChat(agents=[agent1, agent2, agent3], messages=[], max_round=2)
# Basic counting
assert json.dumps(groupchat._mentioned_agents("", [agent1, agent2, agent3]), sort_keys=True) == "{}"
assert json.dumps(groupchat._mentioned_agents("alice", [agent1, agent2, agent3]), sort_keys=True) == '{"alice": 1}'
assert (
json.dumps(groupchat._mentioned_agents("alice bob alice", [agent1, agent2, agent3]), sort_keys=True)
== '{"alice": 2, "bob": 1}'
)
assert (
json.dumps(groupchat._mentioned_agents("alice bob alice sam", [agent1, agent2, agent3]), sort_keys=True)
== '{"alice": 2, "bob": 1, "sam": 1}'
)
assert (
json.dumps(groupchat._mentioned_agents("alice bob alice sam robert", [agent1, agent2, agent3]), sort_keys=True)
== '{"alice": 2, "bob": 1, "sam": 1}'
)
# Substring
assert (
json.dumps(groupchat._mentioned_agents("sam samantha basam asami", [agent1, agent2, agent3]), sort_keys=True)
== '{"sam": 1}'
)
# Word boundaries
assert (
json.dumps(groupchat._mentioned_agents("alice! .alice. .alice", [agent1, agent2, agent3]), sort_keys=True)
== '{"alice": 3}'
)
# Special characters in agent names
agent4 = autogen.ConversableAgent(
".*",
max_consecutive_auto_reply=2,
human_input_mode="NEVER",
llm_config=False,
default_auto_reply="Match everything.",
)
groupchat = autogen.GroupChat(agents=[agent1, agent2, agent3, agent4], messages=[], max_round=2)
assert (
json.dumps(
groupchat._mentioned_agents("alice bob alice sam robert .*", [agent1, agent2, agent3, agent4]),
sort_keys=True,
)
== '{".*": 1, "alice": 2, "bob": 1, "sam": 1}'
)
def test_termination():
agent1 = autogen.ConversableAgent(
"alice",
max_consecutive_auto_reply=10,
human_input_mode="NEVER",
llm_config=False,
default_auto_reply="This is alice speaking.",
)
agent2 = autogen.ConversableAgent(
"bob",
max_consecutive_auto_reply=10,
human_input_mode="NEVER",
llm_config=False,
default_auto_reply="This is bob speaking.",
)
agent3 = autogen.ConversableAgent(
"sam",
max_consecutive_auto_reply=10,
human_input_mode="NEVER",
llm_config=False,
default_auto_reply="This is sam speaking. TERMINATE",
)
# Test empty is_termination_msg function
groupchat = autogen.GroupChat(
agents=[agent1, agent2, agent3], messages=[], speaker_selection_method="round_robin", max_round=10
)
group_chat_manager = autogen.GroupChatManager(groupchat=groupchat, llm_config=False, is_termination_msg=None)
agent1.initiate_chat(group_chat_manager, message="'None' is_termination_msg function.")
assert len(groupchat.messages) == 10
# Test user-provided is_termination_msg function
agent1.reset()
agent2.reset()
agent3.reset()
groupchat = autogen.GroupChat(
agents=[agent1, agent2, agent3], messages=[], speaker_selection_method="round_robin", max_round=10
)
group_chat_manager = autogen.GroupChatManager(
groupchat=groupchat,
llm_config=False,
is_termination_msg=lambda x: x.get("content", "").rstrip().find("TERMINATE") >= 0,
)
agent1.initiate_chat(group_chat_manager, message="User-provided is_termination_msg function.")
assert len(groupchat.messages) == 3
def test_next_agent():
def create_agent(name: str) -> autogen.ConversableAgent:
return autogen.ConversableAgent(
name,
max_consecutive_auto_reply=10,
human_input_mode="NEVER",
llm_config=False,
default_auto_reply=f"This is {name} speaking.",
)
agent1 = create_agent("alice")
agent2 = create_agent("bob")
agent3 = create_agent("sam")
agent4 = create_agent("sally")
agent5 = create_agent("samantha")
agent6 = create_agent("robert")
# Test empty is_termination_msg function
groupchat = autogen.GroupChat(
agents=[agent1, agent2, agent3], messages=[], speaker_selection_method="round_robin", max_round=10
)
assert groupchat.next_agent(agent1, [agent1, agent2, agent3]) == agent2
assert groupchat.next_agent(agent2, [agent1, agent2, agent3]) == agent3
assert groupchat.next_agent(agent3, [agent1, agent2, agent3]) == agent1
assert groupchat.next_agent(agent1) == agent2
assert groupchat.next_agent(agent2) == agent3
assert groupchat.next_agent(agent3) == agent1
assert groupchat.next_agent(agent1, [agent1, agent3]) == agent3
assert groupchat.next_agent(agent3, [agent1, agent3]) == agent1
assert groupchat.next_agent(agent2, [agent1, agent3]) == agent3
assert groupchat.next_agent(agent4, [agent1, agent3]) == agent1
assert groupchat.next_agent(agent4, [agent1, agent2, agent3]) == agent1
with pytest.raises(UndefinedNextAgent):
groupchat.next_agent(agent4, [agent5, agent6])
def test_send_intros():
agent1 = autogen.ConversableAgent(
"alice",
description="The first agent.",
max_consecutive_auto_reply=10,
human_input_mode="NEVER",
llm_config=False,
default_auto_reply="This is alice speaking. TERMINATE",
)
agent2 = autogen.ConversableAgent(
"bob",
description="The second agent.",
max_consecutive_auto_reply=10,
human_input_mode="NEVER",
llm_config=False,
default_auto_reply="This is bob speaking. TERMINATE",
)
agent3 = autogen.ConversableAgent(
"sam",
description="The third agent.",
max_consecutive_auto_reply=10,
human_input_mode="NEVER",
llm_config=False,
default_auto_reply="This is sam speaking. TERMINATE",
)
agent4 = autogen.ConversableAgent(
"sally",
description="The fourth agent.",
max_consecutive_auto_reply=10,
human_input_mode="NEVER",
llm_config=False,
default_auto_reply="This is sally speaking. TERMINATE",
)
# Test empty is_termination_msg function
groupchat = autogen.GroupChat(
agents=[agent1, agent2, agent3],
messages=[],
speaker_selection_method="round_robin",
max_round=10,
send_introductions=True,
)
intro = groupchat.introductions_msg()
assert "The first agent." in intro
assert "The second agent." in intro
assert "The third agent." in intro
assert "The fourth agent." not in intro
intro = groupchat.introductions_msg([agent1, agent2, agent4])
assert "The first agent." in intro
assert "The second agent." in intro
assert "The third agent." not in intro
assert "The fourth agent." in intro
groupchat = autogen.GroupChat(
agents=[agent1, agent2, agent3],
messages=[],
speaker_selection_method="round_robin",
max_round=10,
send_introductions=True,
)
group_chat_manager = autogen.GroupChatManager(
groupchat=groupchat,
llm_config=False,
is_termination_msg=lambda x: x.get("content", "").rstrip().find("TERMINATE") >= 0,
)
group_chat_manager.initiate_chat(group_chat_manager, message="The initiating message.")
for a in [agent1, agent2, agent3]:
messages = agent1.chat_messages[group_chat_manager]
assert len(messages) == 3
assert "The first agent." in messages[0]["content"]
assert "The second agent." in messages[0]["content"]
assert "The third agent." in messages[0]["content"]
assert "The initiating message." == messages[1]["content"]
assert messages[2]["content"] == agent1._default_auto_reply
# Reset and start again
agent1.reset()
agent2.reset()
agent3.reset()
agent4.reset()
# Check the default (no introductions)
groupchat2 = autogen.GroupChat(
agents=[agent1, agent2, agent3], messages=[], speaker_selection_method="round_robin", max_round=10
)
group_chat_manager2 = autogen.GroupChatManager(
groupchat=groupchat2,
llm_config=False,
is_termination_msg=lambda x: x.get("content", "").rstrip().find("TERMINATE") >= 0,
)
group_chat_manager2.initiate_chat(group_chat_manager2, message="The initiating message.")
for a in [agent1, agent2, agent3]:
messages = agent1.chat_messages[group_chat_manager2]
assert len(messages) == 2
assert "The initiating message." == messages[0]["content"]
assert messages[1]["content"] == agent1._default_auto_reply
def test_selection_helpers():
agent1 = autogen.ConversableAgent(
"alice",
max_consecutive_auto_reply=10,
human_input_mode="NEVER",
llm_config=False,
default_auto_reply="This is alice speaking.",
description="Alice is an AI agent.",
)
agent2 = autogen.ConversableAgent(
"bob",
max_consecutive_auto_reply=10,
human_input_mode="NEVER",
llm_config=False,
description="Bob is an AI agent.",
)
agent3 = autogen.ConversableAgent(
"sam",
max_consecutive_auto_reply=10,
human_input_mode="NEVER",
llm_config=False,
default_auto_reply="This is sam speaking.",
system_message="Sam is an AI agent.",
)
# Test empty is_termination_msg function
groupchat = autogen.GroupChat(
agents=[agent1, agent2, agent3], messages=[], speaker_selection_method="round_robin", max_round=10
)
select_speaker_msg = groupchat.select_speaker_msg()
select_speaker_prompt = groupchat.select_speaker_prompt()
assert "Alice is an AI agent." in select_speaker_msg
assert "Bob is an AI agent." in select_speaker_msg
assert "Sam is an AI agent." in select_speaker_msg
assert str(["Alice", "Bob", "Sam"]).lower() in select_speaker_prompt.lower()
with mock.patch.object(builtins, "input", lambda _: "1"):
groupchat.manual_select_speaker()
def test_init_default_parameters():
agents = [autogen.ConversableAgent(name=f"Agent{i}", llm_config=False) for i in range(3)]
group_chat = GroupChat(agents=agents, messages=[], max_round=3)
for agent in agents:
assert set([a.name for a in group_chat.allowed_speaker_transitions_dict[agent]]) == set(
[a.name for a in agents]
)
def test_graph_parameters():
agents = [autogen.ConversableAgent(name=f"Agent{i}", llm_config=False) for i in range(3)]
with pytest.raises(ValueError):
GroupChat(
agents=agents,
messages=[],
max_round=3,
allowed_or_disallowed_speaker_transitions={agents[0]: [agents[1]], agents[1]: [agents[2]]},
)
with pytest.raises(ValueError):
GroupChat(
agents=agents,
messages=[],
max_round=3,
allow_repeat_speaker=False, # should be None
allowed_or_disallowed_speaker_transitions={agents[0]: [agents[1]], agents[1]: [agents[2]]},
)
with pytest.raises(ValueError):
GroupChat(
agents=agents,
messages=[],
max_round=3,
allow_repeat_speaker=None,
allowed_or_disallowed_speaker_transitions={agents[0]: [agents[1]], agents[1]: [agents[2]]},
speaker_transitions_type="a",
)
group_chat = GroupChat(
agents=agents,
messages=[],
max_round=3,
allowed_or_disallowed_speaker_transitions={agents[0]: [agents[1]], agents[1]: [agents[2]]},
speaker_transitions_type="allowed",
)
assert "Agent0" in group_chat.agent_names
def test_graceful_exit_before_max_round():
agent1 = autogen.ConversableAgent(
"alice",
max_consecutive_auto_reply=10,
human_input_mode="NEVER",
llm_config=False,
default_auto_reply="This is alice speaking.",
)
agent2 = autogen.ConversableAgent(
"bob",
max_consecutive_auto_reply=10,
human_input_mode="NEVER",
llm_config=False,
default_auto_reply="This is bob speaking.",
)
agent3 = autogen.ConversableAgent(
"sam",
max_consecutive_auto_reply=10,
human_input_mode="NEVER",
llm_config=False,
default_auto_reply="This is sam speaking.",
)
# This speaker_transitions limits the transition to be only from agent1 to agent2, and from agent2 to agent3 and end.
allowed_or_disallowed_speaker_transitions = {agent1: [agent2], agent2: [agent3]}
# Test empty is_termination_msg function
groupchat = autogen.GroupChat(
agents=[agent1, agent2, agent3],
messages=[],
speaker_selection_method="round_robin",
max_round=10,
allow_repeat_speaker=None,
allowed_or_disallowed_speaker_transitions=allowed_or_disallowed_speaker_transitions,
speaker_transitions_type="allowed",
)
group_chat_manager = autogen.GroupChatManager(groupchat=groupchat, llm_config=False, is_termination_msg=None)
agent1.initiate_chat(group_chat_manager, message="")
# Note that 3 is much lower than 10 (max_round), so the conversation should end before 10 rounds.
assert len(groupchat.messages) == 3
def test_clear_agents_history():
agent1 = autogen.ConversableAgent(
"alice",
max_consecutive_auto_reply=10,
human_input_mode="NEVER",
llm_config=False,
default_auto_reply="This is alice speaking.",
)
agent2 = autogen.ConversableAgent(
"bob",
max_consecutive_auto_reply=10,
human_input_mode="NEVER",
llm_config=False,
default_auto_reply="This is bob speaking.",
)
agent3 = autogen.ConversableAgent(
"sam",
max_consecutive_auto_reply=10,
human_input_mode="ALWAYS",
llm_config=False,
)
groupchat = autogen.GroupChat(agents=[agent1, agent2, agent3], messages=[], max_round=3, enable_clear_history=True)
group_chat_manager = autogen.GroupChatManager(groupchat=groupchat, llm_config=False)
# testing pure "clear history" statement
with mock.patch.object(builtins, "input", lambda _: "clear history. How you doing?"):
res = agent1.initiate_chat(group_chat_manager, message="hello", summary_method="last_msg")
agent1_history = list(agent1._oai_messages.values())[0]
agent2_history = list(agent2._oai_messages.values())[0]
assert agent1_history == [{"content": "How you doing?", "name": "sam", "role": "user"}]
assert agent2_history == [{"content": "How you doing?", "name": "sam", "role": "user"}]
assert groupchat.messages == [{"content": "How you doing?", "name": "sam", "role": "user"}]
print("Chat summary", res.summary)
print("Chat cost", res.cost)
print("Chat history", res.chat_history)
# testing clear history for defined agent
with mock.patch.object(builtins, "input", lambda _: "clear history bob. How you doing?"):
agent1.initiate_chat(group_chat_manager, message="hello")
agent1_history = list(agent1._oai_messages.values())[0]
agent2_history = list(agent2._oai_messages.values())[0]
assert agent1_history == [
{"content": "hello", "role": "assistant", "name": "alice"},
{"content": "This is bob speaking.", "name": "bob", "role": "user"},
{"content": "How you doing?", "name": "sam", "role": "user"},
]
assert agent2_history == [{"content": "How you doing?", "name": "sam", "role": "user"}]
assert groupchat.messages == [
{"content": "hello", "role": "user", "name": "alice"},
{"content": "This is bob speaking.", "name": "bob", "role": "user"},
{"content": "How you doing?", "name": "sam", "role": "user"},
]
# testing clear history with defined nr of messages to preserve
with mock.patch.object(builtins, "input", lambda _: "clear history 1. How you doing?"):
agent1.initiate_chat(group_chat_manager, message="hello")
agent1_history = list(agent1._oai_messages.values())[0]
agent2_history = list(agent2._oai_messages.values())[0]
assert agent1_history == [
{"content": "This is bob speaking.", "name": "bob", "role": "user"},
{"content": "How you doing?", "name": "sam", "role": "user"},
]
assert agent2_history == [
{"content": "This is bob speaking.", "role": "assistant", "name": "bob"},
{"content": "How you doing?", "name": "sam", "role": "user"},
]
assert groupchat.messages == [
{"content": "This is bob speaking.", "role": "user", "name": "bob"},
{"content": "How you doing?", "role": "user", "name": "sam"},
]
# testing clear history with defined agent and nr of messages to preserve
with mock.patch.object(builtins, "input", lambda _: "clear history bob 1. How you doing?"):
agent1.initiate_chat(group_chat_manager, message="hello")
agent1_history = list(agent1._oai_messages.values())[0]
agent2_history = list(agent2._oai_messages.values())[0]
assert agent1_history == [
{"content": "hello", "role": "assistant", "name": "alice"},
{"content": "This is bob speaking.", "name": "bob", "role": "user"},
{"content": "How you doing?", "name": "sam", "role": "user"},
]
assert agent2_history == [
{"content": "This is bob speaking.", "role": "assistant", "name": "bob"},
{"content": "How you doing?", "name": "sam", "role": "user"},
]
assert groupchat.messages == [
{"content": "hello", "name": "alice", "role": "user"},
{"content": "This is bob speaking.", "name": "bob", "role": "user"},
{"content": "How you doing?", "name": "sam", "role": "user"},
]
# testing saving tool_call message when clear history going to remove it leaving only tool_response message
agent1.reset()
agent2.reset()
agent3.reset()
# we want to broadcast the message only in the preparation.
groupchat = autogen.GroupChat(agents=[agent1, agent2, agent3], messages=[], max_round=1, enable_clear_history=True)
group_chat_manager = autogen.GroupChatManager(groupchat=groupchat, llm_config=False)
# We want to trigger the broadcast of group chat manager, which requires `request_reply` to be set to True.
agent1.send("dummy message", group_chat_manager, request_reply=True)
agent1.send(
{
"content": None,
"role": "assistant",
"function_call": None,
"tool_calls": [
{"id": "call_test_id", "function": {"arguments": "", "name": "test_tool"}, "type": "function"}
],
},
group_chat_manager,
request_reply=True,
)
agent1.send(
{
"role": "tool",
"tool_responses": [{"tool_call_id": "call_emulated", "role": "tool", "content": "example tool response"}],
"content": "example tool response",
},
group_chat_manager,
request_reply=True,
)
# increase max_round to 3
groupchat.max_round = 3
group_chat_manager = autogen.GroupChatManager(groupchat=groupchat, llm_config=False)
with mock.patch.object(builtins, "input", lambda _: "clear history alice 1. How you doing?"):
agent1.initiate_chat(group_chat_manager, message="hello", clear_history=False)
agent1_history = list(agent1._oai_messages.values())[0]
assert agent1_history == [
{
"tool_calls": [
{"id": "call_test_id", "function": {"arguments": "", "name": "test_tool"}, "type": "function"},
],
"content": None,
"role": "assistant",
},
{
"content": "example tool response",
"tool_responses": [{"tool_call_id": "call_emulated", "role": "tool", "content": "example tool response"}],
"role": "tool",
"name": "alice",
},
]
# testing clear history called from tool response
agent1.reset()
agent2.reset()
agent3.reset()
agent2 = autogen.ConversableAgent(
"bob",
max_consecutive_auto_reply=10,
human_input_mode="NEVER",
llm_config=False,
default_auto_reply={
"role": "tool",
"tool_responses": [{"tool_call_id": "call_emulated", "role": "tool", "content": "USER INTERRUPTED"}],
"content": "Clear history. How you doing?",
},
)
groupchat = autogen.GroupChat(agents=[agent1, agent2, agent3], messages=[], max_round=1, enable_clear_history=True)
group_chat_manager = autogen.GroupChatManager(groupchat=groupchat, llm_config=False)
agent1.send("dummy message", group_chat_manager, request_reply=True)
agent1.send(
{
"content": None,
"role": "assistant",
"function_call": None,
"tool_calls": [
{"id": "call_test_id", "function": {"arguments": "", "name": "test_tool"}, "type": "function"}
],
},
group_chat_manager,
request_reply=True,
)
groupchat.max_round = 2
group_chat_manager = autogen.GroupChatManager(groupchat=groupchat, llm_config=False)
agent1.initiate_chat(group_chat_manager, message="hello")
agent1_history = list(agent1._oai_messages.values())[0]
assert agent1_history == [
{
"tool_calls": [
{"id": "call_test_id", "function": {"arguments": "", "name": "test_tool"}, "type": "function"},
],
"content": None,
"role": "assistant",
},
]
def test_get_agent_by_name():
def agent(name: str) -> autogen.ConversableAgent:
return autogen.ConversableAgent(
name=name,
max_consecutive_auto_reply=10,
human_input_mode="NEVER",
llm_config=False,
)
def team(members: List[autogen.Agent], name: str) -> autogen.Agent:
gc = autogen.GroupChat(agents=members, messages=[])
return autogen.GroupChatManager(groupchat=gc, name=name, llm_config=False)
team_member1 = agent("team1_member1")
team_member2 = agent("team1_member2")
team_dup_member1 = agent("team1_member1")
team_dup_member2 = agent("team1_member2")
user = agent("user")
team1 = team([team_member1, team_member2], "team1")
team1_duplicate = team([team_dup_member1, team_dup_member2], "team1")
gc = autogen.GroupChat(agents=[user, team1, team1_duplicate], messages=[])
# Testing default arguments
assert gc.agent_by_name("user") == user
assert gc.agent_by_name("team1") == team1 or gc.agent_by_name("team1") == team1_duplicate
# Testing recursive search
assert gc.agent_by_name("user", recursive=True) == user
assert (
gc.agent_by_name("team1_member1", recursive=True) == team_member1
or gc.agent_by_name("team1_member1", recursive=True) == team_dup_member1
)
# Get agent that does not exist
assert gc.agent_by_name("team2") is None
assert gc.agent_by_name("team2", recursive=True) is None
assert gc.agent_by_name("team2", raise_on_name_conflict=True) is None
assert gc.agent_by_name("team2", recursive=True, raise_on_name_conflict=True) is None
# Testing naming conflict
with pytest.raises(AgentNameConflict):
gc.agent_by_name("team1", raise_on_name_conflict=True)
# Testing name conflict with recursive search
with pytest.raises(AgentNameConflict):
gc.agent_by_name("team1_member1", recursive=True, raise_on_name_conflict=True)
def test_get_nested_agents_in_groupchat():
def agent(name: str) -> autogen.ConversableAgent:
return autogen.ConversableAgent(
name=name,
max_consecutive_auto_reply=10,
human_input_mode="NEVER",
llm_config=False,
)
def team(name: str) -> autogen.ConversableAgent:
member1 = agent(f"member1_{name}")
member2 = agent(f"member2_{name}")
gc = autogen.GroupChat(agents=[member1, member2], messages=[])
return autogen.GroupChatManager(groupchat=gc, name=name, llm_config=False)
user = agent("user")
team1 = team("team1")
team2 = team("team2")
gc = autogen.GroupChat(agents=[user, team1, team2], messages=[])
agents = gc.nested_agents()
assert len(agents) == 7
def test_nested_teams_chat():
"""Tests chat capabilities of nested teams"""
team1_msg = {"content": "Hello from team 1"}
team2_msg = {"content": "Hello from team 2"}
def agent(name: str, auto_reply: Optional[Dict[str, Any]] = None) -> autogen.ConversableAgent:
return autogen.ConversableAgent(
name=name,
max_consecutive_auto_reply=10,
human_input_mode="NEVER",
llm_config=False,
default_auto_reply=auto_reply,
)
def team(name: str, auto_reply: Optional[Dict[str, Any]] = None) -> autogen.ConversableAgent:
member1 = agent(f"member1_{name}", auto_reply=auto_reply)
member2 = agent(f"member2_{name}", auto_reply=auto_reply)
gc = autogen.GroupChat(agents=[member1, member2], messages=[])
return autogen.GroupChatManager(groupchat=gc, name=name, llm_config=False)
def chat(gc_manager: autogen.GroupChatManager):
team1_member1 = gc_manager.groupchat.agent_by_name("member1_team1", recursive=True)
team2_member2 = gc_manager.groupchat.agent_by_name("member2_team2", recursive=True)
assert team1_member1 is not None
assert team2_member2 is not None
team1_member1.send(team1_msg, team2_member2, request_reply=True)
user = agent("user")
team1 = team("team1", auto_reply=team1_msg)
team2 = team("team2", auto_reply=team2_msg)
gc = autogen.GroupChat(agents=[user, team1, team2], messages=[])
gc_manager = autogen.GroupChatManager(groupchat=gc, llm_config=False)
chat(gc_manager)
team1_member1 = gc.agent_by_name("member1_team1", recursive=True)
team2_member2 = gc.agent_by_name("member2_team2", recursive=True)
assert team1_member1 and team2_member2
msg = team1_member1.chat_messages[team2_member2][0]
reply = team1_member1.chat_messages[team2_member2][1]
assert msg["content"] == team1_msg["content"]
assert reply["content"] == team2_msg["content"]
def test_custom_speaker_selection():
a1 = autogen.UserProxyAgent(
name="a1",
default_auto_reply="This is a1 speaking.",
human_input_mode="NEVER",
code_execution_config={},
)
a2 = autogen.UserProxyAgent(
name="a2",
default_auto_reply="This is a2 speaking.",
human_input_mode="NEVER",
code_execution_config={},
)
a3 = autogen.UserProxyAgent(
name="a3",
default_auto_reply="TERMINATE",
human_input_mode="NEVER",
code_execution_config={},
)
def custom_speaker_selection_func(last_speaker: Agent, groupchat: GroupChat) -> Agent:
"""Define a customized speaker selection function.
A recommended way is to define a transition for each speaker using the groupchat allowed_or_disallowed_speaker_transitions parameter.
"""
if last_speaker is a1:
return a2
elif last_speaker is a2:
return a3
groupchat = autogen.GroupChat(
agents=[a1, a2, a3],
messages=[],
max_round=20,
speaker_selection_method=custom_speaker_selection_func,
)
manager = autogen.GroupChatManager(groupchat=groupchat, llm_config=False)
result = a1.initiate_chat(manager, message="Hello, this is a1 speaking.")
assert len(result.chat_history) == 3
def test_custom_speaker_selection_with_transition_graph():
"""
In this test, although speaker_selection_method is defined, the speaker transitions are also defined.
There are 26 agents here, a to z.
The speaker transitions are defined such that the agents can transition to the next alphabet.
In addition, because we want the transition order to be a,u,t,o,g,e,n, we also define the speaker transitions for these agents.
The speaker_selection_method is defined to return the next agent in the expected sequence.
"""
# For loop that creates UserProxyAgent with names from a to z
agents = [
autogen.UserProxyAgent(
name=chr(97 + i),
default_auto_reply=f"My name is {chr(97 + i)}",
human_input_mode="NEVER",
code_execution_config={},
)
for i in range(26)
]
# Initiate allowed speaker transitions
allowed_or_disallowed_speaker_transitions = {}
# Each agent can transition to the next alphabet as a baseline
# Key is Agent, value is a list of Agents that the key Agent can transition to
for i in range(25):
allowed_or_disallowed_speaker_transitions[agents[i]] = [agents[i + 1]]
# The test is to make sure that the agent sequence is a,u,t,o,g,e,n, so we need to add those transitions
expected_sequence = ["a", "u", "t", "o", "g", "e", "n"]
current_agent = None
previous_agent = None
for char in expected_sequence:
# convert char to i so that we can use chr(97+i)
current_agent = agents[ord(char) - 97]
if previous_agent is not None:
# Add transition
allowed_or_disallowed_speaker_transitions[previous_agent].append(current_agent)
previous_agent = current_agent
def custom_speaker_selection_func(last_speaker: Agent, groupchat: GroupChat) -> Optional[Agent]:
"""
Define a customized speaker selection function.
"""
expected_sequence = ["a", "u", "t", "o", "g", "e", "n"]
last_speaker_char = last_speaker.name
# Find the index of last_speaker_char in the expected_sequence
last_speaker_index = expected_sequence.index(last_speaker_char)
# Return the next agent in the expected sequence
if last_speaker_index == len(expected_sequence) - 1:
return None # terminate the conversation
else:
next_agent = agents[ord(expected_sequence[last_speaker_index + 1]) - 97]
return next_agent
groupchat = autogen.GroupChat(
agents=agents,
messages=[],
max_round=20,
speaker_selection_method=custom_speaker_selection_func,
allowed_or_disallowed_speaker_transitions=allowed_or_disallowed_speaker_transitions,
speaker_transitions_type="allowed",
)
manager = autogen.GroupChatManager(groupchat=groupchat, llm_config=False)
results = agents[0].initiate_chat(manager, message="My name is a")
actual_sequence = []
# Append to actual_sequence using results.chat_history[idx]['content'][-1]
for idx in range(len(results.chat_history)):
actual_sequence.append(results.chat_history[idx]["content"][-1]) # append the last character of the content
assert expected_sequence == actual_sequence
def test_custom_speaker_selection_overrides_transition_graph():
"""
In this test, team A engineer can transition to team A executor and team B engineer, but team B engineer cannot transition to team A executor.
The expected behaviour is that the custom speaker selection function will override the constraints of the graph.
"""
# For loop that creates UserProxyAgent with names from a to z
agents = [
autogen.UserProxyAgent(
name="teamA_engineer",
default_auto_reply="My name is teamA_engineer",
human_input_mode="NEVER",
code_execution_config={},
),
autogen.UserProxyAgent(
name="teamA_executor",
default_auto_reply="My name is teamA_executor",
human_input_mode="NEVER",
code_execution_config={},
),
autogen.UserProxyAgent(
name="teamB_engineer",
default_auto_reply="My name is teamB_engineer",
human_input_mode="NEVER",
code_execution_config={},
),
]
allowed_or_disallowed_speaker_transitions = {}
# teamA_engineer can transition to teamA_executor and teamB_engineer
# teamB_engineer can transition to no one
allowed_or_disallowed_speaker_transitions[agents[0]] = [agents[1], agents[2]]
def custom_speaker_selection_func(last_speaker: Agent, groupchat: GroupChat) -> Optional[Agent]:
if last_speaker.name == "teamA_engineer":
return agents[2] # Goto teamB_engineer
elif last_speaker.name == "teamB_engineer":
return agents[1] # Goto teamA_executor and contradict the graph
groupchat = autogen.GroupChat(
agents=agents,
messages=[],
max_round=20,
speaker_selection_method=custom_speaker_selection_func,
allowed_or_disallowed_speaker_transitions=allowed_or_disallowed_speaker_transitions,
speaker_transitions_type="allowed",
)
manager = autogen.GroupChatManager(groupchat=groupchat, llm_config=False)
results = agents[0].initiate_chat(manager, message="My name is teamA_engineer")
speakers = []
for idx in range(len(results.chat_history)):
speakers.append(results.chat_history[idx].get("name"))
assert "teamA_executor" in speakers
def test_role_for_select_speaker_messages():
agent1 = autogen.ConversableAgent(
"alice",
max_consecutive_auto_reply=10,
human_input_mode="NEVER",
llm_config=False,
default_auto_reply="This is alice speaking.",
)
agent2 = autogen.ConversableAgent(
"bob",
max_consecutive_auto_reply=10,
human_input_mode="NEVER",
llm_config=False,
default_auto_reply="This is bob speaking.",
)
groupchat = autogen.GroupChat(
agents=[agent1, agent2],
messages=[{"role": "user", "content": "Let's have a chat!"}],
max_round=3,
role_for_select_speaker_messages="system",
)
# Replicate the _auto_select_speaker nested chat.
# Agent for checking the response from the speaker_select_agent
checking_agent = autogen.ConversableAgent("checking_agent")
# Agent for selecting a single agent name from the response
speaker_selection_agent = autogen.ConversableAgent(
"speaker_selection_agent",
llm_config=None,
human_input_mode="NEVER", # Suppresses some extra terminal outputs, outputs will be handled by select_speaker_auto_verbose
)
# The role_for_select_speaker_message is put into the initiate_chat of the nested two-way chat
# into a message attribute called 'override_role'. This is evaluated in Conversable Agent's _append_oai_message function
# e.g.: message={'content':self.select_speaker_prompt(agents),'override_role':self.role_for_select_speaker_messages},
message = {"content": "A prompt goes here.", "override_role": groupchat.role_for_select_speaker_messages}
checking_agent._append_oai_message(message, "assistant", speaker_selection_agent, is_sending=True)
# Test default is "system"
assert len(checking_agent.chat_messages) == 1
assert checking_agent.chat_messages[speaker_selection_agent][-1]["role"] == "system"
# Test as "user"
groupchat.role_for_select_speaker_messages = "user"
message = {"content": "A prompt goes here.", "override_role": groupchat.role_for_select_speaker_messages}
checking_agent._append_oai_message(message, "assistant", speaker_selection_agent, is_sending=True)
assert len(checking_agent.chat_messages) == 1
assert checking_agent.chat_messages[speaker_selection_agent][-1]["role"] == "user"
# Test as something unusual
groupchat.role_for_select_speaker_messages = "SockS"
message = {"content": "A prompt goes here.", "override_role": groupchat.role_for_select_speaker_messages}
checking_agent._append_oai_message(message, "assistant", speaker_selection_agent, is_sending=True)
assert len(checking_agent.chat_messages) == 1
assert checking_agent.chat_messages[speaker_selection_agent][-1]["role"] == "SockS"
# Test empty string and None isn't accepted
# Test with empty strings
with pytest.raises(ValueError) as e:
groupchat = autogen.GroupChat(
agents=[agent1, agent2],
messages=[{"role": "user", "content": "Let's have a chat!"}],
max_round=3,
role_for_select_speaker_messages="",
)
assert "role_for_select_speaker_messages cannot be empty or None." in str(e.value)
with pytest.raises(ValueError) as e:
groupchat = autogen.GroupChat(
agents=[agent1, agent2],
messages=[{"role": "user", "content": "Let's have a chat!"}],
max_round=3,
role_for_select_speaker_messages=None,
)
assert "role_for_select_speaker_messages cannot be empty or None." in str(e.value)
def test_select_speaker_message_and_prompt_templates():
"""
In this test, two agents are part of a group chat which has customized select speaker message and select speaker prompt templates. Both valid and empty string values will be used.
The expected behaviour is that the customized speaker selection message and prompts will override the default values or throw exceptions if empty.
"""
agent1 = autogen.ConversableAgent(
"Alice",
description="A wonderful employee named Alice.",
human_input_mode="NEVER",
llm_config=False,
)
agent2 = autogen.ConversableAgent(
"Bob",
description="An amazing employee named Bob.",
human_input_mode="NEVER",
llm_config=False,
)
# Customised message, this is always the first message in the context
custom_msg = """You are the CEO of a niche organisation creating small software tools for the healthcare sector with a small team of specialists. Call them in sequence.
The job roles and responsibilities are:
{roles}
You must select only from {agentlist}."""
# Customised prompt, this is always the last message in the context
custom_prompt = """Read the above conversation.
Then select the next job role from {agentlist} to take action.
RETURN ONLY THE NAME OF THE NEXT ROLE."""
# Test empty is_termination_msg function
groupchat = autogen.GroupChat(
agents=[agent1, agent2],
messages=[],
speaker_selection_method="auto",
max_round=10,
select_speaker_message_template=custom_msg,
select_speaker_prompt_template=custom_prompt,
)
# Test with valid strings, checking for the correct string and roles / agentlist to be included
assert groupchat.select_speaker_msg() == custom_msg.replace(
"{roles}", "Alice: A wonderful employee named Alice.\nBob: An amazing employee named Bob."
).replace("{agentlist}", "['Alice', 'Bob']")
assert groupchat.select_speaker_prompt() == custom_prompt.replace("{agentlist}", "['Alice', 'Bob']")
# Test with empty strings
with pytest.raises(ValueError, match="select_speaker_message_template cannot be empty or None."):
groupchat = autogen.GroupChat(
agents=[agent1, agent2],
messages=[],
speaker_selection_method="auto",
max_round=10,
select_speaker_message_template="",
select_speaker_prompt_template="Not empty.",
)
# Will not throw an exception, prompt can be empty/None (empty is converted to None)
groupchat = autogen.GroupChat(
agents=[agent1, agent2],
messages=[],
speaker_selection_method="auto",
max_round=10,
select_speaker_message_template="Not empty.",
select_speaker_prompt_template="",
)
assert groupchat.select_speaker_prompt_template is None
# Test with None
with pytest.raises(ValueError, match="select_speaker_message_template cannot be empty or None."):
groupchat = autogen.GroupChat(
agents=[agent1, agent2],
messages=[],
speaker_selection_method="auto",
max_round=10,
select_speaker_message_template=None,
select_speaker_prompt_template="Not empty.",
)
# Will not throw an exception, prompt can be empty/None (empty is converted to None)
groupchat = autogen.GroupChat(
agents=[agent1, agent2],
messages=[],
speaker_selection_method="auto",
max_round=10,
select_speaker_message_template="Not empty.",
select_speaker_prompt_template=None,
)
assert groupchat.select_speaker_prompt_template is None
def test_speaker_selection_agent_name_match():
"""
In this test a group chat, with auto speaker selection, the speaker name match
function is tested against the extended name match regex.
"""
user_proxy = autogen.UserProxyAgent(
name="User_proxy",
system_message="A human admin.",
code_execution_config=False,
human_input_mode="NEVER",
)
storywriter = autogen.AssistantAgent(
name="Story_writer",
system_message="An ideas person.",
llm_config=None,
)
pm = autogen.AssistantAgent(
name="Product_manager",
system_message="Great in evaluating story ideas.",
llm_config=None,
)
all_agents = [user_proxy, storywriter, pm]
groupchat = autogen.GroupChat(agents=all_agents, messages=[], max_round=8, speaker_selection_method="auto")
# Test exact match (unchanged outcome)
result = groupchat._mentioned_agents(agents=all_agents, message_content="Story_writer")
assert result == {"Story_writer": 1}
# Test match with extra text (unchanged outcome)
result = groupchat._mentioned_agents(
agents=all_agents,
message_content="' Story_writer.\n\nHere are three story ideas for Grade 3 kids:\n\n1. **The Adventure of the Magic Garden:** A you...",
)
assert result == {"Story_writer": 1}
# Test match with escaped underscore (new outcome)
result = groupchat._mentioned_agents(agents=all_agents, message_content="Story\\_writer")
assert result == {"Story_writer": 1}
# Test match with space (new outcome)
result = groupchat._mentioned_agents(agents=all_agents, message_content="Story writer")
assert result == {"Story_writer": 1}
# Test match with different casing (unchanged outcome)
result = groupchat._mentioned_agents(agents=all_agents, message_content="Story_Writer")
assert result == {}
# Test match with invalid name (unchanged outcome)
result = groupchat._mentioned_agents(agents=all_agents, message_content="NoName_Person")
assert result == {}
# Test match with no name (unchanged outcome)
result = groupchat._mentioned_agents(agents=all_agents, message_content="")
assert result == {}
# Test match with multiple agents and exact matches (unchanged outcome)
result = groupchat._mentioned_agents(
agents=all_agents, message_content="Story_writer will follow the Product_manager."
)
assert result == {"Story_writer": 1, "Product_manager": 1}
# Test match with multiple agents and escaped underscores (new outcome)
result = groupchat._mentioned_agents(
agents=all_agents, message_content="Story\\_writer will follow the Product\\_manager."
)
assert result == {"Story_writer": 1, "Product_manager": 1}
# Test match with multiple agents and escaped underscores (new outcome)
result = groupchat._mentioned_agents(
agents=all_agents, message_content="Story\\_writer will follow the Product\\_manager."
)
assert result == {"Story_writer": 1, "Product_manager": 1}
# Test match with multiple agents and spaces (new outcome)
result = groupchat._mentioned_agents(
agents=all_agents, message_content="Story writer will follow the Product manager."
)
assert result == {"Story_writer": 1, "Product_manager": 1}
# Test match with multiple agents and escaped underscores and spaces mixed (new outcome)
result = groupchat._mentioned_agents(
agents=all_agents, message_content="Story writer will follow the Product\\_manager."
)
assert result == {"Story_writer": 1, "Product_manager": 1}
# Test match with multiple agents and incorrect casing (unchanged outcome)
result = groupchat._mentioned_agents(
agents=all_agents, message_content="Story Writer will follow the product\\_manager."
)
assert result == {}
def test_role_for_reflection_summary():
llm_config = {"config_list": [{"model": "mock", "api_key": "mock"}]}
agent1 = autogen.ConversableAgent(
"alice",
max_consecutive_auto_reply=10,
human_input_mode="NEVER",
llm_config=False,
default_auto_reply="This is alice speaking.",
)
agent2 = autogen.ConversableAgent(
"bob",
max_consecutive_auto_reply=10,
human_input_mode="NEVER",
llm_config=False,
default_auto_reply="This is bob speaking.",
)
groupchat = autogen.GroupChat(
agents=[agent1, agent2], messages=[], max_round=3, speaker_selection_method="round_robin"
)
group_chat_manager = autogen.GroupChatManager(groupchat=groupchat, llm_config=llm_config)
role_name = "user"
with mock.patch.object(
autogen.ConversableAgent, "_generate_oai_reply_from_client"
) as mock_generate_oai_reply_from_client:
mock_generate_oai_reply_from_client.return_value = "Mocked summary"
agent1.initiate_chat(
group_chat_manager,
max_turns=2,
message="hello",
summary_method="reflection_with_llm",
summary_args={"summary_role": role_name},
)
mock_generate_oai_reply_from_client.assert_called_once()
args, kwargs = mock_generate_oai_reply_from_client.call_args
assert kwargs["messages"][-1]["role"] == role_name
def test_speaker_selection_auto_process_result():
"""
Tests the return result of the 2-agent chat used for speaker selection for the auto method.
The last message of the messages passed in will contain a pass or fail.
If passed, the message will contain the name of the correct agent and that agent will be returned.
If failed, the message will contain the reason for failure for the last attempt and the next
agent in the sequence will be returned.
"""
cmo = autogen.ConversableAgent(
name="Chief_Marketing_Officer",
human_input_mode="NEVER",
llm_config=False,
default_auto_reply="This is alice speaking.",
)
pm = autogen.ConversableAgent(
name="Product_Manager",
human_input_mode="NEVER",
llm_config=False,
default_auto_reply="This is bob speaking.",
function_map={"test_func": lambda x: x},
)
agent_list = [cmo, pm]
groupchat = autogen.GroupChat(agents=agent_list, messages=[], max_round=3)
chat_result = autogen.ChatResult(
chat_id=None,
chat_history=[
{
"content": "Let's get this meeting started. First the Product_Manager will create 3 new product ideas.",
"name": "Chairperson",
"role": "assistant",
},
{"content": "You are an expert at finding the next speaker.", "role": "assistant"},
{"content": "Product_Manager", "role": "user"},
{"content": "UPDATED_BELOW", "role": "user"},
],
)
### Agent selected successfully
chat_result.chat_history[3]["content"] = "[AGENT SELECTED]Product_Manager"
# Product_Manager should be returned
assert groupchat._process_speaker_selection_result(chat_result, cmo, agent_list) == pm
### Agent not selected successfully
chat_result.chat_history[3][
"content"
] = "[AGENT SELECTION FAILED]Select speaker attempt #3 of 3 failed as it did not include any agent names."
# The next speaker in the list will be selected, which will be the Product_Manager (as the last speaker is the Chief_Marketing_Officer)
assert groupchat._process_speaker_selection_result(chat_result, cmo, agent_list) == pm
### Invalid result messages, will return the next agent
chat_result.chat_history[3]["content"] = "This text should not be here."
# The next speaker in the list will be selected, which will be the Chief_Marketing_Officer (as the last speaker is the Product_Maanger)
assert groupchat._process_speaker_selection_result(chat_result, pm, agent_list) == cmo
def test_speaker_selection_validate_speaker_name():
"""
Tests the speaker name validation function used to evaluate the return result of the LLM
during speaker selection in 'auto' mode.
Function: _validate_speaker_name
If a single agent name is returned by the LLM, it will add a relevant message to the chat messages and return True, None
If multiple agent names are returned and there are attempts left, it will return a message to be used to prompt the LLM to try again
If multiple agent names are return and there are no attempts left, it will add a relevant message to the chat messages and return True, None
If no agent names are returned and there are attempts left, it will return a message to be used to prompt the LLM to try again
If no agent names are returned and there are no attempts left, it will add a relevant message to the chat messages and return True, None
When returning a message, it will include the 'override_role' key and value to support the GroupChat role_for_select_speaker_messages attribute
"""
# Group Chat setup
cmo = autogen.ConversableAgent(
name="Chief_Marketing_Officer",
human_input_mode="NEVER",
llm_config=False,
default_auto_reply="This is alice speaking.",
)
pm = autogen.ConversableAgent(
name="Product_Manager",
human_input_mode="NEVER",
llm_config=False,
default_auto_reply="This is bob speaking.",
function_map={"test_func": lambda x: x},
)
agent_list = [cmo, pm]
agent_list_string = f"{[agent.name for agent in agent_list]}"
groupchat = autogen.GroupChat(agents=agent_list, messages=[], max_round=3)
# Speaker Selection 2-agent chat setup
# Agent for selecting a single agent name from the response
speaker_selection_agent = autogen.ConversableAgent(
"speaker_selection_agent",
)
# Agent for checking the response from the speaker_select_agent
checking_agent = autogen.ConversableAgent("checking_agent")
# Select speaker messages
select_speaker_messages = [
{
"content": "Let's get this meeting started. First the Product_Manager will create 3 new product ideas.",
"name": "Chairperson",
"role": "assistant",
},
{"content": "You are an expert at finding the next speaker.", "role": "assistant"},
{"content": "UPDATED_BELOW", "role": "user"},
]
### Single agent name returned
attempts_left = 2
attempt = 1
select_speaker_messages[-1]["content"] = "Product_Manager is the next to speak"
result = groupchat._validate_speaker_name(
recipient=checking_agent,
messages=select_speaker_messages,
sender=speaker_selection_agent,
config=None,
attempts_left=attempts_left,
attempt=attempt,
agents=agent_list,
)
assert result == (True, None)
assert select_speaker_messages[-1]["content"] == "[AGENT SELECTED]Product_Manager"
select_speaker_messages.pop(-1) # Remove the last message before the next test
### Multiple agent names returned with attempts left
attempts_left = 2
attempt = 1
select_speaker_messages[-1]["content"] = "Product_Manager must speak after the Chief_Marketing_Officer"
result = groupchat._validate_speaker_name(
recipient=checking_agent,
messages=select_speaker_messages,
sender=speaker_selection_agent,
config=None,
attempts_left=attempts_left,
attempt=attempt,
agents=agent_list,
)
assert result == (
True,
{
"content": groupchat.select_speaker_auto_multiple_template.format(agentlist=agent_list_string),
"name": "checking_agent",
"override_role": groupchat.role_for_select_speaker_messages,
},
)
### Multiple agent names returned with no attempts left
attempts_left = 0
attempt = 1
select_speaker_messages[-1]["content"] = "Product_Manager must speak after the Chief_Marketing_Officer"
result = groupchat._validate_speaker_name(
recipient=checking_agent,
messages=select_speaker_messages,
sender=speaker_selection_agent,
config=None,
attempts_left=attempts_left,
attempt=attempt,
agents=agent_list,
)
assert result == (True, None)
assert (
select_speaker_messages[-1]["content"]
== f"[AGENT SELECTION FAILED]Select speaker attempt #{attempt} of {attempt + attempts_left} failed as it returned multiple names."
)
select_speaker_messages.pop(-1) # Remove the last message before the next test
### No agent names returned with attempts left
attempts_left = 3
attempt = 2
select_speaker_messages[-1]["content"] = "The PM must speak after the CMO"
result = groupchat._validate_speaker_name(
recipient=checking_agent,
messages=select_speaker_messages,
sender=speaker_selection_agent,
config=None,
attempts_left=attempts_left,
attempt=attempt,
agents=agent_list,
)
assert result == (
True,
{
"content": groupchat.select_speaker_auto_none_template.format(agentlist=agent_list_string),
"name": "checking_agent",
"override_role": groupchat.role_for_select_speaker_messages,
},
)
### Multiple agents returned with no attempts left
attempts_left = 0
attempt = 3
select_speaker_messages[-1]["content"] = "The PM must speak after the CMO"
result = groupchat._validate_speaker_name(
recipient=checking_agent,
messages=select_speaker_messages,
sender=speaker_selection_agent,
config=None,
attempts_left=attempts_left,
attempt=attempt,
agents=agent_list,
)
assert result == (True, None)
assert (
select_speaker_messages[-1]["content"]
== f"[AGENT SELECTION FAILED]Select speaker attempt #{attempt} of {attempt + attempts_left} failed as it did not include any agent names."
)
def test_select_speaker_auto_messages():
"""
In this test, two agents are part of a group chat which has customized select speaker "auto" multiple and no-name prompt messages. Both valid and empty string values will be used.
The expected behaviour is that the customized speaker selection "auto" messages will override the default values or throw exceptions if empty.
"""
agent1 = autogen.ConversableAgent(
"Alice",
description="A wonderful employee named Alice.",
human_input_mode="NEVER",
llm_config=False,
)
agent2 = autogen.ConversableAgent(
"Bob",
description="An amazing employee named Bob.",
human_input_mode="NEVER",
llm_config=False,
)
# Customised message for select speaker auto method where multiple agent names are returned
custom_multiple_names_msg = "You mentioned multiple names but we need just one. Select the best one. A reminder that the options are {agentlist}."
# Customised message for select speaker auto method where no agent names are returned
custom_no_names_msg = "You forgot to select a single names and we need one, and only one. Select the best one. A reminder that the options are {agentlist}."
# Test empty is_termination_msg function
groupchat = autogen.GroupChat(
agents=[agent1, agent2],
messages=[],
speaker_selection_method="auto",
max_round=10,
select_speaker_auto_multiple_template=custom_multiple_names_msg,
select_speaker_auto_none_template=custom_no_names_msg,
)
# Test using the _validate_speaker_name function, checking for the correct string and agentlist to be included
agents = [agent1, agent2]
messages = [{"content": "Alice and Bob should both speak.", "name": "speaker_selector", "role": "user"}]
assert groupchat._validate_speaker_name(None, messages, None, None, 1, 1, agents) == (
True,
{
"content": custom_multiple_names_msg.replace("{agentlist}", "['Alice', 'Bob']"),
"name": "checking_agent",
"override_role": groupchat.role_for_select_speaker_messages,
},
)
messages = [{"content": "Fred should both speak.", "name": "speaker_selector", "role": "user"}]
assert groupchat._validate_speaker_name(None, messages, None, None, 1, 1, agents) == (
True,
{
"content": custom_no_names_msg.replace("{agentlist}", "['Alice', 'Bob']"),
"name": "checking_agent",
"override_role": groupchat.role_for_select_speaker_messages,
},
)
# Test with empty strings
with pytest.raises(ValueError, match="select_speaker_auto_multiple_template cannot be empty or None."):
groupchat = autogen.GroupChat(
agents=[agent1, agent2],
messages=[],
speaker_selection_method="auto",
max_round=10,
select_speaker_auto_multiple_template="",
)
with pytest.raises(ValueError, match="select_speaker_auto_none_template cannot be empty or None."):
groupchat = autogen.GroupChat(
agents=[agent1, agent2],
messages=[],
speaker_selection_method="auto",
max_round=10,
select_speaker_auto_none_template="",
)
# Test with None
with pytest.raises(ValueError, match="select_speaker_auto_multiple_template cannot be empty or None."):
groupchat = autogen.GroupChat(
agents=[agent1, agent2],
messages=[],
speaker_selection_method="auto",
max_round=10,
select_speaker_auto_multiple_template=None,
)
with pytest.raises(ValueError, match="select_speaker_auto_none_template cannot be empty or None."):
groupchat = autogen.GroupChat(
agents=[agent1, agent2],
messages=[],
speaker_selection_method="auto",
max_round=10,
select_speaker_auto_none_template=None,
)
def test_manager_messages_to_string():
"""In this test we test the conversion of messages to a JSON string"""
messages = [
{
"content": "You are an expert at finding the next speaker.",
"role": "system",
},
{
"content": "Let's get this meeting started. First the Product_Manager will create 3 new product ideas.",
"name": "Chairperson",
"role": "assistant",
},
]
groupchat = GroupChat(messages=messages, agents=[])
manager = GroupChatManager(groupchat)
# Convert the messages List[Dict] to a JSON string
converted_string = manager.messages_to_string(messages)
# The conversion should match the original messages
assert json.loads(converted_string) == messages
def test_manager_messages_from_string():
"""In this test we test the conversion of a JSON string of messages to a messages List[Dict]"""
messages_str = r"""[{"content": "You are an expert at finding the next speaker.", "role": "system"}, {"content": "Let's get this meeting started. First the Product_Manager will create 3 new product ideas.", "name": "Chairperson", "role": "assistant"}]"""
groupchat = GroupChat(messages=[], agents=[])
manager = GroupChatManager(groupchat)
# Convert the messages List[Dict] to a JSON string
messages = manager.messages_from_string(messages_str)
# The conversion should match the original messages
assert messages_str == json.dumps(messages)
def test_manager_resume_functions():
"""Tests functions within the resume chat functionality"""
# Setup
coder = AssistantAgent(name="Coder", llm_config=None)
groupchat = GroupChat(messages=[], agents=[coder])
manager = GroupChatManager(groupchat)
# Tests that messages are indeed passed in
with pytest.raises(Exception):
manager._valid_resume_messages(messages=[])
# Tests that the messages passed in match the agents of the group chat
messages = [
{
"content": "You are an expert at finding the next speaker.",
"role": "system",
},
{
"content": "Let's get this meeting started. First the Product_Manager will create 3 new product ideas.",
"name": "Chairperson",
"role": "assistant",
},
]
# Chairperson does not exist as an agent
with pytest.raises(Exception):
manager._valid_resume_messages(messages)
messages = [
{
"content": "You are an expert at finding the next speaker.",
"role": "system",
},
{
"content": "Let's get this meeting started. First the Product_Manager will create 3 new product ideas.",
"name": "Coder",
"role": "assistant",
},
]
# Coder does exist as an agent, no error
manager._valid_resume_messages(messages)
# Tests termination message replacement
final_msg = (
"Let's get this meeting started. First the Product_Manager will create 3 new product ideas. TERMINATE this."
)
messages = [
{
"content": "You are an expert at finding the next speaker.",
"role": "system",
},
{
"content": final_msg,
"name": "Coder",
"role": "assistant",
},
]
manager._process_resume_termination(remove_termination_string="TERMINATE", messages=messages)
# TERMINATE should be removed
assert messages[-1]["content"] == final_msg.replace("TERMINATE", "")
# Tests termination message replacement with function
def termination_func(x: str) -> str:
if "APPROVED" in x:
x = x.replace("APPROVED", "")
else:
x = x.replace("TERMINATE", "")
return x
final_msg1 = "Product_Manager has created 3 new product ideas. APPROVED"
messages1 = [
{
"content": "You are an expert at finding the next speaker.",
"role": "system",
},
{
"content": final_msg1,
"name": "Coder",
"role": "assistant",
},
]
manager._process_resume_termination(remove_termination_string=termination_func, messages=messages1)
# APPROVED should be removed
assert messages1[-1]["content"] == final_msg1.replace("APPROVED", "")
final_msg2 = "Idea has been approved. TERMINATE"
messages2 = [
{
"content": "You are an expert at finding the next speaker.",
"role": "system",
},
{
"content": final_msg2,
"name": "Coder",
"role": "assistant",
},
]
manager._process_resume_termination(remove_termination_string=termination_func, messages=messages2)
# TERMINATE should be removed, "approved" should still be present as the termination_func only replaces upper-cased "APPROVED".
assert messages2[-1]["content"] == final_msg2.replace("TERMINATE", "")
assert "approved" in messages2[-1]["content"]
# Check if the termination string doesn't exist there's no replacing of content
final_msg = (
"Let's get this meeting started. First the Product_Manager will create 3 new product ideas. TERMINATE this."
)
messages = [
{
"content": "You are an expert at finding the next speaker.",
"role": "system",
},
{
"content": final_msg,
"name": "Coder",
"role": "assistant",
},
]
manager._process_resume_termination(remove_termination_string="THE-END", messages=messages)
# It should not be changed
assert messages[-1]["content"] == final_msg
# Test that it warns that the termination condition would match
manager._is_termination_msg = lambda x: x.get("content", "").find("TERMINATE") >= 0
# Attach a handler to the logger so we can check the log output
log_stream = io.StringIO()
handler = logging.StreamHandler(log_stream)
logger = logging.getLogger() # Get the root logger
logger.addHandler(handler)
# We should get a warning that TERMINATE is still in the messages
manager._process_resume_termination(remove_termination_string="THE-END", messages=messages)
# Get the logged output and check that the warning was provided.
log_output = log_stream.getvalue()
assert "WARNING: Last message meets termination criteria and this may terminate the chat." in log_output
def test_manager_resume_returns():
"""Tests the return resume chat functionality"""
# Test the return agent and message is correct
coder = AssistantAgent(name="Coder", llm_config=None)
groupchat = GroupChat(messages=[], agents=[coder])
manager = GroupChatManager(groupchat)
messages = [
{
"content": "You are an expert at coding.",
"role": "system",
},
{
"content": "Let's get coding, should I use Python?",
"name": "Coder",
"role": "assistant",
},
]
return_agent, return_message = manager.resume(messages=messages)
assert return_agent == coder
assert return_message == messages[-1]
# Test when no agent provided, the manager will be returned
messages = [{"content": "You are an expert at coding.", "role": "system", "name": "chat_manager"}]
return_agent, return_message = manager.resume(messages=messages)
assert return_agent == manager
assert return_message == messages[-1]
def test_manager_resume_messages():
"""Tests that the messages passed into resume are the correct format"""
coder = AssistantAgent(name="Coder", llm_config=None)
groupchat = GroupChat(messages=[], agents=[coder])
manager = GroupChatManager(groupchat)
messages = 1
# Only acceptable messages types are JSON str and List[Dict]
# Try a number
with pytest.raises(Exception):
return_agent, return_message = manager.resume(messages=messages)
# Try an empty string
with pytest.raises(Exception):
return_agent, return_message = manager.resume(messages="")
# Try a message starter string, which isn't valid
with pytest.raises(Exception):
return_agent, return_message = manager.resume(messages="Let's get this conversation started.")
def test_select_speaker_transform_messages():
"""Tests adding transform messages to a GroupChat for speaker selection when in 'auto' mode"""
# Test adding a TransformMessages to a group chat
test_add_transforms = transform_messages.TransformMessages(
transforms=[
transforms.MessageHistoryLimiter(max_messages=10),
transforms.MessageTokenLimiter(max_tokens=3000, max_tokens_per_message=500, min_tokens=300),
]
)
coder = AssistantAgent(name="Coder", llm_config=None)
groupchat = GroupChat(messages=[], agents=[coder], select_speaker_transform_messages=test_add_transforms)
# Ensure the transform have been added to the GroupChat
assert groupchat._speaker_selection_transforms == test_add_transforms
# Attempt to add a non MessageTransforms object, such as a list of transforms
with pytest.raises(ValueError, match="select_speaker_transform_messages must be None or MessageTransforms."):
groupchat = GroupChat(
messages=[],
agents=[coder],
select_speaker_transform_messages=[transforms.MessageHistoryLimiter(max_messages=10)],
)
# Ensure if we don't pass any transforms in, none are on the GroupChat
groupchat_missing = GroupChat(messages=[], agents=[coder])
assert groupchat_missing._speaker_selection_transforms is None
# Ensure we can pass in None
groupchat_none = GroupChat(
messages=[],
agents=[coder],
select_speaker_transform_messages=None,
)
assert groupchat_none._speaker_selection_transforms is None
if __name__ == "__main__":
# test_func_call_groupchat()
# test_broadcast()
# test_chat_manager()
# test_plugin()
# test_speaker_selection_method()
# test_n_agents_less_than_3()
# test_agent_mentions()
# test_termination()
# test_next_agent()
# test_send_intros()
# test_invalid_allow_repeat_speaker()
# test_graceful_exit_before_max_round()
# test_clear_agents_history()
# test_custom_speaker_selection_overrides_transition_graph()
# test_role_for_select_speaker_messages()
# test_select_speaker_message_and_prompt_templates()
# test_speaker_selection_agent_name_match()
# test_role_for_reflection_summary()
# test_speaker_selection_auto_process_result()
# test_speaker_selection_validate_speaker_name()
# test_select_speaker_auto_messages()
# test_select_speaker_auto_messages()
# test_manager_messages_to_string()
# test_manager_messages_from_string()
test_manager_resume_functions()
# test_manager_resume_returns()
# test_manager_resume_messages()
# test_select_speaker_transform_messages()
pass