Files
evotraders/backend/agents/unified_factory.py
cillin 45c3996434 refactor(cleanup): remove legacy agent classes and complete EvoAgent migration
Remove deprecated AnalystAgent, PMAgent, and RiskAgent classes.
All agent creation now goes through UnifiedAgentFactory creating EvoAgent instances.

- Delete backend/agents/analyst.py (169 lines)
- Delete backend/agents/portfolio_manager.py (420 lines)
- Delete backend/agents/risk_manager.py (139 lines)
- Update all imports to use EvoAgent exclusively
- Clean up unused imports across 25 files
- Update tests to work with simplified agent structure

Constraint: EvoAgent is now the single source of truth for all agent roles
Constraint: UnifiedAgentFactory handles runtime agent creation
Rejected: Keep legacy aliases | creates maintenance burden
Confidence: high
Scope-risk: moderate (affects agent instantiation paths)
Directive: All new agent features must be added to EvoAgent, not legacy classes
Not-tested: Kubernetes sandbox executor (marked with TODO)
2026-04-02 10:51:14 +08:00

333 lines
10 KiB
Python

# -*- coding: utf-8 -*-
"""Unified Agent Factory - Centralized agent creation for 大时代.
This module provides a unified factory for creating all agent types (analysts,
risk manager, portfolio manager) as EvoAgent instances with consistent
configuration. It replaces the scattered agent creation logic in main.py,
pipeline.py, and pipeline_runner.py.
Key features:
- Single entry point for all agent creation
- Creates EvoAgent instances for all agent roles
- Consistent parameter handling across all agent types
- Support for workspace-driven configuration
- Long-term memory integration
"""
from __future__ import annotations
from pathlib import Path
from typing import Any, Optional, Protocol
from backend.agents.base.evo_agent import EvoAgent
class AgentFactoryProtocol(Protocol):
"""Protocol for agent factory implementations."""
def create_analyst(
self,
analyst_type: str,
model: Any,
formatter: Any,
active_skill_dirs: Optional[list[Path]] = None,
long_term_memory: Optional[Any] = None,
) -> EvoAgent: ...
def create_risk_manager(
self,
model: Any,
formatter: Any,
active_skill_dirs: Optional[list[Path]] = None,
long_term_memory: Optional[Any] = None,
) -> EvoAgent: ...
def create_portfolio_manager(
self,
model: Any,
formatter: Any,
initial_cash: float,
margin_requirement: float,
active_skill_dirs: Optional[list[Path]] = None,
long_term_memory: Optional[Any] = None,
) -> EvoAgent: ...
class UnifiedAgentFactory:
"""Unified factory for creating EvoAgent instances with consistent configuration.
This factory centralizes agent creation logic and creates EvoAgent instances
for all agent roles (analysts, risk manager, portfolio manager).
Example:
factory = UnifiedAgentFactory(
config_name="smoke_fullstack",
skills_manager=skills_manager,
)
# Create analyst
analyst = factory.create_analyst(
analyst_type="fundamentals_analyst",
model=model,
formatter=formatter,
)
# Create risk manager
risk_mgr = factory.create_risk_manager(
model=model,
formatter=formatter,
)
# Create portfolio manager
pm = factory.create_portfolio_manager(
model=model,
formatter=formatter,
initial_cash=100000.0,
margin_requirement=0.5,
)
"""
def __init__(
self,
config_name: str,
skills_manager: Any,
toolkit_factory: Optional[Any] = None,
):
"""Initialize the agent factory.
Args:
config_name: Run configuration name (e.g., "smoke_fullstack")
skills_manager: SkillsManager instance for skill/asset management
toolkit_factory: Optional factory function for creating toolkits
"""
self.config_name = config_name
self.skills_manager = skills_manager
self.toolkit_factory = toolkit_factory
def _create_toolkit(
self,
agent_type: str,
active_skill_dirs: Optional[list[Path]] = None,
owner: Optional[Any] = None,
) -> Any:
"""Create toolkit for an agent."""
if self.toolkit_factory is None:
from backend.agents.toolkit_factory import create_agent_toolkit
self.toolkit_factory = create_agent_toolkit
kwargs: dict[str, Any] = {
"active_skill_dirs": active_skill_dirs or [],
}
if owner is not None:
kwargs["owner"] = owner
return self.toolkit_factory(agent_type, self.config_name, **kwargs)
def _load_agent_config(self, agent_id: str) -> Any:
"""Load agent configuration from workspace."""
from backend.agents.agent_workspace import load_agent_workspace_config
workspace_dir = self.skills_manager.get_agent_asset_dir(
self.config_name, agent_id
)
config_path = workspace_dir / "agent.yaml"
if config_path.exists():
return load_agent_workspace_config(config_path)
# Return default config if no agent.yaml
return type(
"AgentConfig",
(),
{"prompt_files": ["SOUL.md"]},
)()
def _create_evo_agent(
self,
agent_id: str,
model: Any,
formatter: Any,
toolkit: Any,
agent_config: Any,
long_term_memory: Optional[Any] = None,
extra_kwargs: Optional[dict[str, Any]] = None,
) -> EvoAgent:
"""Create an EvoAgent instance."""
workspace_dir = self.skills_manager.get_agent_asset_dir(
self.config_name, agent_id
)
kwargs: dict[str, Any] = {
"agent_id": agent_id,
"config_name": self.config_name,
"workspace_dir": workspace_dir,
"model": model,
"formatter": formatter,
"skills_manager": self.skills_manager,
"prompt_files": getattr(agent_config, "prompt_files", ["SOUL.md"]),
"long_term_memory": long_term_memory,
}
if extra_kwargs:
kwargs.update(extra_kwargs)
agent = EvoAgent(**kwargs)
agent.toolkit = toolkit
setattr(agent, "run_id", self.config_name)
# Keep workspace_id for backward compatibility
setattr(agent, "workspace_id", self.config_name)
return agent
def create_analyst(
self,
analyst_type: str,
model: Any,
formatter: Any,
active_skill_dirs: Optional[list[Path]] = None,
long_term_memory: Optional[Any] = None,
) -> EvoAgent:
"""Create an analyst agent.
Args:
analyst_type: Type of analyst (fundamentals, technical, sentiment, valuation)
model: LLM model instance
formatter: Message formatter instance
active_skill_dirs: Optional list of active skill directories
long_term_memory: Optional long-term memory instance
Returns:
EvoAgent instance
"""
toolkit = self._create_toolkit(analyst_type, active_skill_dirs)
agent_config = self._load_agent_config(analyst_type)
return self._create_evo_agent(
agent_id=analyst_type,
model=model,
formatter=formatter,
toolkit=toolkit,
agent_config=agent_config,
long_term_memory=long_term_memory,
)
def create_risk_manager(
self,
model: Any,
formatter: Any,
active_skill_dirs: Optional[list[Path]] = None,
long_term_memory: Optional[Any] = None,
) -> EvoAgent:
"""Create a risk manager agent.
Args:
model: LLM model instance
formatter: Message formatter instance
active_skill_dirs: Optional list of active skill directories
long_term_memory: Optional long-term memory instance
Returns:
EvoAgent instance
"""
toolkit = self._create_toolkit("risk_manager", active_skill_dirs)
agent_config = self._load_agent_config("risk_manager")
return self._create_evo_agent(
agent_id="risk_manager",
model=model,
formatter=formatter,
toolkit=toolkit,
agent_config=agent_config,
long_term_memory=long_term_memory,
)
def create_portfolio_manager(
self,
model: Any,
formatter: Any,
initial_cash: float,
margin_requirement: float,
active_skill_dirs: Optional[list[Path]] = None,
long_term_memory: Optional[Any] = None,
) -> EvoAgent:
"""Create a portfolio manager agent.
Args:
model: LLM model instance
formatter: Message formatter instance
initial_cash: Initial cash allocation
margin_requirement: Margin requirement ratio
active_skill_dirs: Optional list of active skill directories
long_term_memory: Optional long-term memory instance
Returns:
EvoAgent instance
"""
agent_config = self._load_agent_config("portfolio_manager")
# For PM, toolkit is created after agent (needs owner reference)
workspace_dir = self.skills_manager.get_agent_asset_dir(
self.config_name, "portfolio_manager"
)
agent = EvoAgent(
agent_id="portfolio_manager",
config_name=self.config_name,
workspace_dir=workspace_dir,
model=model,
formatter=formatter,
skills_manager=self.skills_manager,
prompt_files=getattr(agent_config, "prompt_files", ["SOUL.md"]),
initial_cash=initial_cash,
margin_requirement=margin_requirement,
long_term_memory=long_term_memory,
)
agent.toolkit = self._create_toolkit(
"portfolio_manager", active_skill_dirs, owner=agent
)
setattr(agent, "run_id", self.config_name)
# Keep workspace_id for backward compatibility
setattr(agent, "workspace_id", self.config_name)
return agent
# Singleton factory instance cache
_factory_cache: dict[str, UnifiedAgentFactory] = {}
def get_agent_factory(
config_name: str,
skills_manager: Any,
toolkit_factory: Optional[Any] = None,
) -> UnifiedAgentFactory:
"""Get or create a cached agent factory instance.
Args:
config_name: Run configuration name
skills_manager: SkillsManager instance
toolkit_factory: Optional toolkit factory function
Returns:
UnifiedAgentFactory instance (cached per config_name)
"""
cache_key = f"{config_name}:{id(skills_manager)}"
if cache_key not in _factory_cache:
_factory_cache[cache_key] = UnifiedAgentFactory(
config_name=config_name,
skills_manager=skills_manager,
toolkit_factory=toolkit_factory,
)
return _factory_cache[cache_key]
def clear_factory_cache() -> None:
"""Clear the factory cache. Useful for testing."""
_factory_cache.clear()
__all__ = [
"UnifiedAgentFactory",
"AgentFactoryProtocol",
"get_agent_factory",
"clear_factory_cache",
]