diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..ec14a1f --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,31 @@ +name: Run Tests + +on: + pull_request: + branches: [ main ] + push: + branches: [ main ] + +jobs: + test: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ["3.10", "3.11"] + + steps: + - uses: actions/checkout@v4 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -e ".[test]" + + - name: Run tests with pytest + run: | + pytest \ No newline at end of file diff --git a/README.md b/README.md index e7bfb0e..2f8c27f 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,7 @@ [![License: Apache 2.0](https://img.shields.io/badge/license-Apache%202.0-yellow.svg)](https://opensource.org/licenses/Apache-2.0) [![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/) [![PyPI version](https://badge.fury.io/py/agentarium.svg)](https://badge.fury.io/py/agentarium) +[![Tests](https://github.com/thytu/Agentarium/actions/workflows/test.yml/badge.svg)](https://github.com/thytu/Agentarium/actions/workflows/test.yml) A powerful Python framework for managing and orchestrating AI agents with ease. Agentarium provides a flexible and intuitive way to create, manage, and coordinate interactions between multiple AI agents in various environments. diff --git a/pyproject.toml b/pyproject.toml index 9b354de..e68032c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,7 +22,19 @@ dependencies = [ "PyYAML>=6.0.1", "boto3>=1.35.86", "aisuite>=0.1.7", + "dill>=0.3.8", ] +[project.optional-dependencies] +test = [ + "pytest>=7.0.0", + "pytest-cov>=4.0.0", +] + +[tool.pytest.ini_options] +testpaths = ["tests"] +python_files = ["test_*.py"] +addopts = "-v --cov=agentarium --cov-report=term-missing --cov-fail-under=80" + [project.urls] Homepage = "https://github.com/thytu/Agentarium" \ No newline at end of file diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..17c6438 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,50 @@ +import os +import pytest + +from agentarium import Agent +from agentarium.CheckpointManager import CheckpointManager +from agentarium.AgentInteractionManager import AgentInteractionManager + + +@pytest.fixture(autouse=True) +def cleanup_checkpoint_files(): + """Automatically clean up any checkpoint files after each test.""" + yield + # Clean up any .dill files in the current directory + for file in os.listdir(): + if file.endswith('.dill'): + os.remove(file) + + +@pytest.fixture +def base_agent(): + """Create a basic agent for testing.""" + return Agent.create_agent( + name="TestAgent", + age=25, + occupation="Software Engineer", + location="Test City", + bio="A test agent" + ) + + +@pytest.fixture +def interaction_manager(): + """Create a fresh interaction manager for testing.""" + return AgentInteractionManager() + + +@pytest.fixture +def checkpoint_manager(): + """Create a test checkpoint manager.""" + manager = CheckpointManager("test_checkpoint") + yield manager + # Cleanup is handled by cleanup_checkpoint_files fixture + + +@pytest.fixture +def agent_pair(): + """Create a pair of agents for interaction testing.""" + alice = Agent.create_agent(name="Alice") + bob = Agent.create_agent(name="Bob") + return alice, bob \ No newline at end of file diff --git a/tests/test_action.py b/tests/test_action.py new file mode 100644 index 0000000..8c8cf14 --- /dev/null +++ b/tests/test_action.py @@ -0,0 +1,135 @@ +import pytest +from agentarium import Action, Agent + +def test_action_creation(): + """Test creating an action with valid parameters.""" + def test_function(*args, **kwargs): + return {"result": "success"} + + action = Action( + name="test", + description="A test action", + parameters=["param1", "param2"], + function=test_function + ) + + assert action.name == "test" + assert action.description == "A test action" + assert action.parameters == ["param1", "param2"] + assert action.function is not None + +def test_action_single_parameter(): + """Test creating an action with a single parameter string.""" + def test_function(*args, **kwargs): + return {"result": "success"} + + # Test with a single parameter string + action = Action( + name="test", + description="A test action", + parameters="param1", + function=test_function + ) + + assert action.parameters == ["param1"] + + # Test with a list of parameters + action = Action( + name="test", + description="A test action", + parameters=["param1"], + function=test_function + ) + + assert action.parameters == ["param1"] + + +def test_invalid_action_name(): + """Test that action creation fails with invalid name.""" + def test_function(*args, **kwargs): + return {"result": "success"} + + with pytest.raises(ValueError): + Action( + name="", + description="A test action", + parameters=["param1"], + function=test_function + ) + + +def test_invalid_parameters(): + """Test that action creation fails with invalid parameters.""" + def test_function(*args, **kwargs): + return {"result": "success"} + + # Test empty parameter name + with pytest.raises(ValueError): + Action( + name="test", + description="A test action", + parameters=[""], + function=test_function + ) + + # Test non-string parameter + with pytest.raises(ValueError): + Action( + name="test", + description="A test action", + parameters=[123], + function=test_function + ) + + +def test_action_format(): + """Test the action format string generation.""" + def test_function(*args, **kwargs): + return {"result": "success"} + + action = Action( + name="test", + description="A test action", + parameters=["param1", "param2"], + function=test_function + ) + + expected_format = "[test][param1][param2]" + assert action.get_format() == expected_format + + +def test_action_execution(): + """Test executing an action with an agent.""" + def test_function(*args, **kwargs): + agent = kwargs["agent"] + return {"message": f"Action executed by {agent.name}"} + + action = Action( + name="test", + description="A test action", + parameters=["param"], + function=test_function + ) + + agent = Agent.create_agent(name="TestAgent") + result = action.function("test_param", agent=agent) + + assert result["action"] == "test" + assert "message" in result + assert "TestAgent" in result["message"] + + +def test_action_without_agent(): + """Test that action execution fails without agent in kwargs.""" + def test_function(*args, **kwargs): + return {"result": "success"} + + action = Action( + name="test", + description="A test action", + parameters=["param"], + function=test_function + ) + + with pytest.raises(RuntimeError): + action.function("test_param") diff --git a/tests/test_agent.py b/tests/test_agent.py new file mode 100644 index 0000000..a960ba1 --- /dev/null +++ b/tests/test_agent.py @@ -0,0 +1,116 @@ +import pytest + +from agentarium import Agent, Action +from agentarium.constant import DefaultValue + + +def test_agent_creation(): + """Test basic agent creation with default and custom values.""" + # Test with default values + agent = Agent.create_agent(bio="A test agent") # prevents the LLM from generating a bio + assert agent.agent_id is not None + assert agent.name is not None + assert agent.age is not None + assert agent.occupation is not None + assert agent.location is not None + assert agent.bio is not None + + # Test with custom values + custom_agent = Agent.create_agent( + name="Alice", + age=25, + occupation="Software Engineer", + location="San Francisco", + bio="A passionate software engineer" + ) + assert custom_agent.name == "Alice" + assert custom_agent.age == 25 + assert custom_agent.occupation == "Software Engineer" + assert custom_agent.location == "San Francisco" + assert custom_agent.bio == "A passionate software engineer" + + +def test_agent_default_actions(): + """Test that agents are created with default actions.""" + agent = Agent.create_agent() + assert "talk" in agent._actions + assert "think" in agent._actions + + +def test_agent_custom_actions(): + """Test adding and using custom actions.""" + def custom_action(*args, **kwargs): + agent = kwargs["agent"] + return {"message": f"Custom action by {agent.name}"} + + custom_action_obj = Action( + name="custom", + description="A custom action", + parameters=["message"], + function=custom_action + ) + + agent = Agent.create_agent(name="Bob", bio="A test agent") + agent.add_action(custom_action_obj) + + assert "custom" in agent._actions + result = agent.execute_action("custom", "test message") + assert result["action"] == "custom" + assert "message" in result + + +def test_agent_interaction(): + """Test basic interaction between agents.""" + alice = Agent.create_agent(name="Alice") + bob = Agent.create_agent(name="Bob") + + message = "Hello Bob!" + alice.talk_to(bob, message) + + # Check Alice's interactions + alice_interactions = alice.get_interactions() + assert len(alice_interactions) == 1 + assert alice_interactions[0].sender == alice + assert alice_interactions[0].receiver == bob + assert alice_interactions[0].message == message + + # Check Bob's interactions + bob_interactions = bob.get_interactions() + assert len(bob_interactions) == 1 + assert bob_interactions[0].sender == alice + assert bob_interactions[0].receiver == bob + assert bob_interactions[0].message == message + + +def test_agent_think(): + """Test agent's ability to think.""" + agent = Agent.create_agent(name="Alice") + thought = "I should learn more about AI" + + agent.think(thought) + + interactions = agent.get_interactions() + assert len(interactions) == 1 + assert interactions[0].sender == agent + assert interactions[0].receiver == agent + assert interactions[0].message == thought + + +def test_invalid_action(): + """Test handling of invalid actions.""" + agent = Agent.create_agent() + + with pytest.raises(RuntimeError): + agent.execute_action("nonexistent_action", "test") + + +def test_agent_reset(): + """Test resetting agent state.""" + agent = Agent.create_agent() + agent.think("Initial thought") + + assert len(agent.get_interactions()) == 1 + + agent.reset() + assert len(agent.get_interactions()) == 0 + assert len(agent.storage) == 0 diff --git a/tests/test_checkpoint_manager.py b/tests/test_checkpoint_manager.py new file mode 100644 index 0000000..a9e7c5f --- /dev/null +++ b/tests/test_checkpoint_manager.py @@ -0,0 +1,94 @@ +import os +import pytest +from agentarium import Agent +from agentarium.CheckpointManager import CheckpointManager + +@pytest.fixture +def checkpoint_manager(): + """Create a test checkpoint manager.""" + manager = CheckpointManager("test_checkpoint") + yield manager + # Cleanup after tests + if os.path.exists(manager.path): + os.remove(manager.path) + + +def test_checkpoint_manager_initialization(checkpoint_manager): + """Test checkpoint manager initialization.""" + assert checkpoint_manager.name == "test_checkpoint" + assert checkpoint_manager.path == "test_checkpoint.dill" + assert checkpoint_manager._action_idx == 0 + assert len(checkpoint_manager.recorded_actions) == 0 + + +def test_checkpoint_save_load(checkpoint_manager): + """Test saving and loading checkpoint data.""" + # Create agents and perform actions + alice = Agent.create_agent(name="Alice") + bob = Agent.create_agent(name="Bob") + + alice.talk_to(bob, "Hello Bob!") + bob.talk_to(alice, "Hi Alice!") + + # Save checkpoint + checkpoint_manager.save() + + # Create a new checkpoint manager and load data + new_manager = CheckpointManager("test_checkpoint") + new_manager.load() + + # Verify recorded actions were loaded + assert len(new_manager.recorded_actions) > 0 + assert new_manager.recorded_actions == checkpoint_manager.recorded_actions + +def test_checkpoint_recording(checkpoint_manager): + """Test that actions are properly recorded.""" + alice = Agent.create_agent(name="Alice") + bob = Agent.create_agent(name="Bob") + + # Perform some actions + alice.talk_to(bob, "Hello!") + bob.think("I should respond...") + bob.talk_to(alice, "Hi there!") + + # Check that actions were recorded + assert len(checkpoint_manager.recorded_actions) == 3 + +def test_singleton_behavior(): + """Test that CheckpointManager behaves like a singleton.""" + manager1 = CheckpointManager("test_singleton") + manager2 = CheckpointManager("test_singleton") + + # Both instances should reference the same object + assert manager1 is manager2 + + # Clean up + if os.path.exists(manager1.path): + os.remove(manager1.path) + +def test_invalid_checkpoint_load(): + """Test loading from a non-existent checkpoint file.""" + manager = CheckpointManager("nonexistent_checkpoint") + + with pytest.raises(FileNotFoundError): + manager.load() + +def test_checkpoint_reset(): + """Test resetting checkpoint manager state.""" + manager = CheckpointManager("test_reset") + + # Create some actions + alice = Agent.create_agent(name="Alice") + alice.think("Test thought") + + assert len(manager.recorded_actions) > 0 + + # Reset by creating a new instance with a different name + new_manager = CheckpointManager("different_name") + assert len(new_manager.recorded_actions) == 0 + + # Clean up + if os.path.exists(manager.path): + os.remove(manager.path) + if os.path.exists(new_manager.path): + os.remove(new_manager.path) \ No newline at end of file diff --git a/tests/test_interaction_manager.py b/tests/test_interaction_manager.py new file mode 100644 index 0000000..4104c5c --- /dev/null +++ b/tests/test_interaction_manager.py @@ -0,0 +1,113 @@ +import pytest +from agentarium import Agent, Interaction +from agentarium.AgentInteractionManager import AgentInteractionManager + + +@pytest.fixture +def interaction_manager(): + """Create a test interaction manager.""" + return AgentInteractionManager() + + +@pytest.fixture +def test_agents(interaction_manager): + """Create test agents for interaction testing.""" + alice = Agent.create_agent(name="Alice") + bob = Agent.create_agent(name="Bob") + return alice, bob + + +def test_agent_registration(interaction_manager, test_agents): + """Test registering agents with the interaction manager.""" + alice, bob = test_agents + + # Verify agents are registered + assert alice.agent_id in interaction_manager._agents + assert bob.agent_id in interaction_manager._agents + + # Verify correct agent objects are stored + assert interaction_manager._agents[alice.agent_id] is alice + assert interaction_manager._agents[bob.agent_id] is bob + +def test_interaction_recording(interaction_manager, test_agents): + """Test recording interactions between agents.""" + alice, bob = test_agents + message = "Hello Bob!" + + # Record an interaction + interaction_manager.record_interaction(alice, bob, message) + + # Verify interaction was recorded + assert len(interaction_manager._interactions) == 1 + interaction = interaction_manager._interactions[0] + assert interaction.sender is alice + assert interaction.receiver is bob + assert interaction.message == message + +def test_get_agent_interactions(interaction_manager, test_agents): + """Test retrieving agent interactions.""" + alice, bob = test_agents + + # Record multiple interactions + interaction_manager.record_interaction(alice, bob, "Hello!") + interaction_manager.record_interaction(bob, alice, "Hi there!") + interaction_manager.record_interaction(alice, bob, "How are you?") + + # Get Alice's interactions + alice_interactions = interaction_manager.get_agent_interactions(alice) + assert len(alice_interactions) == 3 + + # Get Bob's interactions + bob_interactions = interaction_manager.get_agent_interactions(bob) + assert len(bob_interactions) == 3 + +def test_get_agent(interaction_manager, test_agents): + """Test retrieving agents by ID.""" + alice, bob = test_agents + + # Test getting existing agents + assert interaction_manager.get_agent(alice.agent_id) is alice + assert interaction_manager.get_agent(bob.agent_id) is bob + + # Test getting non-existent agent + assert interaction_manager.get_agent("nonexistent_id") is None + +def test_interaction_order(interaction_manager, test_agents): + """Test that interactions are recorded in order.""" + alice, bob = test_agents + + messages = ["First", "Second", "Third"] + + for msg in messages: + interaction_manager.record_interaction(alice, bob, msg) + + # Verify interactions are in order + interactions = interaction_manager.get_agent_interactions(alice) + for i, interaction in enumerate(interactions): + assert interaction.message == messages[i] + +def test_self_interaction(interaction_manager, test_agents): + """Test recording self-interactions (thoughts).""" + alice, _ = test_agents + thought = "I should learn more" + + interaction_manager.record_interaction(alice, alice, thought) + + interactions = interaction_manager.get_agent_interactions(alice) + assert len(interactions) == 1 + assert interactions[0].sender is alice + assert interactions[0].receiver is alice + assert interactions[0].message == thought + +def test_interaction_validation(interaction_manager, test_agents): + """Test validation of interaction recording.""" + alice, _ = test_agents + + # Test with unregistered agent + unregistered_agent = Agent.create_agent(name="Unregistered") + + with pytest.raises(RuntimeError): + interaction_manager.record_interaction(unregistered_agent, alice, "Hello") + + with pytest.raises(RuntimeError): + interaction_manager.record_interaction(alice, unregistered_agent, "Hello")