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:
2026-04-02 00:55:08 +08:00
parent 0fa413380c
commit 16b54d5ccc
73 changed files with 9454 additions and 904 deletions

203
scripts/test_sandbox.py Normal file
View File

@@ -0,0 +1,203 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
沙盒执行器测试脚本
测试多模式技能沙盒执行器的基本功能。
默认使用 none 模式(无沙盒)。
"""
import os
import sys
# 确保后端目录在路径中
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "backend"))
def test_sandbox_initialization():
"""测试沙盒初始化"""
print("=" * 60)
print("测试 1: 沙盒初始化")
print("=" * 60)
from backend.tools.sandboxed_executor import get_sandbox, SkillSandbox
# 重置单例(确保测试干净)
SkillSandbox._instance = None
# 默认应该使用 none 模式
sandbox = get_sandbox()
assert sandbox.current_mode == "none", f"期望模式 'none',实际 '{sandbox.current_mode}'"
print(f"✓ 沙盒模式: {sandbox.current_mode}")
print(f"✓ 后端类型: {type(sandbox._backend).__name__}")
return sandbox
def test_no_sandbox_warning():
"""测试无沙盒模式的安全警告"""
print("\n" + "=" * 60)
print("测试 2: 无沙盒模式安全警告")
print("=" * 60)
import warnings
from backend.tools.sandboxed_executor import NoSandboxBackend
backend = NoSandboxBackend()
# 捕获警告
with warnings.catch_warnings(record=True) as w:
warnings.simplefilter("always")
# 执行会触发警告
try:
backend.execute(
skill_name="builtin/valuation_review",
function_name="build_dcf_report",
function_args={"rows": [], "current_date": "2024-01-01"},
)
except Exception:
pass # 我们不关心执行结果,只关心警告
# 检查是否产生了警告
runtime_warnings = [x for x in w if issubclass(x.category, RuntimeWarning)]
if runtime_warnings:
print("✓ 安全警告已触发")
print(f" 警告内容: {runtime_warnings[0].message}")
else:
print("⚠ 未触发安全警告(可能已缓存)")
def test_docker_config():
"""测试 Docker 模式配置解析"""
print("\n" + "=" * 60)
print("测试 3: Docker 模式配置解析")
print("=" * 60)
# 设置环境变量
os.environ["SKILL_SANDBOX_MODE"] = "docker"
os.environ["SKILL_SANDBOX_MEMORY_LIMIT"] = "1g"
os.environ["SKILL_SANDBOX_CPU_LIMIT"] = "2.0"
from backend.tools.sandboxed_executor import SkillSandbox
# 重置单例
SkillSandbox._instance = None
try:
sandbox = SkillSandbox()
print(f"✓ 沙盒模式: {sandbox.current_mode}")
print(f"✓ 后端类型: {type(sandbox._backend).__name__}")
# 检查配置
backend = sandbox._backend
assert backend.config["memory_limit"] == "1g"
assert backend.config["cpu_limit"] == 2.0
print(f"✓ 内存限制: {backend.config['memory_limit']}")
print(f"✓ CPU 限制: {backend.config['cpu_limit']}")
except Exception as e:
print(f"⚠ Docker 后端创建失败(预期,可能未安装 agentscope-runtime: {e}")
# 恢复环境变量
os.environ["SKILL_SANDBOX_MODE"] = "none"
SkillSandbox._instance = None
def test_analysis_tools_import():
"""测试分析工具导入"""
print("\n" + "=" * 60)
print("测试 4: 分析工具导入")
print("=" * 60)
try:
from backend.tools.analysis_tools import (
TOOL_REGISTRY,
_sandbox,
dcf_valuation_analysis,
)
print(f"✓ TOOL_REGISTRY 包含 {len(TOOL_REGISTRY)} 个工具")
print(f"✓ _sandbox 实例模式: {_sandbox.current_mode}")
print(f"✓ dcf_valuation_analysis 函数可用")
# 检查估值分析工具是否都在
valuation_tools = [
"dcf_valuation_analysis",
"owner_earnings_valuation_analysis",
"ev_ebitda_valuation_analysis",
"residual_income_valuation_analysis",
]
for tool in valuation_tools:
if tool in TOOL_REGISTRY:
print(f"{tool}")
else:
print(f"{tool} 缺失")
except Exception as e:
print(f"✗ 导入失败: {e}")
import traceback
traceback.print_exc()
def test_skill_execution_mock():
"""测试技能执行(模拟)"""
print("\n" + "=" * 60)
print("测试 5: 技能执行(无沙盒模式)")
print("=" * 60)
from backend.tools.sandboxed_executor import get_sandbox
sandbox = get_sandbox()
# 使用模拟参数调用
try:
# 注意:这需要实际的技能模块存在
result = sandbox.execute_skill(
skill_name="builtin/valuation_review",
function_name="build_dcf_report",
function_args={
"rows": [{"ticker": "AAPL", "current_fcf": 100000000}],
"current_date": "2024-01-01",
},
)
print(f"✓ 技能执行成功")
print(f" 结果类型: {type(result)}")
print(f" 结果预览: {str(result)[:100]}...")
except Exception as e:
print(f"⚠ 技能执行失败(可能缺少依赖或数据): {e}")
def main():
"""主测试函数"""
print("\n" + "=" * 60)
print("技能沙盒执行器测试")
print("=" * 60)
print(f"当前 SKILL_SANDBOX_MODE: {os.getenv('SKILL_SANDBOX_MODE', '未设置(默认 none')}")
# 确保使用默认模式测试
os.environ["SKILL_SANDBOX_MODE"] = "none"
try:
test_sandbox_initialization()
test_no_sandbox_warning()
test_docker_config()
test_analysis_tools_import()
test_skill_execution_mock()
print("\n" + "=" * 60)
print("测试完成")
print("=" * 60)
except Exception as e:
print(f"\n✗ 测试失败: {e}")
import traceback
traceback.print_exc()
sys.exit(1)
if __name__ == "__main__":
main()