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

View File

@@ -1,103 +1,335 @@
#!/usr/bin/env bash
#
# 大时代 Development Startup Script
# Split-service mode only
# ================================
#
# 启动模式说明:
# -------------
# 本脚本支持两种启动模式:
#
# 1. 微服务模式 (默认) - 启动 4 个独立服务 + Gateway
# 这是推荐的开发模式,各服务独立运行,便于单独调试和重启
# - agent_service (端口 8000): Agent 生命周期管理
# - runtime_service (端口 8003): 运行时配置和 Pipeline 执行
# - trading_service (端口 8001): 市场数据和交易操作
# - news_service (端口 8002): 新闻采集和富化
# - gateway (端口 8765): WebSocket 网关,前端连接入口
#
# 2. 独立模式 (--standalone) - 仅启动 Gateway
# Gateway 内部会自行管理服务,适合快速验证或资源受限环境
#
# 用法:
# ./start-dev.sh # 启动微服务模式
# ./start-dev.sh --standalone # 启动独立模式
# ./start-dev.sh --help # 显示帮助信息
#
set -euo pipefail
echo "=========================================="
echo "大时代 Development Environment"
echo "=========================================="
# ============================================
# 配置与常量
# ============================================
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
readonly SCRIPT_NAME="$(basename "$0")"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cd "${SCRIPT_DIR}"
# 服务端点配置
readonly AGENT_SERVICE_PORT=8000
readonly TRADING_SERVICE_PORT=8001
readonly NEWS_SERVICE_PORT=8002
readonly RUNTIME_SERVICE_PORT=8003
readonly GATEWAY_PORT=8765
# 颜色定义
readonly RED='\033[0;31m'
readonly GREEN='\033[0;32m'
readonly YELLOW='\033[1;33m'
readonly BLUE='\033[0;34m'
readonly CYAN='\033[0;36m'
readonly NC='\033[0m' # No Color
# 进程 ID 数组
PIDS=()
require_command() {
local command_name="$1"
if ! command -v "${command_name}" >/dev/null 2>&1; then
echo -e "${RED}Missing required command: ${command_name}${NC}"
# 启动模式: "microservices" 或 "standalone"
MODE="microservices"
# ============================================
# 工具函数
# ============================================
log_info() {
echo -e "${GREEN}[INFO]${NC} $1"
}
log_warn() {
echo -e "${YELLOW}[WARN]${NC} $1"
}
log_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
log_step() {
echo -e "${CYAN}[STEP]${NC} $1"
}
log_debug() {
echo -e "${BLUE}[DEBUG]${NC} $1"
}
# ============================================
# 帮助信息
# ============================================
show_help() {
cat << 'EOF'
大时代 Development Startup Script
用法:
./start-dev.sh [选项]
选项:
--standalone 以独立模式启动(仅启动 Gateway内部管理服务
--help, -h 显示此帮助信息
模式说明:
微服务模式 (默认):
启动 4 个独立微服务 + Gateway各服务独立进程便于单独调试
- agent_service: http://localhost:8000 (Agent 生命周期)
- trading_service: http://localhost:8001 (市场数据)
- news_service: http://localhost:8002 (新闻服务)
- runtime_service: http://localhost:8003 (运行时管理)
- gateway: ws://localhost:8765 (WebSocket 网关)
独立模式 (--standalone):
仅启动 Gateway由 Gateway 内部自行管理服务
适合快速验证或资源受限环境
环境要求:
- Python 3.9+
- 虚拟环境 (推荐 .venv)
- .env 文件 (可选但推荐)
示例:
./start-dev.sh # 启动微服务模式
./start-dev.sh --standalone # 启动独立模式
EOF
}
# ============================================
# 参数解析
# ============================================
parse_args() {
while [[ $# -gt 0 ]]; do
case "$1" in
--standalone)
MODE="standalone"
shift
;;
--help|-h)
show_help
exit 0
;;
*)
log_warn "未知选项: $1"
log_info "使用 --help 查看帮助信息"
exit 1
;;
esac
done
}
# ============================================
# 启动前检查
# ============================================
check_python_version() {
log_step "检查 Python 版本..."
if ! command -v python >/dev/null 2>&1; then
log_error "未找到 python 命令"
exit 1
fi
local python_version
python_version=$(python --version 2>&1 | awk '{print $2}')
log_debug "Python 版本: $python_version"
python - <<'PY' || {
import sys
if sys.version_info < (3, 9):
print(f"Python 3.9+ 是必需的,当前版本: {sys.version}")
sys.exit(1)
print(f"Python 版本检查通过: {sys.version.split()[0]}")
PY
log_error "Python 版本不符合要求 (需要 3.9+)"
exit 1
}
}
check_virtual_env() {
log_step "检查虚拟环境..."
if [[ -n "${VIRTUAL_ENV:-}" ]]; then
log_info "已激活虚拟环境: $VIRTUAL_ENV"
return 0
fi
if [[ -f "$SCRIPT_DIR/.venv/bin/activate" ]]; then
log_warn "未激活虚拟环境,自动激活 .venv"
# shellcheck disable=SC1091
source "$SCRIPT_DIR/.venv/bin/activate"
log_info "虚拟环境已激活: $VIRTUAL_ENV"
else
log_warn "未找到虚拟环境,使用系统 Python"
fi
}
check_python_module() {
local module_name="$1"
if ! python -c "import ${module_name}" >/dev/null 2>&1; then
echo -e "${RED}Missing required Python module: ${module_name}${NC}"
echo "Install dependencies with one of:"
echo " pip install -r requirements.txt"
echo " pip install -r requirements-dev.txt"
echo " uv pip install -e '.[dev]'"
check_required_commands() {
log_step "检查必要命令..."
local missing=()
if ! command -v python >/dev/null 2>&1; then
missing+=("python")
fi
if ! command -v lsof >/dev/null 2>&1; then
missing+=("lsof")
fi
if [[ ${#missing[@]} -gt 0 ]]; then
log_error "缺少必要命令: ${missing[*]}"
log_info "请安装缺失的命令后重试"
exit 1
fi
log_info "所有必要命令已安装"
}
load_env_file() {
if [ -f .env ]; then
echo -e "${GREEN}Loading environment from .env${NC}"
check_python_modules() {
log_step "检查 Python 依赖模块..."
local modules=("fastapi" "uvicorn" "websockets" "yaml" "dotenv")
local missing=()
for module in "${modules[@]}"; do
if ! python -c "import $module" 2>/dev/null; then
missing+=("$module")
fi
done
if [[ ${#missing[@]} -gt 0 ]]; then
log_error "缺少 Python 模块: ${missing[*]}"
log_info "请安装依赖: uv pip install -e '.[dev]' 或 pip install -r requirements.txt"
exit 1
fi
log_info "所有依赖模块已安装"
}
check_env_file() {
log_step "检查环境配置文件..."
if [[ -f "$SCRIPT_DIR/.env" ]]; then
log_info "加载环境变量: .env"
set -a
source .env
# shellcheck disable=SC1091
source "$SCRIPT_DIR/.env"
set +a
else
echo -e "${YELLOW}Warning: .env file not found. Copy env.template to .env first if you need live credentials.${NC}"
log_warn ".env 文件不存在,使用默认配置"
log_info "提示: 复制 env.template 到 .env 并配置您的 API 密钥"
fi
}
check_env_var() {
local var_name="$1"
local severity="${2:-warn}"
local value="${!var_name:-}"
if [ -z "${value}" ]; then
if [ "${severity}" = "error" ]; then
echo -e "${RED}Missing required environment variable: ${var_name}${NC}"
exit 1
fi
echo -e "${YELLOW}Warning: ${var_name} is not set${NC}"
check_ports() {
log_step "检查端口占用情况..."
local ports=()
if [[ "$MODE" == "microservices" ]]; then
ports=($AGENT_SERVICE_PORT $TRADING_SERVICE_PORT $NEWS_SERVICE_PORT $RUNTIME_SERVICE_PORT $GATEWAY_PORT)
else
ports=($GATEWAY_PORT)
fi
local occupied=()
for port in "${ports[@]}"; do
if lsof -Pi :"$port" -sTCP:LISTEN -t >/dev/null 2>&1; then
occupied+=("$port")
fi
done
if [[ ${#occupied[@]} -gt 0 ]]; then
log_warn "以下端口已被占用: ${occupied[*]}"
log_info "尝试释放端口..."
for port in "${occupied[@]}"; do
kill_port "$port"
done
else
log_info "所有端口可用"
fi
}
kill_port() {
local port="$1"
local pids
pids=$(lsof -ti :"$port" 2>/dev/null || true)
if [[ -n "$pids" ]]; then
log_warn "释放端口 $port (PID: $pids)"
echo "$pids" | xargs kill -9 2>/dev/null || true
sleep 0.5
fi
}
check_optional_services() {
log_step "检查可选服务..."
# 检查 npm用于前端
if ! command -v npm >/dev/null 2>&1; then
log_warn "npm 未安装,前端启动功能不可用"
else
log_info "npm 已安装"
fi
# 检查 OpenClaw gateway
check_openclaw_gateway
}
check_openclaw_gateway() {
local target_host="127.0.0.1"
local target_port="18789"
if python - <<PY >/dev/null 2>&1
if python - <<PY >/dev/null 2>&1; then
import socket
sock = socket.socket()
sock.settimeout(1.0)
sock.connect(("${target_host}", ${target_port}))
sock.close()
PY
then
echo -e "${GREEN}OpenClaw gateway reachable at ws://${target_host}:${target_port}${NC}"
log_info "OpenClaw gateway 可连接: ws://${target_host}:${target_port}"
else
echo -e "${YELLOW}Warning: OpenClaw gateway is not reachable at ws://${target_host}:${target_port}${NC}"
echo " OpenClaw panel features may be unavailable until it is started."
log_warn "OpenClaw gateway 未启动: ws://${target_host}:${target_port}"
log_info " OpenClaw 面板功能将不可用"
fi
}
print_prereq_help() {
echo "Environment checks:"
echo " - repo root: ${SCRIPT_DIR}"
echo " - python: $(command -v python)"
if [ -n "${VIRTUAL_ENV:-}" ]; then
echo " - virtualenv: ${VIRTUAL_ENV}"
else
echo " - virtualenv: not activated"
fi
}
# ============================================
# 服务启动函数
# ============================================
start_service() {
local name="$1"
local app_path="$2"
local port="$3"
echo -e "${GREEN}Starting ${name}${NC} on port ${port}..."
log_info "启动 ${name} (端口 ${port})..."
SERVICE_NAME="${name}" python -m uvicorn "${app_path}" \
--host 0.0.0.0 \
--port "${port}" \
@@ -108,111 +340,156 @@ start_service() {
PIDS+=($!)
}
cleanup() {
if [ "${#PIDS[@]}" -gt 0 ]; then
echo ""
echo -e "${YELLOW}Stopping development services...${NC}"
kill "${PIDS[@]}" 2>/dev/null || true
wait "${PIDS[@]}" 2>/dev/null || true
fi
start_gateway() {
log_step "启动 Gateway (WebSocket 服务)..."
log_info "Gateway 将作为子进程启动 (端口 ${GATEWAY_PORT})"
log_info "前端连接地址: ws://localhost:${GATEWAY_PORT}"
SERVICE_NAME="gateway" python -m backend.main \
--mode live \
--host 0.0.0.0 \
--port "$GATEWAY_PORT" &
PIDS+=($!)
}
kill_port() {
local port="$1"
local pids=$(lsof -ti :${port} 2>/dev/null || true)
if [ -n "$pids" ]; then
echo -e "${YELLOW}Port ${port} is in use, killing PID(s): ${pids}${NC}"
echo "$pids" | xargs kill -9 2>/dev/null || true
sleep 0.5
# ============================================
# 微服务模式启动
# ============================================
start_microservices_mode() {
log_step "启动微服务模式..."
echo ""
echo -e "${CYAN}==========================================${NC}"
echo -e "${CYAN} 服务端点 ${NC}"
echo -e "${CYAN}==========================================${NC}"
echo -e " agent_service: http://localhost:${AGENT_SERVICE_PORT}"
echo -e " runtime_service: http://localhost:${RUNTIME_SERVICE_PORT}"
echo -e " trading_service: http://localhost:${TRADING_SERVICE_PORT}"
echo -e " news_service: http://localhost:${NEWS_SERVICE_PORT}"
echo -e " gateway: ws://localhost:${GATEWAY_PORT}"
echo -e "${CYAN}==========================================${NC}"
echo ""
# 设置服务 URL 环境变量
export TRADING_SERVICE_URL="${TRADING_SERVICE_URL:-http://localhost:${TRADING_SERVICE_PORT}}"
export NEWS_SERVICE_URL="${NEWS_SERVICE_URL:-http://localhost:${NEWS_SERVICE_PORT}}"
export RUNTIME_SERVICE_URL="${RUNTIME_SERVICE_URL:-http://localhost:${RUNTIME_SERVICE_PORT}}"
export OPENCLAW_SERVICE_URL="${OPENCLAW_SERVICE_URL:-http://localhost:18789}"
export ENABLE_DASHBOARD_COMPAT_EXPORTS="${ENABLE_DASHBOARD_COMPAT_EXPORTS:-true}"
log_debug "环境变量:"
log_debug " TRADING_SERVICE_URL=${TRADING_SERVICE_URL}"
log_debug " NEWS_SERVICE_URL=${NEWS_SERVICE_URL}"
log_debug " RUNTIME_SERVICE_URL=${RUNTIME_SERVICE_URL}"
log_debug " OPENCLAW_SERVICE_URL=${OPENCLAW_SERVICE_URL}"
echo ""
# 启动 4 个微服务
start_service "agent_service" "backend.apps.agent_service:app" "$AGENT_SERVICE_PORT"
start_service "runtime_service" "backend.apps.runtime_service:app" "$RUNTIME_SERVICE_PORT"
start_service "trading_service" "backend.apps.trading_service:app" "$TRADING_SERVICE_PORT"
start_service "news_service" "backend.apps.news_service:app" "$NEWS_SERVICE_PORT"
# 启动 Gateway作为子进程
start_gateway
echo ""
log_info "所有服务已启动"
log_info "按 Ctrl+C 停止所有服务"
echo ""
}
# ============================================
# 独立模式启动
# ============================================
start_standalone_mode() {
log_step "启动独立模式..."
echo ""
echo -e "${CYAN}==========================================${NC}"
echo -e "${CYAN} 独立模式 ${NC}"
echo -e "${CYAN}==========================================${NC}"
echo -e " gateway: ws://localhost:${GATEWAY_PORT}"
echo -e "${CYAN}==========================================${NC}"
echo ""
log_info "Gateway 将内部管理服务"
# 启动 Gateway独立模式
start_gateway
echo ""
log_info "Gateway 已启动(独立模式)"
log_info "按 Ctrl+C 停止服务"
echo ""
}
# ============================================
# 清理与信号处理
# ============================================
cleanup() {
if [[ ${#PIDS[@]} -gt 0 ]]; then
echo ""
log_step "正在停止服务..."
for pid in "${PIDS[@]}"; do
if kill -0 "$pid" 2>/dev/null; then
kill "$pid" 2>/dev/null || true
fi
done
# 等待进程结束
wait "${PIDS[@]}" 2>/dev/null || true
log_info "所有服务已停止"
fi
}
trap cleanup EXIT INT TERM
if [ $# -gt 0 ]; then
echo -e "${YELLOW}Ignoring legacy mode argument(s): $*${NC}"
echo "Split-service mode is now the only supported development mode."
fi
# ============================================
# 主程序
# ============================================
require_command python
require_command lsof
main() {
# 解析命令行参数
parse_args "$@"
if [ -z "${VIRTUAL_ENV:-}" ]; then
if [ -f ".venv/bin/activate" ]; then
echo -e "${YELLOW}Virtual environment not activated; auto-activating .venv${NC}"
# shellcheck disable=SC1091
source .venv/bin/activate
# 显示启动横幅
echo ""
echo -e "${CYAN}==========================================${NC}"
echo -e "${CYAN} 大时代 Development Environment ${NC}"
echo -e "${CYAN}==========================================${NC}"
echo ""
# 切换到项目根目录
cd "$SCRIPT_DIR"
log_debug "工作目录: $SCRIPT_DIR"
# 启动前检查
check_required_commands
check_python_version
check_virtual_env
check_python_modules
check_env_file
check_ports
check_optional_services
echo ""
echo -e "${GREEN}==========================================${NC}"
echo -e "${GREEN} 启动前检查完成 ${NC}"
echo -e "${GREEN}==========================================${NC}"
echo ""
# 根据模式启动服务
if [[ "$MODE" == "standalone" ]]; then
start_standalone_mode
else
echo -e "${YELLOW}Warning: no active virtual environment and .venv not found${NC}"
start_microservices_mode
fi
fi
load_env_file
# 等待所有后台进程
wait
}
print_prereq_help
python - <<'PY'
import sys
if sys.version_info < (3, 9):
raise SystemExit("Python 3.9+ is required")
print(f"Python version OK: {sys.version.split()[0]}")
PY
check_python_module fastapi
check_python_module uvicorn
check_python_module websockets
check_python_module yaml
check_python_module dotenv
check_env_var OPENAI_API_KEY
check_env_var FINNHUB_API_KEY
check_env_var FIN_DATA_SOURCE
if ! command -v npm >/dev/null 2>&1; then
echo -e "${YELLOW}Warning: npm is not installed. Frontend startup via 'evotraders frontend' will not work.${NC}"
fi
export TRADING_SERVICE_URL="${TRADING_SERVICE_URL:-http://localhost:8001}"
export NEWS_SERVICE_URL="${NEWS_SERVICE_URL:-http://localhost:8002}"
export RUNTIME_SERVICE_URL="${RUNTIME_SERVICE_URL:-http://localhost:8003}"
export OPENCLAW_SERVICE_URL="${OPENCLAW_SERVICE_URL:-http://localhost:18789}"
check_openclaw_gateway
echo ""
echo -e "${GREEN}Starting 大时代 split services (default mode)...${NC}"
echo " agent_service: http://localhost:8000"
echo " runtime_service: http://localhost:8003"
echo " openclaw_gateway: ws://localhost:18789"
echo " trading_service: http://localhost:8001"
echo " news_service: http://localhost:8002"
echo ""
echo "Exported backend preference URLs:"
echo " TRADING_SERVICE_URL=${TRADING_SERVICE_URL}"
echo " NEWS_SERVICE_URL=${NEWS_SERVICE_URL}"
echo " RUNTIME_SERVICE_URL=${RUNTIME_SERVICE_URL}"
echo " OPENCLAW_SERVICE_URL=${OPENCLAW_SERVICE_URL}"
echo ""
echo -e "${GREEN}Checking ports...${NC}"
kill_port 8000
kill_port 8001
kill_port 8002
kill_port 8003
kill_port 8765
start_service "agent_service" "backend.apps.agent_service:app" 8000
start_service "runtime_service" "backend.apps.runtime_service:app" 8003
start_service "trading_service" "backend.apps.trading_service:app" 8001
start_service "news_service" "backend.apps.news_service:app" 8002
echo -e "${GREEN}Starting Gateway (WebSocket, port 8765)...${NC}"
SERVICE_NAME="gateway" python -m backend.main \
--mode live \
--host 0.0.0.0 \
--port 8765 &
PIDS+=($!)
echo -e "${GREEN}Split services are running.${NC}"
echo "Use Ctrl+C to stop all services."
wait
# 执行主程序
main "$@"