部署流程(打包→安装→配置→启动→验证)
### 1. 打包 wheel(Windows 开发机) 把后端源码工程(含前端 static 资源)打包成一个 `.whl` 文件。InspectAI 后端位于 `n2sql_sample/backend`,用 `uv build --wheel` 生成平台无关的 wheel。 ```bash cd D:/zhinengjiancha.23/n2sql_sample/backend # 清理旧产物 rm -rf dist build src/*.egg-info *.egg-info # 打包 uv build --wheel # 输出: dist/inspectai-1.0.0-py3-none-any.whl(约 19M) ``` **打包后必须验证两点**(否则部署即崩): ```bash # ① 含前端 static(没打进会 404) python -m zipfile -l dist/inspectai-1.0.0-py3-none-any.whl | grep "static/index.html" # ② 依赖声明完整(漏声明会启动崩) python -m zipfile -e dist/inspectai-1.0.0-py3-none-any.whl /tmp/whl_chk grep "Requires-Dist" /tmp/whl_chk/inspectai-1.0.0.dist-info/METADATA ``` **关键坑**:`pyproject.toml` 的 `<project>.dependencies` 必须覆盖代码里所有 `import` 的第三方包。实测踩过:`psycopg2-binary` 被 `pg_exec.py` import 但未声明,部署后启动即报 `ModuleNotFoundError`。漏了就补回 `pyproject.toml` 再重打包。 **依赖下载是可选的**:有网环境(开发机/测试服务器都能连 pypi)直接在服务器 `pip install <wheel>` 在线装依赖,**无需** `pip download` 离线 wheelhouse——那是无网生产环境才需要的。`inspectai-*.whl` 是部署产物本体,不要当第三方包去 download。
### 2. 服务器安装(venv + wheel) 服务器(如 `xt@144.34.185.72`,Ubuntu,Python 3.12)上用目标解释器建 venv,安装 wheel 及其依赖。 ```bash # 传 wheel 到服务器 scp -P 26225 dist/inspectai-1.0.0-py3-none-any.whl xt@144.34.185.72:/opt/inspectai/ # 服务器安装 ssh -p 26225 xt@144.34.185.72 'bash -s' <<'EOF' set -e cd /opt/inspectai mkdir -p logs uploads PY=/wks/python/micromamba/envs/py12/bin/python # 建 venv(幂等:已存在跳过) [ -x venv/bin/python ] || $PY -m venv venv venv/bin/python --version # 升级 pip(清华镜像加速) venv/bin/python -m pip install --upgrade pip -q -i https://pypi.tuna.tsinghua.edu.cn/simple # 装 wheel(含依赖,在线) venv/bin/pip install inspectai-1.0.0-py3-none-any.whl -i https://pypi.tuna.tsinghua.edu.cn/simple EOF ``` **安装后启动前必跑 import 自检**(比直接起 uvicorn 更快暴露漏声明依赖): ```bash venv/bin/python -c "import inspectai_api.main" ``` - 通过 → 可以进入下一步启动 - 报 `No module named 'xxx'` → 紧急止血 `pip install xxx` + 根因修复(补回 `pyproject.toml` 重打包) 自检通过的标志:控制台打印 `[OK] 全局异常处理器已注册` 和 `✅ StreamManager 已初始化`,无 Traceback。
### 3. 配置 .env + 改端口 + 启动 + 验证 **配置 .env(★铁律:放启动目录 `/opt/inspectai/.env`,不是 config/)** 后端用 pydantic-settings 加载 `.env`,加载优先级:环境变量 `INSPECTAI_CONFIG` → **当前工作目录 `.env`**(推荐)→ `config/.env`。启动目录必须与 uvicorn 的 CWD 一致。日志会打印实际来源确认: ``` [InspectAI Config] 已加载 .env 文件: /opt/inspectai/.env (来源: 当前工作目录 .env) ``` ```bash ssh -p 26225 xt@144.34.185.72 'bash -s' <<'EOF' cd /opt/inspectai cat > .env <<'ENVEOF' APP_NAME=InspectAI DEBUG=True API_HOST=0.0.0.0 API_PORT=9081 MYSQL_HOST=144.34.185.72 MYSQL_PORT=13301 MYSQL_DATABASE=n2sqlv3 MYSQL_USER=automng MYSQL_PASSWORD=Automng_123 DORIS_HOST=101.43.140.244 DORIS_PORT=9030 DORIS_DATABASE=aisql WORKER_ENABLED=false ENVEOF chmod 600 .env EOF ``` **启动 uvicorn**(loguru 已统一写 `logs/app.log`,stdout 丢弃避免重复): ```bash cd /opt/inspectai pkill -f "uvicorn inspectai_api.main:app --host 0.0.0.0 --port 9081" 2>/dev/null || true sleep 1 nohup venv/bin/uvicorn inspectai_api.main:app --host 0.0.0.0 --port 9081 > /dev/null 2>&1 & ``` **改端口**:改 `.env` 的 `API_PORT` + uvicorn `--port`,两者必须一致。前端 `config.js` 用 `window.location.origin` 同源,**无需改**。多实例部署用不同端口(9081/9082)+ 不同部署目录(`/opt/inspectai`、`/opt/inspectai2`)。 **验证(双检)**: ```bash # /health(200 = 进程起来了,但不测 DB) curl -s http://localhost:9081/health # {"status":"ok","version":"1.0.0"} # 前端可访问(200 = static 打进 wheel 了) curl -s -o /dev/null -w "%{http_code}\n" http://localhost:9081/index.html curl -s -o /dev/null -w "%{http_code}\n" http://localhost:9081/config.js # 端口监听 + 外网可达 ss -tlnp | grep 9081 curl -s http://144.34.185.72:9081/health ``` 浏览器访问 `http://<服务器IP>:9081/` 应见登录页。
### 4. 用 aidev_install agent 一键部署 以上三步(打包→安装→配置→启动→验证)封装在 `aidev_install` 智能体的 `deploy.sh` 脚本里,一条命令完成全流程: ```bash bash .config/agent/aidev_install/scripts/deploy.sh \ --ssh "ssh -p 26225 xt@144.34.185.72" \ --python /wks/python/micromamba/envs/py12/bin/python \ --port 9081 \ --mysql-host 144.34.185.72 --mysql-port 13301 \ --mysql-db n2sqlv3 --mysql-user automng --mysql-pass 'Automng_123' \ --doris-host 101.43.140.244 --doris-port 9030 --doris-db aisql ``` 脚本会自动执行 5 个 Phase: | Phase | 动作 | 验证 | |-------|------|------| | 1 打包 | `uv build --wheel` | dist/ 有 whl | | 2 传输 | scp + 自动 mkdir 部署目录 | 服务器收到 whl | | 3 安装 | 建 venv + pip install wheel + **import 自检** | inspectai 版本 + import 通过 | | 4 配置 | 本机生成 .env(含端口)+ scp 上传 | `grep API_PORT` 确认 | | 5 启动 | nohup uvicorn + 轮询 /health 30s | health 200 + 前端 200 + 端口监听 | **升级模式**(仅换 wheel,复用 venv/.env): ```bash bash .config/agent/aidev_install/scripts/deploy.sh --upgrade \ --ssh "ssh -p 26225 xt@144.34.185.72" --port 9081 ``` **多实例部署**(第二套,不同端口/目录): ```bash bash .config/agent/aidev_install/scripts/deploy.sh \ --ssh "ssh -p 26225 xt@144.34.185.72" \ --deploy-dir /opt/inspectai2 --port 9082 \ --mysql-host 144.34.185.72 --mysql-port 13301 \ --mysql-db n2sqlv3 --mysql-user automng --mysql-pass 'Automng_123' ``` **触发 agent 的方式**:对 ZCode 说"打包部署后端到 9081 端口"、"升级 inspectai"、"换个端口起一套",aidev skill 会路由到 `aidev_install`。agent 内置 10 条铁律(见 `AIDEV.md` 第四节),其中最关键:.env 放启动目录、import 自检、依赖漏声明根因修复。
无源码运维(部署/重启/升级原理)
### 为什么服务器不需要源码 **核心原理**:wheel 把源码打包进去了,`pip install` 后源码被解压到 venv 的 `site-packages/`,启动命令通过 Python 包导入机制找到代码,**不依赖源码工程在场**。 **wheel 是什么** `inspectai-1.0.0-py3-none-any.whl` 本质是个 zip,里面装着三个 Python 包(`inspectai_api/`、`inspectai_cal/`、`inspectai_shared/`)+ 前端 `static/` + 元数据。`pip install` 后,源码落到: ``` /opt/inspectai/venv/lib/python3.12/site-packages/ ├── inspectai_api/ ← 后端源码(含 main.py、modules/、static/) ├── inspectai_cal/ ← Worker 源码 ├── inspectai_shared/ ← 共享源码 └── inspectai-1.0.0.dist-info/ ← 元数据(版本、依赖、entry_points) ``` **启动命令如何找到代码** `uvicorn inspectai_api.main:app` —— uvicorn 按 Python 包导入机制找 `inspectai_api`,而 `site-packages` 是 venv 的默认搜索路径。所以: ``` 开发机(有源码) 服务器(无源码) ───────────── ────────────── backend/src/ /opt/inspectai/ inspectai_api/ venv/site-packages/inspectai_api/ inspectai_cal/ uv build venv/site-packages/inspectai_cal/ inspectai_shared/ ─────────> venv/site-packages/inspectai_shared/ static/(前端) 打成 wheel venv/site-packages/inspectai_api/static/ │ uvicorn 启动时从这里 import ``` **对比"源码部署"的劣势**:如果服务器放源码 + `PYTHONPATH=src`,升级要 rsync 整个 src、依赖还得单独管、版本难追溯。wheel 部署把"代码+依赖+版本"打成一个可追溯的制品,`pip show inspectai` 就能看到版本,`pip install --upgrade` 一条命令升级,干净利落。 **服务器上只需三样东西**:①venv(含已装的包)②`.env`(配置)③wheel 文件(留档,用于回滚)。源码工程完全不需要。
### 升级(换 wheel + 重启) 升级 = 用新 wheel 覆盖 `site-packages/` 里的包 + 重启进程加载新代码。分两种: **便捷升级(日常迭代,仅业务代码变更,最快)** 适用:只改了业务代码,没动 `pyproject.toml` 依赖。传一个 wheel 即可。 ```bash # 开发机:改版本号 + 打包 cd D:/zhinengjiancha.23/n2sql_sample/backend sed -i 's/version = "1.0.0"/version = "1.0.1"/' pyproject.toml uv build --wheel # 传到服务器 scp -P 26225 dist/inspectai-1.0.1-py3-none-any.whl xt@144.34.185.72:/opt/inspectai/ # 服务器:--upgrade 覆盖旧版,--no-deps 不动依赖 ssh -p 26225 xt@144.34.185.72 'bash -s' <<'EOF' cd /opt/inspectai source venv/bin/activate pip install inspectai-1.0.1-py3-none-any.whl --upgrade --no-deps # 重启 pkill -f "uvicorn inspectai_api.main:app"; sleep 1 nohup venv/bin/uvicorn inspectai_api.main:app --host 0.0.0.0 --port 9081 >/dev/null 2>&1 & # 验证版本 pip show inspectai | grep Version curl -s http://localhost:9081/health EOF ``` 或用 agent 一键:`deploy.sh --upgrade --ssh "..." --port 9081` **完整升级(有新依赖时)** 适用:`pyproject.toml` 的 `dependencies` 变了(加包/升版本)。去掉 `--no-deps`: ```bash pip install inspectai-1.0.1-py3-none-any.whl --upgrade -i https://pypi.tuna.tsinghua.edu.cn/simple ``` **升级决策表**: | 场景 | 方式 | 命令 | |------|------|------| | 只改业务代码 | 便捷升级 | `--upgrade --no-deps` | | 加/升了依赖 | 完整升级 | `--upgrade`(带 deps) | | 不确定 | 完整升级 | 宁可重装依赖 | **关键**:升级前先备份当前 wheel(`cp inspectai-1.0.0-*.whl inspectai-backup-$(date +%Y%m%d).whl`),出问题能回滚。
### 重启(不换代码,如改了 .env / 进程僵死) 改了 `.env` 必须**重启**才生效(pydantic-settings 只在进程启动时读一次 `.env`)。 **systemd 方式(生产常驻,推荐)** 先配 `/etc/systemd/system/inspectai-api.service`,之后: ```bash sudo systemctl restart inspectai-api sudo systemctl status inspectai-api journalctl -u inspectai-api -f # 看实时日志 ``` **nohup 方式(手动)** ```bash cd /opt/inspectai pkill -f "uvicorn inspectai_api.main:app --host 0.0.0.0 --port 9081" sleep 1 nohup venv/bin/uvicorn inspectai_api.main:app --host 0.0.0.0 --port 9081 > /dev/null 2>&1 & echo "PID=$!" ``` **优雅停止 vs 强杀**:`pkill` 默认发 SIGTERM,uvicorn 会处理完当前请求再退出;`kill -9` 是强杀(SIGKILL),可能丢正在处理的请求,仅在僵死时用。 **验证重启成功**: ```bash # 进程在 pgrep -af "uvicorn inspectai_api" # 端口监听 ss -tlnp | grep 9081 # health 200 curl -s http://localhost:9081/health # 日志确认 .env 重新加载 grep "已加载 .env" logs/app.log | tail -1 ```
### 回滚(升级失败时退回旧版) 前提:升级前备份了旧 wheel。回滚 = 用旧 wheel 覆盖 + 重启。 ```bash ssh -p 26225 xt@144.34.185.72 'bash -s' <<'EOF' cd /opt/inspectai source venv/bin/activate # 用备份的旧 wheel 强制重装(--force-reinstall --no-deps) pip install inspectai-backup-20260804.whl --force-reinstall --no-deps # 重启 pkill -f "uvicorn inspectai_api.main:app"; sleep 1 nohup venv/bin/uvicorn inspectai_api.main:app --host 0.0.0.0 --port 9081 >/dev/null 2>&1 & # 确认版本退回 pip show inspectai | grep Version EOF ``` **回滚注意事项**: - `--force-reinstall` 强制覆盖(同版本号也覆盖),`--no-deps` 不动依赖(回滚只退业务代码,依赖保持当前); - 如果旧版本依赖更少/不同,回滚后可能残留多余依赖包,一般无害; - DB schema 变更(alembic upgrade)回滚要配合 `alembic downgrade`,光退代码不够——这类变更走 `aidev_alembic`; - 回滚后务必验证 health + 关键业务接口。 **查看当前运行版本**: ```bash ssh -p 26225 xt@144.34.185.72 '/opt/inspectai/venv/bin/pip show inspectai | grep Version' curl -s http://144.34.185.72:9081/health # health 响应里也带 version 字段 ```
升级实战(aidev_install 升级全过程)
### 升级全过程回顾(aidev_install agent 实战) 以一次真实修复为例:后端 `conversation.py` 加了 PostgreSQL/DWS 分支(修智能问数默认库为 PG 时取不到表结构的 bug),需要升级到远程服务器。用 `aidev_install` agent 的 `deploy.sh` 完成。 **第一次升级(踩坑)**:版本号没改(还是 1.0.0),`deploy.sh --upgrade` 内部用 `--upgrade --no-deps`,pip 认为已装同版本直接跳过——**代码没被覆盖**,服务起来跑的是旧代码,但 import 自检仍通过(旧代码也能 import),极具迷惑性。 ```text [Phase 3] pip install inspectai-1.0.0-py3-none-any.whl --upgrade --no-deps → inspectai is already installed with the same version as the provided wheel. → Use --force-reinstall to force an installation. ← 代码没换! ``` **根因**:pip 的 `--upgrade` 对**同版本号**包不生效(它认为没变化)。`--upgrade` 只在版本号递增时才覆盖。 **修复 deploy.sh**:升级模式改用 `--force-reinstall --no-deps`——强制覆盖,不管版本号是否变化。 ```bash # 修复前(upgrade 模式) install_mode="--upgrade --no-deps" # 同版本号不覆盖 ❌ # 修复后(upgrade / code-only 模式) install_mode="--force-reinstall --no-deps" # 强制覆盖 ✅ ``` **第二次升级(成功)**:force-reinstall 生效,代码被真正替换,重启后直连验证两个函数对 PG 默认库返回正确。 ```text [Phase 3] pip install inspectai-1.0.0-py3-none-any.whl --force-reinstall --no-deps → Successfully installed inspectai-1.0.0 ← 强制覆盖 [Phase 5] ✅ 第7秒就绪 (200) [_build_all_tables_schema] 构建表数: 2 | 表: ['test_accounts','test_transactions'] ``` **关键认知**:升级后不能只看 `/health` 200(旧代码也能 200),必须验证**具体修复点**生效(这次是直连调修复的函数,确认对 PG 返回了表)。
### 仅代码升级(开发最常用) > 开发迭代中最高频操作:只改了业务代码,**不动 `.env` 配置、不碰数据库、不改依赖**。用 `deploy.sh --code-only` 一键完成。 **一条命令**: ```bash bash .config/agent/aidev_install/scripts/deploy.sh --code-only \ --ssh "ssh -p 26225 xt@144.34.185.72" \ --deploy-dir /opt/inspectai2 --port 9082 ``` **秒级迭代(复用已打好的 wheel,跳过打包)**: ```bash bash .config/agent/aidev_install/scripts/deploy.sh --code-only \ --local-wheel dist/inspectai-1.0.0-py3-none-any.whl \ --ssh "ssh -p 26225 xt@144.34.185.72" \ --deploy-dir /opt/inspectai2 --port 9082 ``` **`--code-only` 内部 4 步**(跳过配 .env): | 步骤 | 动作 | 不碰什么 | |------|------|---------| | 1 打包 | `uv build --wheel`(`--local-wheel` 时跳过) | — | | 2 传输 | scp wheel 到服务器 | — | | 3 安装 | `pip install --force-reinstall --no-deps` | **不碰依赖** | | 4 重启 | kill 旧进程 + nohup uvicorn + 轮询 health | **不碰 .env / 数据库** | **为什么用 `--force-reinstall --no-deps`**: | 参数 | 作用 | 不这么写的后果 | |------|------|--------------| | `--force-reinstall` | 强制覆盖同版本包 | 同版本号时 pip 跳过,跑的还是旧代码 | | `--no-deps` | 不重装依赖 | 避免动依赖版本导致环境漂移 | **实测验证(配置/数据确实没动)**: ```text $ stat -c "%y %n" /opt/inspectai2/.env 2026-08-04 11:36:25 ← 首次部署时间,code-only 没碰它 $ curl .../config/database/default {"alias":"pg1","db_type":"Dws","is_default_db":true} ← db_config 数据未变 ``` **手动等价操作**(理解原理): ```bash cd /opt/inspectai2 source venv/bin/activate pip install inspectai-1.0.0-py3-none-any.whl --force-reinstall --no-deps pkill -f "uvicorn inspectai_api.main:app --host 0.0.0.0 --port 9082"; sleep 1 nohup venv/bin/uvicorn inspectai_api.main:app --host 0.0.0.0 --port 9082 > /dev/null 2>&1 & ``` **何时不能用 `--code-only`**:改了 `.env`(需手动改+重启)、加了第三方依赖(用 `--upgrade`)、改了数据库 schema(配合 `aidev_alembic`)。
### 升级决策表(deploy.sh 四种模式) | 模式 | 命令 | 打包 | 装 | 配.env | 适用场景 | |------|------|------|-----|--------|---------| | `--build-only` | `deploy.sh --build-only` | ✅ | ❌ | ❌ | 只打 wheel 不部署 | | `deploy`(默认) | `deploy.sh --ssh ... --mysql-* ...` | ✅ | ✅含依赖 | ✅ | 首次部署/换机 | | `--upgrade` | `deploy.sh --upgrade --ssh ... --port N` | ✅ | force-reinstall --no-deps | ❌ | 换代码(默认推荐) | | **`--code-only`** | `deploy.sh --code-only --ssh ... --port N` | ✅(可跳过) | **force-reinstall --no-deps** | **❌** | **开发迭代:只换代码** | **如何选模式**(决策树): ```text 改了什么? ├─ 只改业务代码(router/service/crud/前端static) │ └─ 改了 pyproject.toml 依赖? │ ├─ 否 → --code-only(最快,不碰依赖/.env/DB)★开发首选 │ └─ 是 → --upgrade(带依赖安装) ├─ 改了 .env(端口/DB连接/License) │ └─ 手动改 .env + 仅重启(不换代码) ├─ 改了数据库 schema │ └─ 配合 aidev_alembic 做迁移 + --code-only(若代码也改了) └─ 换了台机器/首次部署 └─ deploy(全新部署) ``` **版本号要不要改**:用 `--force-reinstall` 后,版本号改不改都能覆盖。但建议修复迭代时递增 version(如 1.0.0→1.0.1),便于 `pip show inspectai` 追踪当前跑的是哪版。`--code-only` 连续调试时可不改版本号(反正强制覆盖)。
### 踩坑教训(本次升级固化进 agent) **坑1:`--upgrade` 对同版本号包不覆盖** pip 的 `--upgrade` 只在版本号递增时才覆盖。开发中修复 bug 往往不改版本号(还是 1.0.0),`--upgrade` 会静默跳过——服务起来了但跑旧代码,且 import 自检通过(旧代码也能 import),极具迷惑性。 修复:`deploy.sh` 升级/code-only 模式统一用 `--force-reinstall --no-deps`,不管版本号。 **坑2:升级后只看 health 200 不够** `/health` 返回静态 `{"status":"ok"}`,旧代码新代码都 200。升级后必须验证**具体修复点**: ```bash # 错误:只看 health curl http://localhost:9082/health # 200 但可能是旧代码 # 正确:验证修复的具体功能(这次是直连调修复的函数) ssh ... 'cd /opt/inspectai2 && venv/bin/python -c " from inspectai_shared.database import SessionLocal from inspectai_api.modules.chat2sql.dbconfig.crud import get_default_db_config from inspectai_api.modules.chat2sql.v1.api.routes.conversation import _build_all_tables_schema with SessionLocal() as s: dbcfg = get_default_db_config(session=s) schema = _build_all_tables_schema(dbcfg, str(dbcfg.db_type), session=s) print(\"表数:\", len([k for k in schema if k not in (\"all_table_name_cn\",\"all_columns\")])) "' ``` **坑3:依赖漏声明导致部署即崩(本次关联坑)** 代码 `import psycopg2` 但 `pyproject.toml` 没声明 → 部署后启动报 `ModuleNotFoundError`。`deploy.sh` 的 import 自检(Phase 3.4)能快速暴露: ```text === 3.4 import 自检 === FAIL_IMPORT=1 缺失模块: No module named 'psycopg2' HINT: 补回 backend/pyproject.toml [project].dependencies 后重打包 ``` 修复不能只在服务器 `pip install psycopg2-binary` 了事(治标),必须补回 `pyproject.toml` 重打包(治本),否则下次部署/换机还会崩。 **坑4:配置和数据库的边界** `--code-only` 严格不动 `.env` 和数据库。如果修复涉及配置变更(如新增环境变量),必须: 1. 先手动改服务器 `.env` 2. 再 `--code-only` 升级代码(重启时会加载新 .env) `--code-only` 的"仅代码"语义是设计而非缺陷——它把"代码变更"和"配置变更"解耦,避免一次代码升级意外覆盖用户手动调过的配置。 **坑5:force-reinstall 报 `Can't uninstall ... outside environment`(editable 安装冲突)** 现象:wheel 重装时报错,提示 inspectai 装在 venv 外不敢卸载: ```text $ pip install inspectai-1.0.0-py3-none-any.whl --force-reinstall --no-deps Attempting uninstall: inspectai Found existing installation: inspectai 1.0.0 Not uninstalling inspectai at /home/xt/wks/.../src, outside environment /opt/inspectai/venv Can't uninstall 'inspectai'. No files were found to uninstall. ``` **根因**:该 venv 历史上用 `pip install -e .`(editable 模式)装过 inspectai,源码目录 `/home/xt/wks/zhinengjiancha/n2sql_sample/backend/src/` 残留了 `inspectai.egg-info`。改用 wheel 安装时,pip 检测到这个 venv 外的 editable 痕迹,出于安全(不敢删 venv 外文件)拒绝卸载。 **迷惑性**:报错后 pip **可能仍完成了安装**(dist-info 更新了,site-packages 是新代码)——所以不能只看报错判定失败,要 `pip show` + 验证具体代码是否生效。 **彻底修复**(三步): ```bash # 1. 显式卸载(绕过 outside environment 限制) venv/bin/pip uninstall inspectai -y # 2. 删源码目录的 editable 元数据残留 rm -rf /home/xt/wks/zhinengjiancha/n2sql_sample/backend/src/inspectai.egg-info rm -rf /home/xt/wks/zhinengjiancha/n2sql_sample/backend/src/*.egg-info # 3. 干净重装(无 editable 痕迹,不再报错) venv/bin/pip install inspectai-1.0.0-py3-none-any.whl --no-deps ``` **预防**:开发机若既跑源码(`pip install -e .`)又部署 wheel 到同机,两者用**不同的 venv**,避免 editable 痕迹污染部署 venv。`deploy.sh` 的 Phase 3.2b 已自动执行"卸载 + 清 egg-info",升级时无需手动处理。
操作记录
### 打包 uv build --wheel ### 显式卸载(绕过 outside environment 限制) venv/bin/pip uninstall inspectai -y ### 删源码目录的 editable 元数据残留 rm -rf /home/xt/wks/zhinengjiancha/n2sql_sample/backend/src/inspectai.egg-info rm -rf /home/xt/wks/zhinengjiancha/n2sql_sample/backend/src/*.egg-info ### 干净重装(无 editable 痕迹,不再报错) venv/bin/pip install inspectai-1.0.0-py3-none-any.whl --no-deps venv/bin/uvicorn inspectai_api.main:app --reload --host 0.0.0.0 --port 9081 nohup venv/bin/uvicorn inspectai_api.main:app --reload --host 0.0.0.0 --port 9081 > /tmp/n2sql.log 2>&1 & ### force-reinstall可能会残留信息 下面的方式存在残留的问题 venv/bin/pip install inspectai-1.0.0-py3-none-any.whl --force-reinstall --no-deps $ cat reist.sh /opt/inspectai/venv/bin/pip install /opt/inspectai/inspectai-1.0.0-py3-none-any.whl --force-reinstall --no-deps
uv build --wheel scp -P 26225 inspectai-1.0.0-py3-none-any.whl xt@144.34.185.72:/tmp/ $ which python /wks/python/micromamba/envs/py12/bin/python xt@fine-bump-3:/tmp$ which pip /wks/python/micromamba/envs/py12/bin/pip $ pip list |grep ins inspectai 1.0.0 typing-inspection 0.4.2 xt@fine-bump-3:/tmp$ pip show inspectai|grep Loca Location: /home/xt/wks/zhinengjiancha/n2sql_sample/backend/src pip uninstall inspectai -y rm -rf /home/xt/wks/zhinengjiancha/n2sql_sample/backend/src/inspectai.egg-info rm -rf /home/xt/wks/zhinengjiancha/n2sql_sample/backend/src/*.egg-info pip install inspectai-1.0.0-py3-none-any.whl --no-deps $ pip show inspectai|grep Loca Location: /wks/python/micromamba/envs/py12/lib/python3.12/site-packages ps -ef |grep uv cd /opt/inspectai uvicorn inspectai_api.main:app --reload --host 0.0.0.0 --port 9081 一定要在存在.env的目录下执行,否则会报错
### 现象(2026-08-06) 数据库管理 → pg1 → **查看结构**:本地显示全部表,线上只显示 1 张(test_accounts) ### 排查过程 1)确认服务器装的是新 wheel ```text /tmp/inspectai-1.0.0-py3-none-any.whl 7322296 字节(08-06 09:15 scp 上传) site-packages 安装时间 09:27,uvicorn 09:31 重启(新代码已生效) /opt/inspectai 下残留的同名 whl 是 08-04 的旧包(6723205 字节),勿混用 ``` 2)确认已安装包内是新前端 ```bash SP=/wks/python/micromamba/envs/py12/lib/python3.12/site-packages grep -o "assets/index-[^\"]*" $SP/inspectai_api/static/index.html # → index-Az_rlMFK.js(新) grep -c "get_all_table" $SP/inspectai_api/static/assets/index-*.js # 新 js = 1(含 get_all_table=1),旧 index-GAGdQuSM.js = 0 ``` 3)看后端日志 /opt/inspectai/logs/app.log —— 浏览器实际请求**没带** get_all_table=1 ```text 使用数据库配置: pg1 (id=3, type=Dws), get_all_table=0 GET /api/v1/config/database/schema?db_id=3&page=1&size=10 200 ``` 4)在服务器上 curl 直接对比 ```bash # 不带参数(旧前端行为)→ 只返回 1 张 test_accounts curl "http://127.0.0.1:9081/api/v1/config/database/schema?db_id=3&page=1&size=100" # 带 get_all_table=1(新前端行为)→ 返回全部表 curl "http://127.0.0.1:9081/api/v1/config/database/schema?db_id=3&page=1&size=100&get_all_table=1" ``` ### 根因 - 浏览器**缓存了旧 index.html**,仍加载旧 js(不含 get_all_table=1) - 后端 /schema 默认 get_all_table=0 → 用元数据库 t_table_meta(is_active) 做交集过滤 - 远程 t_table_meta 只注册了 1 张表 → 只显示 1 张 ### 解决 - 浏览器 **Ctrl+F5 强制刷新**(或清缓存),新前端请求即带 get_all_table=1 - 建议:index.html 响应加 no-cache 头,避免每次发版都被旧缓存坑 ### 经验 - 前端"功能失效"先看后端日志里的**实际请求参数**,再怀疑代码 - 发版后验证三件套:wheel 时间/大小、static/index.html 指向的 js hash、浏览器实际加载的 js hash
nl2sql 项目部署实战(nl2sqlv3 打包→部署→验证)
### nl2sql 项目是什么 nl2sql 是一个**自然语言转 SQL** 的智能问数系统:用户用中文提问(如"查一下高风险地区的客户交易"),系统自动生成 SQL 并执行,返回查询结果。核心链路:**自然语言 → LLM 生成 SQL → 安全校验 → 执行 → 返回结果**。 **技术栈**: | 层 | 技术 | 说明 | |---|------|------| | 后端 | FastAPI + SQLModel | Python 3.12,包名 `app`,入口 `app.main:app` | | 前端 | Vue 3 + Element Plus | 构建产物放 `app/static/` 随 wheel 发布 | | 数据库 | MySQL / Doris / PostgreSQL / DWS | 默认 SQLite 元库,业务库可切换 | | LLM | OpenAI 兼容 API | SSE 流式对话,逐 token 推送 | **项目族**:nl2sqlv1 → nl2sqlv2 → nl2sqlv3 → nl2sqlv4 → nl2sqlv5(迭代版本)。每个版本独立目录、独立端口、独立 venv。nl2sqlv3 端口 9381(开发)/ 9083(部署)。 **与 InspectAI 的关系**:nl2sql 是 InspectAI 的**简化重写版**——只保留智能问数核心功能(对话 + SQL 执行 + 数据库管理),去掉了 Worker 异步任务、字段池、报告生成等复杂模块。包结构从三个包(inspectai_api/inspectai_cal/inspectai_shared)简化为单包(app)。
### 打包与部署(用 aidev_install_nl2sql agent) nl2sql 的打包部署与 InspectAI 共用同一套方法论(wheel + venv + .env),但参数不同。封装在 `aidev_install_nl2sql` agent 的 `deploy.sh` 脚本里。 **一条命令完成部署**: ```bash bash .config/agent/aidev_install_nl2sql/scripts/deploy.sh \ --ssh "ssh -p 26225 xt@144.34.185.72" \ --python /wks/python/micromamba/envs/py12/bin/python \ --backend-dir D:/zhinengjiancha.23/nl2sqlv3/api \ --web-dir D:/zhinengjiancha.23/nl2sqlv3/web \ --deploy-dir /opt/nl2sqlv3 --port 9083 ``` **deploy.sh 自动执行 5 个 Phase**: | Phase | 动作 | 关键产物 | |-------|------|---------| | 1 打包 | 前端 `npm run build` → 拷到 `api/app/static/` → `uv build --wheel` | `dist/nl2sqlv3_api-0.1.0-py3-none-any.whl` | | 2 传输 | `scp` wheel 到服务器 `/opt/nl2sqlv3/` | 服务器收到 wheel | | 3 安装 | 建 venv → `pip install wheel` → **import 自检** | `import app.main` 通过 | | 4 配置 | 写 `/opt/nl2sqlv3/.env`(端口 9083 + SQLite 元库) | .env 就位 | | 5 启动 | `nohup uvicorn app.main:app --port 9083` + 轮询 /health | health 200 + 前端 200 | **与 InspectAI deploy.sh 的关键差异**: | 差异点 | InspectAI | nl2sql | |--------|-----------|--------| | 包名 | `inspectai` | `app`(import `app.main`) | | uvicorn 目标 | `inspectai_api.main:app` | `app.main:app` | | 元数据库 | MySQL(必须配 MySQL 连接) | SQLite(默认无需 MySQL) | | 前端位置 | `inspectai_api/static/` | `app/static/` | | 部署目录 | `/opt/inspectai` | `/opt/nl2sqlv3`(v4 用 `/opt/nl2sqlv4`) | **升级模式**(只换代码,不动 .env / 依赖): ```bash bash .config/agent/aidev_install_nl2sql/scripts/deploy.sh \ --code-only \ --ssh "ssh -p 26225 xt@144.34.185.72" \ --deploy-dir /opt/nl2sqlv3 --port 9083 ```
### 验证(部署后四项检查) 部署完成后必须验证以下四项,全部通过才算成功: ```bash # ① /health 返回 200(进程存活) curl -s http://localhost:9083/health # 期望: {"code":0,"message":"success","data":{"status":"ok"},"detail":null} # ② 前端可访问(static 打进 wheel 了) curl -s -o /dev/null -w "%{http_code}" http://localhost:9083/index.html # 期望: 200 # ③ prompt 模板随包发布(智能问数依赖) ls /opt/nl2sqlv3/venv/lib/python3.12/site-packages/app/static/prompts/prompt01.md # 期望: 文件存在 # ④ 数据库类型支持(含 PG/DWS) cd /opt/nl2sqlv3 && venv/bin/python -c \ "from app.utils.db_factory import SUPPORTED_DB_TYPES; print(sorted(SUPPORTED_DB_TYPES))" # 期望: ['doris', 'dws', 'hive', 'impala', 'mysql', 'postgresql', 'sqlite'] ``` **验证清单**: | 检查项 | 命令 | 通过标准 | |--------|------|---------| | 进程存活 | `pgrep -f "uvicorn app.main.*9083"` | 有 PID | | 端口监听 | `ss -tlnp | grep 9083` | LISTEN 状态 | | /health | `curl -s localhost:9083/health` | code==0 | | 前端 | `curl -s -o /dev/null -w "%{http_code}" localhost:9083/index.html` | 200 | | prompts | `ls .../static/prompts/prompt01.md` | 文件存在 | | DB 类型 | `venv/bin/python -c "from app.utils.db_factory import SUPPORTED_DB_TYPES; ..."` | 含 postgresql + dws | | 外网可达 | `curl -s http://144.34.185.72:9083/health` | code==0 | 浏览器访问 `http://<服务器IP>:9083/` 应看到登录页,用 `admin/admin123` 可登录。
### 与 InspectAI 部署的核心区别 **一句话**:InspectAI 是三包(api/cal/shared)+ MySQL 元库 + Worker 异步任务;nl2sql 是单包(app)+ SQLite 元库 + 无 Worker。 **结构对比**: ```text InspectAI (n2sql_sample/backend) nl2sql (nl2sqlv3) ───────────────────────────── ───────────────────── src/ app/ inspectai_api/ ← Web API main.py ← FastAPI 入口 modules/ modules/ ← 业务模块 static/ static/ ← 前端 + prompts inspectai_cal/ ← Worker 异步任务 core/ ← 配置/DB/安全 inspectai_shared/ ← 共享组件 utils/ ← 多库 operator pyproject.toml ← 三包声明 pyproject.toml ← 单包声明 ``` **部署差异决策表**: | 场景 | InspectAI | nl2sql | |------|-----------|--------| | 首次部署 | 必须配 MySQL 连接(否则起不来) | 默认 SQLite 即可(零配置启动) | | 前端更新 | 重新 `npm run build` → 拷到 `inspectai_api/static/` → 重打包 | 重新 `npm run build` → 拷到 `app/static/` → 重打包 | | 新增数据库类型 | 改 `inspectai_api/modules/chat2sql/v1/utils/` | 改 `app/utils/db_factory.py` + `db_.py` | | 方言适配 | `prompts/prompt01.md` + `prompt_filter.py` | 同左(已对齐) | | 依赖注入 | `pyproject.toml [project].dependencies`(三包共享) | 同左(单包) | **部署命令对比**: ```bash # InspectAI(需要 MySQL 参数) bash .config/agent/aidev_install/scripts/deploy.sh \ --ssh "..." --port 9081 \ --mysql-host ... --mysql-db ... --mysql-user ... --mysql-pass ... # nl2sql(SQLite 零配置) bash .config/agent/aidev_install_nl2sql/scripts/deploy.sh \ --ssh "..." --port 9083 \ --backend-dir D:/zhinengjiancha.23/nl2sqlv3/api \ --web-dir D:/zhinengjiancha.23/nl2sqlv3/web \ --deploy-dir /opt/nl2sqlv3 ```
参考