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
496 lines
14 KiB
Bash
Executable File
496 lines
14 KiB
Bash
Executable File
#!/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 "$@"
|