Files
evotraders/start-dev.sh
cillin 16b54d5ccc 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
2026-04-02 00:55:08 +08:00

496 lines
14 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
#
# 大时代 Development Startup Script
# ================================
#
# 启动模式说明:
# -------------
# 本脚本支持两种启动模式:
#
# 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
# ============================================
# 配置与常量
# ============================================
readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
readonly SCRIPT_NAME="$(basename "$0")"
# 服务端点配置
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=()
# 启动模式: "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_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 "所有必要命令已安装"
}
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
# shellcheck disable=SC1091
source "$SCRIPT_DIR/.env"
set +a
else
log_warn ".env 文件不存在,使用默认配置"
log_info "提示: 复制 env.template 到 .env 并配置您的 API 密钥"
fi
}
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; then
import socket
sock = socket.socket()
sock.settimeout(1.0)
sock.connect(("${target_host}", ${target_port}))
sock.close()
PY
log_info "OpenClaw gateway 可连接: ws://${target_host}:${target_port}"
else
log_warn "OpenClaw gateway 未启动: ws://${target_host}:${target_port}"
log_info " OpenClaw 面板功能将不可用"
fi
}
# ============================================
# 服务启动函数
# ============================================
start_service() {
local name="$1"
local app_path="$2"
local port="$3"
log_info "启动 ${name} (端口 ${port})..."
SERVICE_NAME="${name}" python -m uvicorn "${app_path}" \
--host 0.0.0.0 \
--port "${port}" \
--reload \
--reload-dir backend \
--log-level warning \
--no-access-log &
PIDS+=($!)
}
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+=($!)
}
# ============================================
# 微服务模式启动
# ============================================
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
# ============================================
# 主程序
# ============================================
main() {
# 解析命令行参数
parse_args "$@"
# 显示启动横幅
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
start_microservices_mode
fi
# 等待所有后台进程
wait
}
# 执行主程序
main "$@"