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)
This commit is contained in:
2026-04-02 10:51:14 +08:00
parent 49d704c363
commit 45c3996434
34 changed files with 201 additions and 1534 deletions

View File

@@ -1,48 +1,46 @@
# -*- coding: utf-8 -*-
"""
Agents package for the current mixed runtime.
Agents package for the EvoAgent-based runtime.
Exports:
- EvoAgent: Next-generation agent with workspace support
- EvoAgent: Core agent with workspace support
- ToolGuardMixin: Tool call approval/denial flow
- CommandHandler: System command handling
- AgentFactory: Design-time agent creation under `workspaces/`
- WorkspaceManager: Legacy alias for the persistent `workspaces/` registry
- WorkspaceManager: Alias for the persistent `workspaces/` registry
- WorkspaceRegistry: Explicit design-time `workspaces/` registry
- RunWorkspaceManager: Run-scoped workspace asset manager
- AgentRegistry: Central agent registry
- Legacy compatibility: AnalystAgent, PMAgent, RiskAgent
- UnifiedAgentFactory: Runtime agent factory for creating EvoAgent instances
"""
# New EvoAgent architecture (from agent_core.py)
# EvoAgent architecture
from .agent_core import EvoAgent, ToolGuardMixin, CommandHandler
from .factory import AgentFactory, ModelConfig
from .unified_factory import UnifiedAgentFactory, get_agent_factory, clear_factory_cache
from .workspace import WorkspaceManager, WorkspaceRegistry, WorkspaceConfig
from .workspace_manager import RunWorkspaceManager
from .registry import AgentRegistry, AgentInfo, get_registry, reset_registry
# Legacy agents (backward compatibility)
from .analyst import AnalystAgent
from .portfolio_manager import PMAgent
from .risk_manager import RiskAgent
__all__ = [
# New architecture
# Core EvoAgent
"EvoAgent",
"ToolGuardMixin",
"CommandHandler",
# Factories
"AgentFactory",
"ModelConfig",
"UnifiedAgentFactory",
"get_agent_factory",
"clear_factory_cache",
# Workspace
"WorkspaceManager",
"WorkspaceRegistry",
"WorkspaceConfig",
"RunWorkspaceManager",
# Registry
"AgentRegistry",
"AgentInfo",
"get_registry",
"reset_registry",
# Legacy compatibility
"AnalystAgent",
"PMAgent",
"RiskAgent",
]

View File

@@ -1,169 +0,0 @@
# -*- coding: utf-8 -*-
"""
Analyst Agent - Based on AgentScope ReActAgent
Performs analysis using tools and LLM
.. deprecated:: 0.2.0
AnalystAgent is deprecated and will be removed in a future version.
Use :class:`backend.agents.base.evo_agent.EvoAgent` instead.
See docs/CRITICAL_FIXES.md for migration guide.
"""
import warnings
from typing import Any, Dict, Optional
from agentscope.agent import ReActAgent
from agentscope.memory import InMemoryMemory, LongTermMemoryBase
from agentscope.message import Msg
from ..config.constants import ANALYST_TYPES
from ..utils.progress import progress
from .prompt_factory import build_agent_system_prompt, clear_prompt_factory_cache
# Emit deprecation warning on module import
warnings.warn(
"AnalystAgent is deprecated. Use EvoAgent instead. "
"See docs/CRITICAL_FIXES.md for migration guide.",
DeprecationWarning,
stacklevel=2,
)
class AnalystAgent(ReActAgent):
"""
Analyst Agent - Uses LLM for tool selection and analysis
Inherits from AgentScope's ReActAgent
.. deprecated:: 0.2.0
Use :class:`backend.agents.base.evo_agent.EvoAgent` with
workspace-driven configuration instead.
"""
def __init__(
self,
analyst_type: str,
toolkit: Any,
model: Any,
formatter: Any,
agent_id: Optional[str] = None,
config: Optional[Dict[str, Any]] = None,
long_term_memory: Optional[LongTermMemoryBase] = None,
):
"""
Initialize Analyst Agent
.. deprecated:: 0.2.0
Use :class:`backend.agents.unified_factory.UnifiedAgentFactory`
or :class:`backend.agents.base.evo_agent.EvoAgent` instead.
Args:
analyst_type: Type of analyst (e.g., "fundamentals", etc.)
toolkit: AgentScope Toolkit instance
model: LLM model instance
formatter: Message formatter instance
agent_id: Agent ID (defaults to "{analyst_type}_analyst")
config: Configuration dictionary
long_term_memory: Optional ReMeTaskLongTermMemory instance
"""
# Emit runtime deprecation warning
warnings.warn(
f"AnalystAgent('{analyst_type}') is deprecated. "
"Use EvoAgent via UnifiedAgentFactory instead.",
DeprecationWarning,
stacklevel=2,
)
if analyst_type not in ANALYST_TYPES:
raise ValueError(
f"Unknown analyst type: {analyst_type}. "
f"Must be one of: {list(ANALYST_TYPES.keys())}",
)
object.__setattr__(self, "analyst_type_key", analyst_type)
object.__setattr__(
self,
"analyst_persona",
ANALYST_TYPES[analyst_type]["display_name"],
)
if agent_id is None:
agent_id = analyst_type
object.__setattr__(self, "agent_id", agent_id)
object.__setattr__(self, "config", config or {})
object.__setattr__(self, "toolkit", toolkit)
sys_prompt = self._load_system_prompt()
kwargs = {
"name": agent_id,
"sys_prompt": sys_prompt,
"model": model,
"formatter": formatter,
"toolkit": toolkit,
"memory": InMemoryMemory(),
"max_iters": 10,
}
if long_term_memory:
kwargs["long_term_memory"] = long_term_memory
kwargs["long_term_memory_mode"] = "static_control"
super().__init__(**kwargs)
def _load_system_prompt(self) -> str:
"""Load system prompt for analyst"""
return build_agent_system_prompt(
agent_id=self.agent_id,
config_name=self.config.get("config_name", "default"),
toolkit=self.toolkit,
)
async def reply(self, x: Msg = None) -> Msg:
"""
Override reply method to add progress tracking
Args:
x: Input message (content must be str)
Returns:
Response message (content is str)
"""
ticker = None
if x and hasattr(x, "metadata") and x.metadata:
ticker = x.metadata.get("tickers")
if ticker:
progress.update_status(
self.name,
ticker,
f"Starting {self.analyst_persona} analysis",
)
result = await super().reply(x)
if ticker:
progress.update_status(
self.name,
ticker,
"Analysis completed",
)
return result
def reload_runtime_assets(self, active_skill_dirs: Optional[list] = None) -> None:
"""Reload toolkit and system prompt from current run assets."""
from .toolkit_factory import create_agent_toolkit
clear_prompt_factory_cache()
self.toolkit = create_agent_toolkit(
self.agent_id,
self.config.get("config_name", "default"),
active_skill_dirs=active_skill_dirs,
)
self._apply_runtime_sys_prompt(self._load_system_prompt())
def _apply_runtime_sys_prompt(self, sys_prompt: str) -> None:
"""Update the prompt used by future turns and the cached system msg."""
self._sys_prompt = sys_prompt
for msg, _marks in self.memory.content:
if getattr(msg, "role", None) == "system":
msg.content = sys_prompt
break

View File

@@ -8,7 +8,7 @@ import logging
from abc import ABC, abstractmethod
from dataclasses import dataclass, field
from pathlib import Path
from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional, Protocol
from typing import TYPE_CHECKING, Any, Dict, List, Optional
if TYPE_CHECKING:
from .agent import EvoAgent

View File

@@ -8,11 +8,11 @@ from __future__ import annotations
import json
import logging
from dataclasses import dataclass, field, asdict
from dataclasses import dataclass, field
from datetime import datetime
from enum import Enum
from pathlib import Path
from typing import Any, Dict, List, Optional, Set
from typing import Any, Dict, List, Optional
logger = logging.getLogger(__name__)

View File

@@ -31,7 +31,6 @@ from .hooks import (
HOOK_PRE_REASONING,
)
from ..prompts.builder import (
PromptBuilder,
build_system_prompt_from_workspace,
)
from ..agent_workspace import load_agent_workspace_config

View File

@@ -12,11 +12,10 @@ from dataclasses import dataclass, field
from datetime import datetime
from enum import Enum
from pathlib import Path
from typing import Any, Dict, List, Optional, Set
from typing import Any, Dict, List, Optional
from .evaluation_hook import (
EvaluationCollector,
EvaluationResult,
MetricType,
)

View File

@@ -12,7 +12,6 @@ from __future__ import annotations
import asyncio
import json
import logging
from dataclasses import dataclass, field
from datetime import UTC, datetime
from enum import Enum

View File

@@ -1,420 +0,0 @@
# -*- coding: utf-8 -*-
"""
Portfolio Manager Agent - Based on AgentScope ReActAgent
Responsible for decision-making (NOT trade execution)
.. deprecated:: 0.2.0
PMAgent is deprecated and will be removed in a future version.
Use :class:`backend.agents.base.evo_agent.EvoAgent` instead.
See docs/CRITICAL_FIXES.md for migration guide.
"""
import warnings
from pathlib import Path
from typing import Any, Dict, Optional, Callable
from agentscope.agent import ReActAgent
from agentscope.memory import InMemoryMemory, LongTermMemoryBase
from agentscope.message import Msg, TextBlock
from agentscope.tool import Toolkit, ToolResponse
from ..utils.progress import progress
from .prompt_factory import build_agent_system_prompt, clear_prompt_factory_cache
from .team_pipeline_config import update_active_analysts
from ..config.constants import ANALYST_TYPES
# Emit deprecation warning on module import
warnings.warn(
"PMAgent is deprecated. Use EvoAgent instead. "
"See docs/CRITICAL_FIXES.md for migration guide.",
DeprecationWarning,
stacklevel=2,
)
class PMAgent(ReActAgent):
"""
Portfolio Manager Agent - Makes investment decisions
Key features:
1. PM outputs decisions only (action + quantity per ticker)
2. Trade execution happens externally (in pipeline/executor)
3. Supports both backtest and live modes
.. deprecated:: 0.2.0
Use :class:`backend.agents.base.evo_agent.EvoAgent` with
workspace-driven configuration instead.
"""
"""
Portfolio Manager Agent - Makes investment decisions
Key features:
1. PM outputs decisions only (action + quantity per ticker)
2. Trade execution happens externally (in pipeline/executor)
3. Supports both backtest and live modes
"""
def __init__(
self,
name: str = "portfolio_manager",
model: Any = None,
formatter: Any = None,
initial_cash: float = 100000.0,
margin_requirement: float = 0.25,
config: Optional[Dict[str, Any]] = None,
long_term_memory: Optional[LongTermMemoryBase] = None,
toolkit_factory: Any = None,
toolkit_factory_kwargs: Optional[Dict[str, Any]] = None,
toolkit: Optional[Toolkit] = None,
):
# Emit runtime deprecation warning
warnings.warn(
"PMAgent is deprecated. Use EvoAgent via UnifiedAgentFactory instead.",
DeprecationWarning,
stacklevel=2,
)
object.__setattr__(self, "config", config or {})
# Portfolio state
object.__setattr__(
self,
"portfolio",
{
"cash": initial_cash,
"positions": {},
"margin_used": 0.0,
"margin_requirement": margin_requirement,
},
)
# Decisions made in current cycle
object.__setattr__(self, "_decisions", {})
toolkit_factory_kwargs = toolkit_factory_kwargs or {}
object.__setattr__(self, "_toolkit_factory", toolkit_factory)
object.__setattr__(
self,
"_toolkit_factory_kwargs",
toolkit_factory_kwargs,
)
object.__setattr__(self, "_create_team_agent_cb", None)
object.__setattr__(self, "_remove_team_agent_cb", None)
# Create toolkit after local state is ready so bound tool methods can be registered.
if toolkit is None:
if toolkit_factory is not None:
toolkit = toolkit_factory(
name,
self.config.get("config_name", "default"),
owner=self,
**toolkit_factory_kwargs,
)
else:
toolkit = self._create_toolkit()
object.__setattr__(self, "toolkit", toolkit)
sys_prompt = build_agent_system_prompt(
agent_id=name,
config_name=self.config.get("config_name", "default"),
toolkit=self.toolkit,
)
kwargs = {
"name": name,
"sys_prompt": sys_prompt,
"model": model,
"formatter": formatter,
"toolkit": toolkit,
"memory": InMemoryMemory(),
"max_iters": 10,
}
if long_term_memory:
kwargs["long_term_memory"] = long_term_memory
kwargs["long_term_memory_mode"] = "both"
super().__init__(**kwargs)
def _create_toolkit(self) -> Toolkit:
"""Create toolkit with decision recording tool"""
toolkit = Toolkit()
toolkit.register_tool_function(self._make_decision)
return toolkit
def _make_decision(
self,
ticker: str,
action: str,
quantity: int,
confidence: int = 50,
reasoning: str = "",
) -> ToolResponse:
"""
Record a trading decision for a ticker.
Args:
ticker: Stock ticker symbol (e.g., "AAPL")
action: Decision - "long", "short" or "hold"
quantity: Number of shares to trade (0 for hold)
confidence: Confidence level 0-100
reasoning: Explanation for this decision
Returns:
ToolResponse confirming decision recorded
"""
if action not in ["long", "short", "hold"]:
return ToolResponse(
content=[
TextBlock(
type="text",
text=f"Invalid action: {action}. "
"Must be 'long', 'short', or 'hold'.",
),
],
)
self._decisions[ticker] = {
"action": action,
"quantity": quantity if action != "hold" else 0,
"confidence": confidence,
"reasoning": reasoning,
}
return ToolResponse(
content=[
TextBlock(
type="text",
text=f"Decision recorded: {action} "
f"{quantity} shares of {ticker}"
f" (confidence: {confidence}%)",
),
],
)
def _add_team_analyst(self, agent_id: str) -> ToolResponse:
"""Add one analyst to active discussion team."""
config_name = self.config.get("config_name", "default")
project_root = Path(__file__).resolve().parents[2]
active = update_active_analysts(
project_root=project_root,
config_name=config_name,
available_analysts=list(ANALYST_TYPES.keys()),
add=[agent_id],
)
return ToolResponse(
content=[
TextBlock(
type="text",
text=(
f"Active analyst team updated. Added: {agent_id}. "
f"Current active analysts: {', '.join(active)}"
),
),
],
)
def _remove_team_analyst(self, agent_id: str) -> ToolResponse:
"""Remove one analyst from active discussion team."""
callback_msg = ""
callback = self._remove_team_agent_cb
if callback is not None:
callback_msg = callback(agent_id=agent_id)
config_name = self.config.get("config_name", "default")
project_root = Path(__file__).resolve().parents[2]
active = update_active_analysts(
project_root=project_root,
config_name=config_name,
available_analysts=list(ANALYST_TYPES.keys()),
remove=[agent_id],
)
return ToolResponse(
content=[
TextBlock(
type="text",
text=(
f"Active analyst team updated. Removed: {agent_id}. "
f"Current active analysts: {', '.join(active)}"
+ (f" | {callback_msg}" if callback_msg else "")
),
),
],
)
def _set_active_analysts(self, agent_ids: str) -> ToolResponse:
"""Set active analysts from comma-separated agent ids."""
requested = [
item.strip() for item in str(agent_ids or "").split(",") if item.strip()
]
config_name = self.config.get("config_name", "default")
project_root = Path(__file__).resolve().parents[2]
active = update_active_analysts(
project_root=project_root,
config_name=config_name,
available_analysts=list(ANALYST_TYPES.keys()),
set_to=requested,
)
return ToolResponse(
content=[
TextBlock(
type="text",
text=f"Active analyst team set to: {', '.join(active)}",
),
],
)
def _create_team_analyst(self, agent_id: str, analyst_type: str) -> ToolResponse:
"""Create a runtime analyst instance and activate it."""
callback = self._create_team_agent_cb
if callback is None:
return ToolResponse(
content=[
TextBlock(
type="text",
text="Runtime agent creation is not available in current pipeline.",
),
],
)
result = callback(agent_id=agent_id, analyst_type=analyst_type)
return ToolResponse(
content=[
TextBlock(type="text", text=result),
],
)
def set_team_controller(
self,
*,
create_agent_callback: Optional[Callable[..., str]] = None,
remove_agent_callback: Optional[Callable[..., str]] = None,
) -> None:
"""Inject runtime team lifecycle callbacks from pipeline."""
object.__setattr__(self, "_create_team_agent_cb", create_agent_callback)
object.__setattr__(self, "_remove_team_agent_cb", remove_agent_callback)
async def reply(self, x: Msg = None) -> Msg:
"""
Make investment decisions
Returns:
Msg with decisions in metadata
"""
if x is None:
return Msg(
name=self.name,
content="No input provided",
role="assistant",
)
# Clear previous decisions
self._decisions = {}
progress.update_status(
self.name,
None,
"Analyzing and making decisions",
)
result = await super().reply(x)
progress.update_status(self.name, None, "Completed")
# Attach decisions to metadata
if result.metadata is None:
result.metadata = {}
result.metadata["decisions"] = self._decisions.copy()
result.metadata["portfolio"] = self.portfolio.copy()
return result
def get_decisions(self) -> Dict[str, Dict]:
"""Get decisions from current cycle"""
return self._decisions.copy()
def get_portfolio_state(self) -> Dict[str, Any]:
"""Get current portfolio state"""
return self.portfolio.copy()
def load_portfolio_state(self, portfolio: Dict[str, Any]):
"""Load portfolio state"""
if not portfolio:
return
self.portfolio = {
"cash": portfolio.get("cash", self.portfolio["cash"]),
"positions": portfolio.get("positions", {}).copy(),
"margin_used": portfolio.get("margin_used", 0.0),
"margin_requirement": portfolio.get(
"margin_requirement",
self.portfolio["margin_requirement"],
),
}
def update_portfolio(self, portfolio: Dict[str, Any]):
"""Update portfolio after external execution"""
self.portfolio.update(portfolio)
def _has_open_positions(self) -> bool:
"""Return whether the current portfolio still has non-zero positions."""
for position in self.portfolio.get("positions", {}).values():
if position.get("long", 0) or position.get("short", 0):
return True
return False
def can_apply_initial_cash(self) -> bool:
"""Only allow cash rebasing before any positions or margin exist."""
return (
not self._has_open_positions()
and float(self.portfolio.get("margin_used", 0.0) or 0.0) == 0.0
)
def apply_runtime_portfolio_config(
self,
*,
margin_requirement: Optional[float] = None,
initial_cash: Optional[float] = None,
) -> Dict[str, bool]:
"""Apply safe run-time portfolio config updates."""
result = {
"margin_requirement": False,
"initial_cash": False,
}
if margin_requirement is not None:
self.portfolio["margin_requirement"] = float(margin_requirement)
result["margin_requirement"] = True
if initial_cash is not None and self.can_apply_initial_cash():
self.portfolio["cash"] = float(initial_cash)
result["initial_cash"] = True
return result
def reload_runtime_assets(self, active_skill_dirs: Optional[list] = None) -> None:
"""Reload toolkit and system prompt from current run assets."""
from .toolkit_factory import create_agent_toolkit
clear_prompt_factory_cache()
toolkit_factory = self._toolkit_factory or create_agent_toolkit
toolkit_kwargs = dict(self._toolkit_factory_kwargs)
if active_skill_dirs is not None:
toolkit_kwargs["active_skill_dirs"] = active_skill_dirs
self.toolkit = toolkit_factory(
self.name,
self.config.get("config_name", "default"),
owner=self,
**toolkit_kwargs,
)
self._apply_runtime_sys_prompt(
build_agent_system_prompt(
agent_id=self.name,
config_name=self.config.get("config_name", "default"),
toolkit=self.toolkit,
),
)
def _apply_runtime_sys_prompt(self, sys_prompt: str) -> None:
"""Update the prompt used by future turns and the cached system msg."""
self._sys_prompt = sys_prompt
for msg, _marks in self.memory.content:
if getattr(msg, "role", None) == "system":
msg.content = sys_prompt
break

View File

@@ -1,139 +0,0 @@
# -*- coding: utf-8 -*-
"""
Risk Manager Agent - Based on AgentScope ReActAgent
Uses LLM for risk assessment
.. deprecated:: 0.2.0
RiskAgent is deprecated and will be removed in a future version.
Use :class:`backend.agents.base.evo_agent.EvoAgent` instead.
See docs/CRITICAL_FIXES.md for migration guide.
"""
import warnings
from typing import Any, Dict, Optional
from agentscope.agent import ReActAgent
from agentscope.memory import InMemoryMemory, LongTermMemoryBase
from agentscope.message import Msg
from agentscope.tool import Toolkit
from ..utils.progress import progress
from .prompt_factory import build_agent_system_prompt, clear_prompt_factory_cache
# Emit deprecation warning on module import
warnings.warn(
"RiskAgent is deprecated. Use EvoAgent instead. "
"See docs/CRITICAL_FIXES.md for migration guide.",
DeprecationWarning,
stacklevel=2,
)
class RiskAgent(ReActAgent):
"""
Risk Manager Agent - Uses LLM for risk assessment
Inherits from AgentScope's ReActAgent
.. deprecated:: 0.2.0
Use :class:`backend.agents.base.evo_agent.EvoAgent` with
workspace-driven configuration instead.
"""
def __init__(
self,
model: Any,
formatter: Any,
name: str = "risk_manager",
config: Optional[Dict[str, Any]] = None,
long_term_memory: Optional[LongTermMemoryBase] = None,
toolkit: Optional[Toolkit] = None,
):
"""
Initialize Risk Manager Agent
.. deprecated:: 0.2.0
Use :class:`backend.agents.unified_factory.UnifiedAgentFactory`
or :class:`backend.agents.base.evo_agent.EvoAgent` instead.
Args:
model: LLM model instance
formatter: Message formatter instance
name: Agent name
config: Configuration dictionary
long_term_memory: Optional ReMeTaskLongTermMemory instance
"""
# Emit runtime deprecation warning
warnings.warn(
"RiskAgent is deprecated. Use EvoAgent via UnifiedAgentFactory instead.",
DeprecationWarning,
stacklevel=2,
)
object.__setattr__(self, "config", config or {})
object.__setattr__(self, "agent_id", name)
if toolkit is None:
toolkit = Toolkit()
object.__setattr__(self, "toolkit", toolkit)
sys_prompt = self._load_system_prompt()
kwargs = {
"name": name,
"sys_prompt": sys_prompt,
"model": model,
"formatter": formatter,
"toolkit": toolkit,
"memory": InMemoryMemory(),
"max_iters": 10,
}
if long_term_memory:
kwargs["long_term_memory"] = long_term_memory
kwargs["long_term_memory_mode"] = "static_control"
super().__init__(**kwargs)
def _load_system_prompt(self) -> str:
"""Load system prompt for risk manager"""
return build_agent_system_prompt(
agent_id=self.agent_id,
config_name=self.config.get("config_name", "default"),
toolkit=self.toolkit,
)
async def reply(self, x: Msg = None) -> Msg:
"""
Provide risk assessment
Args:
x: Input message (content must be str)
Returns:
Msg with risk warnings (content is str)
"""
progress.update_status(self.name, None, "Assessing risk")
result = await super().reply(x)
progress.update_status(self.name, None, "Risk assessment completed")
return result
def reload_runtime_assets(self, active_skill_dirs: Optional[list] = None) -> None:
"""Reload toolkit and system prompt from current run assets."""
from .toolkit_factory import create_agent_toolkit
clear_prompt_factory_cache()
self.toolkit = create_agent_toolkit(
self.agent_id,
self.config.get("config_name", "default"),
active_skill_dirs=active_skill_dirs,
)
self._apply_runtime_sys_prompt(self._load_system_prompt())
def _apply_runtime_sys_prompt(self, sys_prompt: str) -> None:
"""Update the prompt used by future turns and the cached system msg."""
self._sys_prompt = sys_prompt
for msg, _marks in self.memory.content:
if getattr(msg, "role", None) == "system":
msg.content = sys_prompt
break

View File

@@ -6,7 +6,7 @@ import shutil
import tempfile
import zipfile
from threading import Lock
from typing import Any, Dict, Iterable, Iterator, List, Optional, Set
from typing import Any, Dict, Iterable, List, Optional, Set
from urllib.parse import urlparse
from urllib.request import urlretrieve

View File

@@ -9,7 +9,7 @@ from __future__ import annotations
import asyncio
import logging
from typing import Any, Callable, Dict, List, Optional, Set
from typing import Callable, Dict, List, Set
from agentscope.message import Msg

View File

@@ -10,7 +10,6 @@ from __future__ import annotations
import logging
from typing import Any, Dict, List, Optional
from agentscope.message import Msg
logger = logging.getLogger(__name__)

View File

@@ -11,7 +11,7 @@ from __future__ import annotations
import asyncio
import logging
import uuid
from typing import Any, Awaitable, Callable, Dict, List, Optional, Union
from typing import Any, Awaitable, Callable, Dict, List, Optional
from agentscope.message import Msg

View File

@@ -9,7 +9,7 @@ from __future__ import annotations
import asyncio
import logging
from typing import Any, Awaitable, Callable, Dict, List, Optional, Type
from typing import Any, Dict, List, Optional
from agentscope.message import Msg

View File

@@ -12,7 +12,6 @@ import yaml
from backend.agents.agent_workspace import load_agent_workspace_config
from backend.agents.skills_manager import SkillsManager
from backend.agents.skill_loader import load_skill_from_dir, get_skill_tools
from backend.agents.skill_metadata import parse_skill_metadata
from backend.config.bootstrap_config import get_bootstrap_config_for_run

View File

@@ -2,31 +2,23 @@
"""Unified Agent Factory - Centralized agent creation for 大时代.
This module provides a unified factory for creating all agent types (analysts,
risk manager, portfolio manager) with consistent configuration. It replaces
the scattered agent creation logic in main.py, pipeline.py, and pipeline_runner.py.
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
- Automatic EvoAgent vs Legacy Agent selection based on _resolve_evo_agent_ids()
- 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
import os
from pathlib import Path
from typing import TYPE_CHECKING, Any, Optional, Protocol, TypeVar, Union
from typing import Any, Optional, Protocol
if TYPE_CHECKING:
from backend.agents.base.evo_agent import EvoAgent
from backend.agents.analyst import AnalystAgent
from backend.agents.risk_manager import RiskAgent
from backend.agents.portfolio_manager import PMAgent
# Type aliases for agent types
AgentType = Union["EvoAgent", "AnalystAgent", "RiskAgent", "PMAgent"]
T = TypeVar("T")
from backend.agents.base.evo_agent import EvoAgent
class AgentFactoryProtocol(Protocol):
@@ -39,7 +31,7 @@ class AgentFactoryProtocol(Protocol):
formatter: Any,
active_skill_dirs: Optional[list[Path]] = None,
long_term_memory: Optional[Any] = None,
) -> AnalystAgent | EvoAgent: ...
) -> EvoAgent: ...
def create_risk_manager(
self,
@@ -47,7 +39,7 @@ class AgentFactoryProtocol(Protocol):
formatter: Any,
active_skill_dirs: Optional[list[Path]] = None,
long_term_memory: Optional[Any] = None,
) -> RiskAgent | EvoAgent: ...
) -> EvoAgent: ...
def create_portfolio_manager(
self,
@@ -57,18 +49,14 @@ class AgentFactoryProtocol(Protocol):
margin_requirement: float,
active_skill_dirs: Optional[list[Path]] = None,
long_term_memory: Optional[Any] = None,
) -> PMAgent | EvoAgent: ...
) -> EvoAgent: ...
class UnifiedAgentFactory:
"""Unified factory for creating agents with consistent configuration.
"""Unified factory for creating EvoAgent instances with consistent configuration.
This factory centralizes agent creation logic and automatically selects
between EvoAgent (new) and Legacy Agent based on the EVO_AGENT_IDS
environment variable configuration.
By default, all supported roles use EvoAgent. Set EVO_AGENT_IDS=legacy
to disable EvoAgent entirely.
This factory centralizes agent creation logic and creates EvoAgent instances
for all agent roles (analysts, risk manager, portfolio manager).
Example:
factory = UnifiedAgentFactory(
@@ -103,7 +91,6 @@ class UnifiedAgentFactory:
config_name: str,
skills_manager: Any,
toolkit_factory: Optional[Any] = None,
evo_agent_ids: Optional[set[str]] = None,
):
"""Initialize the agent factory.
@@ -111,49 +98,11 @@ class UnifiedAgentFactory:
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
evo_agent_ids: Optional set of agent IDs to use EvoAgent.
If None, uses _resolve_evo_agent_ids() default.
"""
self.config_name = config_name
self.skills_manager = skills_manager
self.toolkit_factory = toolkit_factory
# Determine which agents should use EvoAgent
if evo_agent_ids is not None:
self._evo_agent_ids = evo_agent_ids
else:
self._evo_agent_ids = self._resolve_evo_agent_ids()
def _resolve_evo_agent_ids(self) -> set[str]:
"""Return agent ids selected to use EvoAgent.
By default, all supported roles use EvoAgent.
EVO_AGENT_IDS can be used to limit to specific roles.
"""
from backend.config.constants import ANALYST_TYPES
all_supported = set(ANALYST_TYPES) | {"risk_manager", "portfolio_manager"}
raw = os.getenv("EVO_AGENT_IDS", "")
if not raw.strip():
# Default: all supported roles use EvoAgent
return all_supported
if raw.strip().lower() in ("legacy", "old", "none"):
return set()
requested = {item.strip() for item in raw.split(",") if item.strip()}
return {
agent_id
for agent_id in requested
if agent_id in ANALYST_TYPES
or agent_id in {"risk_manager", "portfolio_manager"}
}
def _should_use_evo_agent(self, agent_id: str) -> bool:
"""Check if an agent should use EvoAgent."""
return agent_id in self._evo_agent_ids
def _create_toolkit(
self,
agent_type: str,
@@ -202,10 +151,8 @@ class UnifiedAgentFactory:
agent_config: Any,
long_term_memory: Optional[Any] = None,
extra_kwargs: Optional[dict[str, Any]] = None,
) -> "EvoAgent":
) -> EvoAgent:
"""Create an EvoAgent instance."""
from backend.agents.base.evo_agent import EvoAgent
workspace_dir = self.skills_manager.get_agent_asset_dir(
self.config_name, agent_id
)
@@ -239,7 +186,7 @@ class UnifiedAgentFactory:
formatter: Any,
active_skill_dirs: Optional[list[Path]] = None,
long_term_memory: Optional[Any] = None,
) -> "AnalystAgent | EvoAgent":
) -> EvoAgent:
"""Create an analyst agent.
Args:
@@ -250,31 +197,16 @@ class UnifiedAgentFactory:
long_term_memory: Optional long-term memory instance
Returns:
AnalystAgent or EvoAgent instance
EvoAgent instance
"""
toolkit = self._create_toolkit(analyst_type, active_skill_dirs)
if self._should_use_evo_agent(analyst_type):
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,
)
# Legacy path
from backend.agents.analyst import AnalystAgent
return AnalystAgent(
analyst_type=analyst_type,
toolkit=toolkit,
agent_config = self._load_agent_config(analyst_type)
return self._create_evo_agent(
agent_id=analyst_type,
model=model,
formatter=formatter,
agent_id=analyst_type,
config={"config_name": self.config_name},
toolkit=toolkit,
agent_config=agent_config,
long_term_memory=long_term_memory,
)
@@ -284,7 +216,7 @@ class UnifiedAgentFactory:
formatter: Any,
active_skill_dirs: Optional[list[Path]] = None,
long_term_memory: Optional[Any] = None,
) -> "RiskAgent | EvoAgent":
) -> EvoAgent:
"""Create a risk manager agent.
Args:
@@ -294,31 +226,17 @@ class UnifiedAgentFactory:
long_term_memory: Optional long-term memory instance
Returns:
RiskAgent or EvoAgent instance
EvoAgent instance
"""
toolkit = self._create_toolkit("risk_manager", active_skill_dirs)
if self._should_use_evo_agent("risk_manager"):
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,
)
# Legacy path
from backend.agents.risk_manager import RiskAgent
return RiskAgent(
agent_config = self._load_agent_config("risk_manager")
return self._create_evo_agent(
agent_id="risk_manager",
model=model,
formatter=formatter,
name="risk_manager",
config={"config_name": self.config_name},
long_term_memory=long_term_memory,
toolkit=toolkit,
agent_config=agent_config,
long_term_memory=long_term_memory,
)
def create_portfolio_manager(
@@ -329,7 +247,7 @@ class UnifiedAgentFactory:
margin_requirement: float,
active_skill_dirs: Optional[list[Path]] = None,
long_term_memory: Optional[Any] = None,
) -> "PMAgent | EvoAgent":
) -> EvoAgent:
"""Create a portfolio manager agent.
Args:
@@ -341,52 +259,34 @@ class UnifiedAgentFactory:
long_term_memory: Optional long-term memory instance
Returns:
PMAgent or EvoAgent instance
EvoAgent instance
"""
if self._should_use_evo_agent("portfolio_manager"):
agent_config = self._load_agent_config("portfolio_manager")
agent_config = self._load_agent_config("portfolio_manager")
# For PM, toolkit is created after agent (needs owner reference)
from backend.agents.base.evo_agent import EvoAgent
# For PM, toolkit is created after agent (needs owner reference)
workspace_dir = self.skills_manager.get_agent_asset_dir(
self.config_name, "portfolio_manager"
)
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
# Legacy path
from backend.agents.portfolio_manager import PMAgent
return PMAgent(
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,
config={"config_name": self.config_name},
long_term_memory=long_term_memory,
toolkit_factory=self.toolkit_factory,
toolkit_factory_kwargs={"active_skill_dirs": active_skill_dirs or []},
)
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