feat(agent): complete EvoAgent integration for all 6 agent roles
Migrate all agent roles from Legacy to EvoAgent architecture: - fundamentals_analyst, technical_analyst, sentiment_analyst, valuation_analyst - risk_manager, portfolio_manager Key changes: - EvoAgent now supports Portfolio Manager compatibility methods (_make_decision, get_decisions, get_portfolio_state, load_portfolio_state, update_portfolio) - Add UnifiedAgentFactory for centralized agent creation - ToolGuard with batch approval API and WebSocket broadcast - Legacy agents marked deprecated (AnalystAgent, RiskAgent, PMAgent) - Remove backend/agents/compat.py migration shim - Add run_id alongside workspace_id for semantic clarity - Complete integration test coverage (13 tests) - All smoke tests passing for 6 agent roles Constraint: Must maintain backward compatibility with existing run configs Constraint: Memory support must work with EvoAgent (no fallback to Legacy) Rejected: Separate PM implementation for EvoAgent | unified approach cleaner Confidence: high Scope-risk: broad Directive: EVO_AGENT_IDS env var still respected but defaults to all roles Not-tested: Kubernetes sandbox mode for skill execution
This commit is contained in:
@@ -90,6 +90,8 @@ class EvoAgent(ToolGuardMixin, ReActAgent):
|
||||
sys_prompt: Optional[str] = None,
|
||||
max_iters: int = 10,
|
||||
memory: Optional[Any] = None,
|
||||
long_term_memory: Optional[Any] = None,
|
||||
long_term_memory_mode: str = "static_control",
|
||||
enable_tool_guard: bool = True,
|
||||
enable_bootstrap_hook: bool = True,
|
||||
enable_memory_compaction: bool = False,
|
||||
@@ -97,6 +99,9 @@ class EvoAgent(ToolGuardMixin, ReActAgent):
|
||||
memory_compact_threshold: Optional[int] = None,
|
||||
env_context: Optional[str] = None,
|
||||
prompt_files: Optional[List[str]] = None,
|
||||
# Portfolio manager specific parameters
|
||||
initial_cash: Optional[float] = None,
|
||||
margin_requirement: Optional[float] = None,
|
||||
):
|
||||
"""Initialize EvoAgent.
|
||||
|
||||
@@ -144,16 +149,24 @@ class EvoAgent(ToolGuardMixin, ReActAgent):
|
||||
# Initialize hook manager
|
||||
self._hook_manager = HookManager()
|
||||
|
||||
# Build kwargs for parent ReActAgent
|
||||
kwargs = {
|
||||
"name": agent_id,
|
||||
"model": model,
|
||||
"sys_prompt": self._sys_prompt,
|
||||
"toolkit": toolkit,
|
||||
"memory": memory or InMemoryMemory(),
|
||||
"formatter": formatter,
|
||||
"max_iters": max_iters,
|
||||
}
|
||||
|
||||
# Add long-term memory if provided
|
||||
if long_term_memory:
|
||||
kwargs["long_term_memory"] = long_term_memory
|
||||
kwargs["long_term_memory_mode"] = long_term_memory_mode
|
||||
|
||||
# Initialize parent ReActAgent
|
||||
super().__init__(
|
||||
name=agent_id,
|
||||
model=model,
|
||||
sys_prompt=self._sys_prompt,
|
||||
toolkit=toolkit,
|
||||
memory=memory or InMemoryMemory(),
|
||||
formatter=formatter,
|
||||
max_iters=max_iters,
|
||||
)
|
||||
super().__init__(**kwargs)
|
||||
|
||||
# Register hooks
|
||||
self._register_hooks(
|
||||
@@ -366,6 +379,110 @@ class EvoAgent(ToolGuardMixin, ReActAgent):
|
||||
self.toolkit = new_toolkit
|
||||
logger.info("Skills reloaded for agent: %s", self.agent_id)
|
||||
|
||||
def _make_decision(
|
||||
self,
|
||||
ticker: str,
|
||||
action: str,
|
||||
quantity: int,
|
||||
confidence: int = 50,
|
||||
reasoning: str = "",
|
||||
) -> "ToolResponse":
|
||||
"""Record a trading decision for a ticker (PM agent compatibility).
|
||||
|
||||
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
|
||||
"""
|
||||
from agentscope.message import TextBlock
|
||||
from agentscope.tool import ToolResponse
|
||||
|
||||
if action not in ["long", "short", "hold"]:
|
||||
return ToolResponse(
|
||||
content=[
|
||||
TextBlock(
|
||||
type="text",
|
||||
text=f"Invalid action: {action}. Must be 'long', 'short', or 'hold'.",
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
# Store decision in metadata for retrieval
|
||||
if not hasattr(self, "_decisions"):
|
||||
self._decisions = {}
|
||||
|
||||
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} {quantity} shares of {ticker} "
|
||||
f"(confidence: {confidence}%)",
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
def get_decisions(self) -> Dict[str, Dict]:
|
||||
"""Get decisions from current cycle (PM compatibility)."""
|
||||
return getattr(self, "_decisions", {}).copy()
|
||||
|
||||
def get_portfolio_state(self) -> Dict[str, Any]:
|
||||
"""Get current portfolio state (PM compatibility)."""
|
||||
return getattr(self, "_portfolio", {}).copy()
|
||||
|
||||
def load_portfolio_state(self, portfolio: Dict[str, Any]) -> None:
|
||||
"""Load portfolio state (PM compatibility).
|
||||
|
||||
Args:
|
||||
portfolio: Portfolio state dict with cash, positions, margin_used
|
||||
"""
|
||||
if not portfolio:
|
||||
return
|
||||
|
||||
if not hasattr(self, "_portfolio"):
|
||||
self._portfolio = {
|
||||
"cash": 100000.0,
|
||||
"positions": {},
|
||||
"margin_used": 0.0,
|
||||
"margin_requirement": 0.25,
|
||||
}
|
||||
|
||||
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]) -> None:
|
||||
"""Update portfolio after external execution (PM compatibility).
|
||||
|
||||
Args:
|
||||
portfolio: Portfolio updates to apply
|
||||
"""
|
||||
if not hasattr(self, "_portfolio"):
|
||||
self._portfolio = {
|
||||
"cash": 100000.0,
|
||||
"positions": {},
|
||||
"margin_used": 0.0,
|
||||
"margin_requirement": 0.25,
|
||||
}
|
||||
self._portfolio.update(portfolio)
|
||||
|
||||
def rebuild_sys_prompt(self) -> None:
|
||||
"""Rebuild and replace the system prompt at runtime.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user