Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions backend/src/agents/main_agent/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
from .main_agent import MainAgent
from .base_agent import BaseAgent
from .chat_agent import ChatAgent
from .agent_types import create_agent, register_agent_type, get_available_types
from .core import ModelConfig, SystemPromptBuilder
from .session import SessionFactory
from .tools import ToolRegistry, ToolFilter, GatewayIntegration, create_default_registry
Expand All @@ -56,6 +57,9 @@
"BaseAgent",
"ChatAgent",
"MainAgent",
"create_agent",
"register_agent_type",
"get_available_types",

# Core components
"ModelConfig",
Expand Down
60 changes: 60 additions & 0 deletions backend/src/agents/main_agent/agent_types.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
"""
Agent type registry and factory function.

Provides a single entry point for creating agents by type string.
New agent types register here as they're implemented.
"""

import logging
from typing import Optional, List

from agents.main_agent.base_agent import BaseAgent

logger = logging.getLogger(__name__)


# Registry of agent type string → class
# New agent types add themselves here as they're implemented
_AGENT_TYPES = {}


def register_agent_type(type_name: str, agent_class: type) -> None:
"""Register an agent class for a given type string."""
_AGENT_TYPES[type_name] = agent_class


def get_available_types() -> List[str]:
"""Return list of registered agent type names."""
return list(_AGENT_TYPES.keys())


def create_agent(agent_type: str = "chat", **kwargs) -> BaseAgent:
"""
Create an agent by type string.

Args:
agent_type: Agent type ("chat", "skill", "voice"). Default: "chat".
**kwargs: Passed directly to the agent constructor (session_id, user_id, etc.)

Returns:
BaseAgent subclass instance

Raises:
ValueError: If agent_type is not registered
"""
agent_class = _AGENT_TYPES.get(agent_type)
if agent_class is None:
available = ", ".join(sorted(_AGENT_TYPES.keys()))
raise ValueError(f"Unknown agent_type '{agent_type}'. Available: {available}")

logger.info(f"Creating {agent_type} agent ({agent_class.__name__})")
return agent_class(**kwargs)


# Register built-in agent types
def _register_defaults():
from agents.main_agent.chat_agent import ChatAgent
register_agent_type("chat", ChatAgent)


_register_defaults()
72 changes: 72 additions & 0 deletions backend/tests/agents/main_agent/test_agent_types.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
"""Tests for agent type registry and factory function."""

import pytest
from agents.main_agent.agent_types import (
create_agent,
register_agent_type,
get_available_types,
_AGENT_TYPES,
)
from agents.main_agent.base_agent import BaseAgent
from agents.main_agent.chat_agent import ChatAgent
from agents.main_agent.main_agent import MainAgent


class TestGetAvailableTypes:
"""Req AT-1: Available types discovery."""

def test_chat_is_registered_by_default(self):
assert "chat" in get_available_types()

def test_returns_list_of_strings(self):
types = get_available_types()
assert isinstance(types, list)
assert all(isinstance(t, str) for t in types)


class TestRegisterAgentType:
"""Req AT-2: Dynamic agent type registration."""

def test_register_new_type(self):
class DummyAgent(BaseAgent):
def _create_agent(self): pass
async def stream_async(self, message, **kwargs): yield ""

register_agent_type("dummy", DummyAgent)
assert "dummy" in get_available_types()

# Cleanup
del _AGENT_TYPES["dummy"]

def test_register_overwrites_existing(self):
original = _AGENT_TYPES.get("chat")
try:
register_agent_type("chat", MainAgent)
assert _AGENT_TYPES["chat"] is MainAgent
finally:
_AGENT_TYPES["chat"] = original


class TestCreateAgent:
"""Req AT-3: Factory function routing."""

def test_unknown_type_raises_value_error(self):
with pytest.raises(ValueError, match="Unknown agent_type 'nonexistent'"):
create_agent("nonexistent", session_id="test")

def test_error_message_lists_available_types(self):
with pytest.raises(ValueError, match="chat"):
create_agent("nonexistent", session_id="test")


class TestMainAgentBackwardCompat:
"""Req AT-4: MainAgent remains a valid ChatAgent subclass."""

def test_mainagent_is_subclass_of_chatagent(self):
assert issubclass(MainAgent, ChatAgent)

def test_mainagent_is_subclass_of_baseagent(self):
assert issubclass(MainAgent, BaseAgent)

def test_chatagent_is_subclass_of_baseagent(self):
assert issubclass(ChatAgent, BaseAgent)
Loading