From ccf25eb1f97fdc5a2f1c242af068d7e2fbdf0001 Mon Sep 17 00:00:00 2001 From: wangwei Date: Mon, 22 Jun 2026 14:28:44 +0800 Subject: [PATCH] feat: add Linux deployment scripts (deploy/start/stop/run_eval) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- deploy.sh | 173 ++++++++++++++++++++++++++++++++++++++++++++++++++++ run_eval.sh | 147 ++++++++++++++++++++++++++++++++++++++++++++ start.sh | 94 ++++++++++++++++++++++++++++ stop.sh | 68 +++++++++++++++++++++ 4 files changed, 482 insertions(+) create mode 100644 deploy.sh create mode 100644 run_eval.sh create mode 100644 start.sh create mode 100644 stop.sh diff --git a/deploy.sh b/deploy.sh new file mode 100644 index 0000000..9c09e70 --- /dev/null +++ b/deploy.sh @@ -0,0 +1,173 @@ +#!/usr/bin/env bash +# deploy.sh — Siemens RAGAS 一键部署脚本(Linux) +# 用法:bash deploy.sh +# 功能:检查环境 → 安装依赖 → 初始化配置 → 启动后台服务 + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "$SCRIPT_DIR" + +# ── 颜色输出 ────────────────────────────────────────────────────── +if [ -t 1 ]; then + GREEN='\033[0;32m'; YELLOW='\033[1;33m'; RED='\033[0;31m'; CYAN='\033[0;36m'; NC='\033[0m' +else + GREEN=''; YELLOW=''; RED=''; CYAN=''; NC='' +fi + +ok() { echo -e "${GREEN}[OK]${NC} $*"; } +warn() { echo -e "${YELLOW}[WARN]${NC} $*"; } +err() { echo -e "${RED}[ERROR]${NC} $*" >&2; } +info() { echo -e "${CYAN}[INFO]${NC} $*"; } + +echo "" +echo -e "${CYAN}============================================================${NC}" +echo -e "${CYAN} Siemens RAGAS Console — Linux 一键部署${NC}" +echo -e "${CYAN}============================================================${NC}" +echo "" + +# ── 阶段 1:Python 版本检查 ─────────────────────────────────────── +info "阶段 1/7:检查 Python 版本..." + +PYTHON_BIN="" +for candidate in python3.12 python3.13 python3.14 python3; do + if command -v "$candidate" &>/dev/null; then + version=$("$candidate" -c "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}')" 2>/dev/null || true) + major=$(echo "$version" | cut -d. -f1) + minor=$(echo "$version" | cut -d. -f2) + if [ "${major:-0}" -ge 3 ] && [ "${minor:-0}" -ge 12 ]; then + PYTHON_BIN="$candidate" + ok "Python $version ($candidate)" + break + fi + fi +done + +if [ -z "$PYTHON_BIN" ]; then + err "未找到 Python 3.12+。请安装后重试。" + err " Ubuntu/Debian: sudo apt install python3.12 python3.12-venv" + err " CentOS/RHEL: sudo dnf install python3.12" + exit 1 +fi + +# ── 阶段 2:虚拟环境 ────────────────────────────────────────────── +info "阶段 2/7:准备虚拟环境..." + +if [ -d ".venv" ] && [ -f ".venv/bin/python" ]; then + ok ".venv 已存在,跳过创建" +else + info "创建 .venv..." + "$PYTHON_BIN" -m venv .venv + ok ".venv 创建完成" +fi + +PIP=".venv/bin/pip" +PYTHON=".venv/bin/python" + +# ── 阶段 3:安装依赖 ────────────────────────────────────────────── +info "阶段 3/7:安装项目依赖(可能需要几分钟)..." + +"$PIP" install --upgrade pip -q +ok "pip 已升级" + +"$PIP" install -e . -q +ok "项目依赖安装完成(pyproject.toml)" + +"$PIP" install fastapi uvicorn httpx -q +ok "Web 服务依赖安装完成(fastapi / uvicorn / httpx)" + +# ── 阶段 4:配置文件 ────────────────────────────────────────────── +info "阶段 4/7:初始化配置文件..." + +if [ ! -f ".env" ]; then + cp .env.example .env + warn ".env 已从 .env.example 复制,请编辑填写实际的 API Key 等配置后再启动:" + warn " nano .env 或 vim .env" + warn " 关键字段:OPENAI_API_KEY, OPENAI_BASE_URL, ALIBABA_ACCESS_KEY_ID, ALIBABA_ACCESS_KEY_SECRET" +else + ok ".env 已存在,跳过" +fi + +# ── 阶段 5:目录初始化 ──────────────────────────────────────────── +info "阶段 5/7:初始化目录结构..." + +mkdir -p configs logs outputs datasets +ok "目录就绪:configs/ logs/ outputs/ datasets/" + +# 确保其他脚本有执行权限 +for script in start.sh stop.sh run_eval.sh; do + [ -f "$script" ] && chmod +x "$script" +done +ok "辅助脚本已设置执行权限" + +# ── 阶段 6:Demo 数据 ───────────────────────────────────────────── +info "阶段 6/7:初始化演示数据..." + +DEMO_DIR="outputs/kba-knowledge-base-offline-baseline" +if [ -d "$DEMO_DIR" ]; then + ok "演示数据已存在,跳过" +else + info "生成演示数据(scripts/seed_sample_run.py)..." + if "$PYTHON" scripts/seed_sample_run.py; then + ok "演示数据生成完成" + else + warn "演示数据生成失败,控制台报告页将为空(服务仍可正常启动)" + fi +fi + +# ── 阶段 7:启动服务 ────────────────────────────────────────────── +info "阶段 7/7:启动 Web 服务..." + +# 检查 .env 是否包含默认占位符 +if grep -q "your-api-key" .env 2>/dev/null; then + warn ".env 中仍包含默认占位符,部分功能(评估执行)将不可用" + warn "请编辑 .env 后重新运行 start.sh" +fi + +# 端口检测 +PORT=8800 +if ss -tlnp 2>/dev/null | grep -q ":$PORT " || netstat -tlnp 2>/dev/null | grep -q ":$PORT "; then + warn "端口 $PORT 已被占用,尝试 8801..." + PORT=8801 + if ss -tlnp 2>/dev/null | grep -q ":$PORT " || netstat -tlnp 2>/dev/null | grep -q ":$PORT "; then + err "端口 8800 和 8801 均被占用。请手动运行:" + err " .venv/bin/python webmain.py --host 0.0.0.0 --port " + exit 1 + fi +fi + +# 清理残留 PID +if [ -f ".server.pid" ]; then + OLD_PID=$(cat .server.pid) + if kill -0 "$OLD_PID" 2>/dev/null; then + warn "检测到已有服务进程 (PID=$OLD_PID),停止旧进程..." + kill "$OLD_PID" 2>/dev/null || true + sleep 1 + fi + rm -f .server.pid +fi + +# 后台启动 +nohup "$PYTHON" webmain.py --host 0.0.0.0 --port "$PORT" >> logs/server.log 2>&1 & +SERVER_PID=$! +echo "$SERVER_PID" > .server.pid + +# 等待 3 秒验证进程存活 +sleep 3 +if kill -0 "$SERVER_PID" 2>/dev/null; then + ok "服务已启动 (PID=$SERVER_PID)" + echo "" + echo -e "${CYAN}============================================================${NC}" + echo -e "${GREEN} 部署成功!${NC}" + echo -e "${GREEN} 访问地址: http://$(hostname -I | awk '{print $1}'):${PORT}${NC}" + echo -e "${GREEN} 本机访问: http://127.0.0.1:${PORT}${NC}" + echo -e "${CYAN} 服务日志: tail -f logs/server.log${NC}" + echo -e "${CYAN} 停止服务: bash stop.sh${NC}" + echo -e "${CYAN}============================================================${NC}" + echo "" +else + err "服务启动失败,请查看日志:" + err " tail -20 logs/server.log" + rm -f .server.pid + exit 1 +fi diff --git a/run_eval.sh b/run_eval.sh new file mode 100644 index 0000000..be1293b --- /dev/null +++ b/run_eval.sh @@ -0,0 +1,147 @@ +#!/usr/bin/env bash +# run_eval.sh — Siemens RAGAS 评估运行脚本(Linux) +# 对应 Windows 的 run_eval.ps1 +# +# 用法: +# bash run_eval.sh # online 评估(默认) +# bash run_eval.sh offline # offline 冒烟测试 +# bash run_eval.sh scenarios/xxx.yaml # 自定义场景 +# bash run_eval.sh online DEBUG # 指定日志级别 +# bash run_eval.sh build scenarios/siemens_build/siemens-pdf-build.yaml +# # 题库生成 + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "$SCRIPT_DIR" + +# ── 颜色输出 ────────────────────────────────────────────────────── +if [ -t 1 ]; then + GREEN='\033[0;32m'; YELLOW='\033[1;33m'; RED='\033[0;31m'; CYAN='\033[0;36m'; NC='\033[0m' +else + GREEN=''; YELLOW=''; RED=''; CYAN=''; NC='' +fi + +ok() { echo -e "${GREEN}[OK]${NC} $*"; } +warn() { echo -e "${YELLOW}[WARN]${NC} $*"; } +err() { echo -e "${RED}[ERROR]${NC} $*" >&2; } +info() { echo -e "${CYAN}[INFO]${NC} $*"; } + +# ── 参数解析 ────────────────────────────────────────────────────── +SCENARIO="${1:-online}" +LOG_LEVEL="${2:-INFO}" + +# 场景别名映射 +declare -A SCENARIO_MAP=( + ["online"]="scenarios/online/siemens-pdf-question-bank-online.yaml" + ["offline"]="scenarios/offline/siemens-pdf-offline-smoke.yaml" +) + +# 检测是否是 dataset build 模式 +BUILD_MODE=false +BUILD_CONFIG="" +if [ "$SCENARIO" = "build" ]; then + BUILD_MODE=true + BUILD_CONFIG="${2:-scenarios/siemens_build/siemens-pdf-build.yaml}" + LOG_LEVEL="${3:-INFO}" +elif [ -v "SCENARIO_MAP[$SCENARIO]" ]; then + SCENARIO="${SCENARIO_MAP[$SCENARIO]}" +fi + +# ── 验证 ────────────────────────────────────────────────────────── +echo "" +echo -e "${CYAN}============================================================${NC}" +echo -e "${CYAN} Siemens RAGAS — 评估运行${NC}" +echo -e "${CYAN}============================================================${NC}" +echo "" + +# 检查虚拟环境 +if [ ! -f ".venv/bin/python" ]; then + err "未找到 .venv,请先执行部署:bash deploy.sh" + exit 1 +fi + +PYTHON=".venv/bin/python" + +# Build 模式校验 +if [ "$BUILD_MODE" = true ]; then + if [ ! -f "$BUILD_CONFIG" ]; then + err "题库生成配置文件不存在:$BUILD_CONFIG" + echo "" + echo "可用配置:" + find scenarios/ -name "*.yaml" 2>/dev/null | head -20 | sed 's/^/ /' + exit 1 + fi + ok "模式 : 题库生成 (dataset build)" + ok "配置文件 : $BUILD_CONFIG" +else + # 场景文件校验 + if [ ! -f "$SCENARIO" ]; then + err "场景文件不存在:$SCENARIO" + echo "" + echo "用法示例:" + echo " bash run_eval.sh # online 评估" + echo " bash run_eval.sh offline # offline 冒烟" + echo " bash run_eval.sh scenarios/xxx.yaml # 自定义场景" + echo " bash run_eval.sh build [config.yaml] # 题库生成" + exit 1 + fi + ok "场景文件 : $SCENARIO" +fi + +# 日志级别校验 +LOG_LEVEL_UPPER="${LOG_LEVEL^^}" +case "$LOG_LEVEL_UPPER" in + DEBUG|INFO|WARNING|ERROR) ;; + *) + warn "未知日志级别 '$LOG_LEVEL',使用默认值 INFO" + LOG_LEVEL_UPPER="INFO" + ;; +esac +ok "日志级别 : $LOG_LEVEL_UPPER" + +# 创建日志目录 +mkdir -p logs +TIMESTAMP=$(date +%Y-%m-%d_%H%M%S) +LOG_FILE="logs/eval_${TIMESTAMP}.log" +ok "日志文件 : $LOG_FILE" + +echo "" +echo -e "${CYAN}============================================================${NC}" +echo -e "${CYAN} 开始运行,按 Ctrl+C 中止${NC}" +echo -e "${CYAN}============================================================${NC}" +echo "" + +# ── 运行 ────────────────────────────────────────────────────────── +export PYTHONIOENCODING="utf-8" +export PYTHONPATH="." + +if [ "$BUILD_MODE" = true ]; then + "$PYTHON" main.py \ + --dataset-build-config "$BUILD_CONFIG" +else + "$PYTHON" main.py \ + --scenario "$SCENARIO" \ + --log-file "$LOG_FILE" \ + --log-level "$LOG_LEVEL_UPPER" +fi + +EXIT_CODE=$? + +echo "" +if [ $EXIT_CODE -eq 0 ]; then + echo -e "${GREEN}============================================================${NC}" + echo -e "${GREEN} 运行完成!${NC}" + if [ "$BUILD_MODE" = false ]; then + echo -e "${GREEN} 日志已保存到:$LOG_FILE${NC}" + fi + echo -e "${CYAN} 在 Web 控制台查看报告:bash start.sh${NC}" + echo -e "${GREEN}============================================================${NC}" +else + err "运行失败(exit code=$EXIT_CODE)" + if [ "$BUILD_MODE" = false ]; then + err "查看日志:cat $LOG_FILE" + fi + exit $EXIT_CODE +fi +echo "" diff --git a/start.sh b/start.sh new file mode 100644 index 0000000..c01d821 --- /dev/null +++ b/start.sh @@ -0,0 +1,94 @@ +#!/usr/bin/env bash +# start.sh — 启动 Siemens RAGAS Web 服务(后台运行) +# 前提:已执行过 deploy.sh(.venv 和依赖均已就绪) +# 用法:bash start.sh + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "$SCRIPT_DIR" + +# ── 颜色输出 ────────────────────────────────────────────────────── +if [ -t 1 ]; then + GREEN='\033[0;32m'; YELLOW='\033[1;33m'; RED='\033[0;31m'; CYAN='\033[0;36m'; NC='\033[0m' +else + GREEN=''; YELLOW=''; RED=''; CYAN=''; NC='' +fi + +ok() { echo -e "${GREEN}[OK]${NC} $*"; } +warn() { echo -e "${YELLOW}[WARN]${NC} $*"; } +err() { echo -e "${RED}[ERROR]${NC} $*" >&2; } + +echo "" +echo -e "${CYAN}============================================================${NC}" +echo -e "${CYAN} Siemens RAGAS Console — 启动服务${NC}" +echo -e "${CYAN}============================================================${NC}" +echo "" + +# 检查虚拟环境 +if [ ! -f ".venv/bin/python" ]; then + err "未找到 .venv,请先执行部署:bash deploy.sh" + exit 1 +fi + +PYTHON=".venv/bin/python" + +# 检查 .env +if [ ! -f ".env" ]; then + warn ".env 不存在,请先复制并编辑配置:" + warn " cp .env.example .env && nano .env" +fi + +if grep -q "your-api-key" .env 2>/dev/null; then + warn ".env 中仍包含默认占位符,部分功能(评估执行)将不可用" +fi + +# 检查是否已有运行中的进程 +if [ -f ".server.pid" ]; then + EXISTING_PID=$(cat .server.pid) + if kill -0 "$EXISTING_PID" 2>/dev/null; then + warn "服务已在运行 (PID=$EXISTING_PID),无需重复启动" + warn "如需重启请先执行:bash stop.sh" + exit 0 + else + # PID 文件残留,清理 + rm -f .server.pid + fi +fi + +# 创建必要目录 +mkdir -p logs + +# 端口检测 +PORT=8800 +if ss -tlnp 2>/dev/null | grep -q ":$PORT " || netstat -tlnp 2>/dev/null | grep -q ":$PORT "; then + warn "端口 $PORT 已被占用,尝试 8801..." + PORT=8801 + if ss -tlnp 2>/dev/null | grep -q ":$PORT " || netstat -tlnp 2>/dev/null | grep -q ":$PORT "; then + err "端口 8800 和 8801 均被占用,请手动指定端口:" + err " .venv/bin/python webmain.py --host 0.0.0.0 --port " + exit 1 + fi +fi + +# 后台启动 +nohup "$PYTHON" webmain.py --host 0.0.0.0 --port "$PORT" >> logs/server.log 2>&1 & +SERVER_PID=$! +echo "$SERVER_PID" > .server.pid + +# 等待 3 秒验证进程存活 +sleep 3 +if kill -0 "$SERVER_PID" 2>/dev/null; then + ok "服务已启动 (PID=$SERVER_PID)" + echo "" + echo -e "${CYAN} 访问地址: http://$(hostname -I | awk '{print $1}'):${PORT}${NC}" + echo -e "${CYAN} 本机访问: http://127.0.0.1:${PORT}${NC}" + echo -e "${CYAN} 查看日志: tail -f logs/server.log${NC}" + echo -e "${CYAN} 停止服务: bash stop.sh${NC}" + echo "" +else + err "服务启动失败,请查看日志:" + err " tail -20 logs/server.log" + rm -f .server.pid + exit 1 +fi diff --git a/stop.sh b/stop.sh new file mode 100644 index 0000000..431b828 --- /dev/null +++ b/stop.sh @@ -0,0 +1,68 @@ +#!/usr/bin/env bash +# stop.sh — 停止 Siemens RAGAS 后台 Web 服务 +# 用法:bash stop.sh + +set -uo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "$SCRIPT_DIR" + +# ── 颜色输出 ────────────────────────────────────────────────────── +if [ -t 1 ]; then + GREEN='\033[0;32m'; YELLOW='\033[1;33m'; RED='\033[0;31m'; CYAN='\033[0;36m'; NC='\033[0m' +else + GREEN=''; YELLOW=''; RED=''; CYAN=''; NC='' +fi + +ok() { echo -e "${GREEN}[OK]${NC} $*"; } +warn() { echo -e "${YELLOW}[WARN]${NC} $*"; } +err() { echo -e "${RED}[ERROR]${NC} $*" >&2; } + +echo "" +echo -e "${CYAN} Siemens RAGAS Console — 停止服务${NC}" +echo "" + +PID_FILE="$SCRIPT_DIR/.server.pid" + +if [ ! -f "$PID_FILE" ]; then + warn "未找到 .server.pid,服务可能未启动或已停止" + exit 0 +fi + +PID=$(cat "$PID_FILE") + +if ! kill -0 "$PID" 2>/dev/null; then + warn "进程 $PID 已不存在,清理 PID 文件" + rm -f "$PID_FILE" + exit 0 +fi + +# 优雅停止(SIGTERM) +echo -e " 正在停止进程 (PID=$PID)..." +kill "$PID" 2>/dev/null || true + +# 等待最多 5 秒 +for i in 1 2 3 4 5; do + sleep 1 + if ! kill -0 "$PID" 2>/dev/null; then + break + fi + echo -e " 等待进程退出... ($i/5)" +done + +# 若进程仍存在,强制终止 +if kill -0 "$PID" 2>/dev/null; then + warn "进程未响应,强制终止 (SIGKILL)..." + kill -9 "$PID" 2>/dev/null || true + sleep 1 +fi + +rm -f "$PID_FILE" + +if kill -0 "$PID" 2>/dev/null; then + err "无法停止进程 $PID,请手动执行:kill -9 $PID" + exit 1 +else + ok "服务已停止" + echo "" +fi