|
### 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 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 项目是什么
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_
|