autogen/test/test_graph_utils.py
Joshua Kim c603ca434e
Graph group chat (#857)
* Move contrib-openai.yml

* Moved groupgroupchat

* From #753

* Removed local test references

* Added ignore=test/agentchat/contrib

* Trying to pass contrib-openai tests

* More specific in unit testing.

* Update .github/workflows/contrib-tests.yml

Co-authored-by: Li Jiang <lijiang1@microsoft.com>

* Remove coverage as it is included in test dependencies

* Improved docstring with overview of GraphGroupChat

* Iterate on feedback

* Precommit pass

* user just use pip install pyautogen[graphs]

* Pass precommit

* Pas precommit

* Graph utils an test completed

* Added inversion tests

* Added inversion util

* allow_repeat_speaker can be a list of Agents

* Remove unnessary imports

* Expect ValueError with 1 and 0 agents

* Check that main passes all tests

* Check main

* Pytest all in main

* All done

* pre-commit changes

* noqa E402

* precommit pass

* Removed bin

* Removed old unit test

* Test test_graph_utils

* minor cleanup

* restore tests

* Correct documentation

* Special case of only one agent remaining.

* Improved pytest

* precommit pass

* Delete OAI_CONFIG_LIST_sample copy

* Returns a filtered list for auto to work

* Rename var speaker_order_dict

* To write test cases

* Added check for a list of Agents to repeat

* precommit pass

* Update documentation

* Extract names in allow_repeat_speaker

* Post review changes

* hange "pull_request_target" into "pull_request" temporarily.

* 3 return values from main

* pre-commit changes

* PC edits

* docstr changes

* PC edits

* Rest of changes from main

* Update autogen/agentchat/groupchat.py

Co-authored-by: Chi Wang <wang.chi@microsoft.com>

* Remove unnecessary script files from tracking

* Non empty scripts files from main

* Revert changes in script files to match main branch

* Removed link from website as notebook is removed.

* test/test_graph_utils.py is tested as part of L52 of build.yml

* GroupChat ValueError check

* docstr update

* More clarification in docstr

* Update autogen/agentchat/groupchat.py

Co-authored-by: Chi Wang <wang.chi@microsoft.com>

* Update autogen/agentchat/groupchat.py

Co-authored-by: Chi Wang <wang.chi@microsoft.com>

* Update autogen/agentchat/groupchat.py

Co-authored-by: Chi Wang <wang.chi@microsoft.com>

* Update autogen/agentchat/groupchat.py

Co-authored-by: Chi Wang <wang.chi@microsoft.com>

* 1.add commit to line138 in groupchat.py;2.fix bug if random choice [];3.return selected_agent if len(graph_eligible_agents) is 1;4.replace all speaker_order to speaker_transitions;5.format

* fix graph_modelling notebook in the last cell

* fix failure in test_groupchat.py

* fix agent out of group to initiate a chat like SocietyOfMind

* add a warning rule in graph_utils to check duplicates in any lists

* refactor allowed_or_disallowed_speaker_transitions to Dict[Agent, List[Agent]] and modify the tests and notebook

* delete Rule 4 in graph_utils and related test case. Add a test to resolve 993fd006e9 (r1460726831)

* fix as the final comments

* modify setup option from graphs to graph and add texts in optional-dependencies.md

* Update autogen/graph_utils.py

---------

Co-authored-by: Li Jiang <lijiang1@microsoft.com>
Co-authored-by: Beibin Li <BeibinLi@users.noreply.github.com>
Co-authored-by: Chi Wang <wang.chi@microsoft.com>
Co-authored-by: Qingyun Wu <qingyun0327@gmail.com>
Co-authored-by: Yishen Sun <freedeaths@FREEDEATHS-XPS>
Co-authored-by: freedeaths <register917@gmail.com>
2024-02-06 03:13:18 +00:00

166 lines
7.2 KiB
Python

import sys
import pytest
import logging
from autogen.agentchat import Agent
import autogen.graph_utils as gru
class TestHelpers:
def test_has_self_loops(self):
# Setup test data
agents = [Agent(name=f"Agent{i}") for i in range(3)]
allowed_speaker_transitions = {
agents[0]: [agents[1], agents[2]],
agents[1]: [agents[2]],
agents[2]: [agents[0]],
}
allowed_speaker_transitions_with_self_loops = {
agents[0]: [agents[0], agents[1], agents[2]],
agents[1]: [agents[1], agents[2]],
agents[2]: [agents[0]],
}
# Testing
assert not gru.has_self_loops(allowed_speaker_transitions)
assert gru.has_self_loops(allowed_speaker_transitions_with_self_loops)
class TestGraphUtilCheckGraphValidity:
def test_valid_structure(self):
agents = [Agent("agent1"), Agent("agent2"), Agent("agent3")]
valid_speaker_transitions_dict = {agent: [other_agent for other_agent in agents] for agent in agents}
gru.check_graph_validity(allowed_speaker_transitions_dict=valid_speaker_transitions_dict, agents=agents)
def test_graph_with_invalid_structure(self):
agents = [Agent("agent1"), Agent("agent2"), Agent("agent3")]
unseen_agent = Agent("unseen_agent")
invalid_speaker_transitions_dict = {unseen_agent: ["stranger"]}
with pytest.raises(ValueError):
gru.check_graph_validity(invalid_speaker_transitions_dict, agents)
def test_graph_with_invalid_string(self):
agents = [Agent("agent1"), Agent("agent2"), Agent("agent3")]
invalid_speaker_transitions_dict = {
agent: ["agent1"] for agent in agents
} # 'agent1' is a string, not an Agent. Therefore raises an error.
with pytest.raises(ValueError):
gru.check_graph_validity(invalid_speaker_transitions_dict, agents)
def test_graph_with_unauthorized_self_loops(self):
agents = [Agent("agent1"), Agent("agent2"), Agent("agent3")]
# Creating a subset of agents allowed to have self-loops
allowed_repeat_speakers = agents[: len(agents) // 2]
# Constructing a speaker transitions dictionary with self-loops for all agents
# Ensuring at least one agent outside the allowed_repeat_speakers has a self-loop
speaker_transitions_dict_with_self_loop = {agent: agent for agent in agents}
# Testing the function with the constructed speaker transitions dict
with pytest.raises(ValueError):
gru.check_graph_validity(
speaker_transitions_dict_with_self_loop, agents, allow_repeat_speaker=allowed_repeat_speakers
)
# Test for Warning 1: Isolated agent nodes
def test_isolated_agent_nodes_warning(self, caplog):
agents = [Agent("agent1"), Agent("agent2"), Agent("agent3")]
# Create a speaker_transitions_dict where at least one agent is isolated
speaker_transitions_dict_with_isolation = {agents[0]: [agents[0], agents[1]], agents[1]: [agents[0]]}
# Add an isolated agent
speaker_transitions_dict_with_isolation[agents[2]] = []
with caplog.at_level(logging.WARNING):
gru.check_graph_validity(
allowed_speaker_transitions_dict=speaker_transitions_dict_with_isolation, agents=agents
)
assert "isolated" in caplog.text
# Test for Warning 2: Warning if the set of agents in allowed_speaker_transitions do not match agents
def test_warning_for_mismatch_in_agents(self, caplog):
agents = [Agent("agent1"), Agent("agent2"), Agent("agent3")]
# Test with missing agents in allowed_speaker_transitions_dict
unknown_agent_dict = {
agents[0]: [agents[0], agents[1], agents[2]],
agents[1]: [agents[0], agents[1], agents[2]],
agents[2]: [agents[0], agents[1], agents[2], Agent("unknown_agent")],
}
with caplog.at_level(logging.WARNING):
gru.check_graph_validity(allowed_speaker_transitions_dict=unknown_agent_dict, agents=agents)
assert "allowed_speaker_transitions do not match agents" in caplog.text
# Test for Warning 3: Warning if there is duplicated agents in allowed_speaker_transitions_dict
def test_warning_for_duplicate_agents(self, caplog):
agents = [Agent("agent1"), Agent("agent2"), Agent("agent3")]
# Construct an `allowed_speaker_transitions_dict` with duplicated agents
duplicate_agents_dict = {
agents[0]: [agents[0], agents[1], agents[2]],
agents[1]: [agents[0], agents[1], agents[2], agents[1]],
agents[2]: [agents[0], agents[1], agents[2], agents[0], agents[2]],
}
with caplog.at_level(logging.WARNING):
gru.check_graph_validity(allowed_speaker_transitions_dict=duplicate_agents_dict, agents=agents)
assert "duplicate" in caplog.text
class TestGraphUtilInvertDisallowedToAllowed:
def test_basic_functionality(self):
agents = [Agent("agent1"), Agent("agent2"), Agent("agent3")]
disallowed_graph = {agents[0]: [agents[1]], agents[1]: [agents[0], agents[2]], agents[2]: []}
expected_allowed_graph = {
agents[0]: [agents[0], agents[2]],
agents[1]: [agents[1]],
agents[2]: [agents[0], agents[1], agents[2]],
}
# Compare names of agents
inverted = gru.invert_disallowed_to_allowed(disallowed_graph, agents)
assert inverted == expected_allowed_graph
def test_empty_disallowed_graph(self):
agents = [Agent("agent1"), Agent("agent2"), Agent("agent3")]
disallowed_graph = {}
expected_allowed_graph = {
agents[0]: [agents[0], agents[1], agents[2]],
agents[1]: [agents[0], agents[1], agents[2]],
agents[2]: [agents[0], agents[1], agents[2]],
}
# Compare names of agents
inverted = gru.invert_disallowed_to_allowed(disallowed_graph, agents)
assert inverted == expected_allowed_graph
def test_fully_disallowed_graph(self):
agents = [Agent("agent1"), Agent("agent2"), Agent("agent3")]
disallowed_graph = {
agents[0]: [agents[0], agents[1], agents[2]],
agents[1]: [agents[0], agents[1], agents[2]],
agents[2]: [agents[0], agents[1], agents[2]],
}
expected_allowed_graph = {agents[0]: [], agents[1]: [], agents[2]: []}
# Compare names of agents
inverted = gru.invert_disallowed_to_allowed(disallowed_graph, agents)
assert inverted == expected_allowed_graph
def test_disallowed_graph_with_nonexistent_agent(self):
agents = [Agent("agent1"), Agent("agent2"), Agent("agent3")]
disallowed_graph = {agents[0]: [Agent("nonexistent_agent")]}
# In this case, the function should ignore the nonexistent agent and proceed with the inversion
expected_allowed_graph = {
agents[0]: [agents[0], agents[1], agents[2]],
agents[1]: [agents[0], agents[1], agents[2]],
agents[2]: [agents[0], agents[1], agents[2]],
}
# Compare names of agents
inverted = gru.invert_disallowed_to_allowed(disallowed_graph, agents)
assert inverted == expected_allowed_graph