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
204 lines
5.9 KiB
Python
204 lines
5.9 KiB
Python
#!/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()
|