Files
PlantAI-rag-new/docs/DEPLOY_FLOW.md
adminandClaude Fable 5 82b954b957 docs(deploy): BFF 手动重启 venv 绝对路径陷阱(2026-09-15 实测)
根因:venv 在部署根 install/venv,BFF 必须 cd sdk 起 app.main:app——
cd sdk 后相对路径 venv/bin/python 解析成不存在的
install/sdk/venv/bin/python(setsid failed to execute,端口静默 DOWN)。
修正 5f 示例命令(cd 前解析绝对路径)并补老进程 socket 失效场景。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-15 10:54:10 +08:00

554 lines
37 KiB
Markdown
Raw Permalink 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.
# PlantAI-rag-new 编译部署全流程手册(agent 自动执行版)
> 供后续 agent 逐条照做的完整流程:**编译源代码 → 打包(含数据库加密)→ 部署到指定 IP 目标服务器 → 部署后完整质检**。
> 硬性要求:目标服务器上是**编译过且数据库加密后**的版本,除编译白名单明文面(约 53 个入口/契约 .py)外**不得出现任何不在公开范围内的明文代码**。
> 所有命令在构建机(当前生产服务器)执行,除非步骤明确标注「目标机」。
> 零硬编码纪律:命令中的路径全部使用变量/脚本位置推导,不得把宿主路径写进任何新增脚本。
## 0. 前置事实与约定(agent 必读,不得想当然)
| 项 | 事实 |
|---|---|
| 构建机 | ssh -p 26518 xzq@192.168.0.175(内网);仓库 `/home/xzq/5090/teamWorkspace/zh/PlantAI-rag-new`,分支 `feature/bioflow-conversation-orchestration` |
| 构建机 Python | 统一 venv `/home/xzq/5090/teamWorkspace/zh/plantai-rag-new-venv/bin/python`(py3.12.13),各编译脚本的 `[python]` 参数一律传它 |
| 编译纪律 | 全部 .py → Cython → gcc hidden visibility → strip → elf 校验 → **删除 .py 与 .c 源码**;白名单明文:各树入口 main/api + config + schemas + db + qa.py + sse_relay.py + bioflow_proxy.py + 全部 `__init__.py`(含 yield 的 SSE 模块必须明文,断流崩溃历史教训) |
| 目标机绑定策略 | **nginx 80 = 唯一客户入口**;8090/8091/8092/8093/8094/8098/8099 全部 `127.0.0.1`(BFF 改绑见 §5b,启动脚本默认已是 127.0.0.1) |
| 数据库加密现状 | QA 的 KB 数据(断言 9.5G+2.7G)已具备加密工具链(`tooling/kb/encrypt_kb.sh`,AES-256-GCM + kb.key),但 **`tooling.env` 当前 SKIP_KB=1**(定版前不执行);Qdrant 向量与 Neo4j 冷拷按设计明文(检索数学/存储引擎限制,见 `tooling/KB_ENCRYPT_GUIDE.md`);**KB 解密密钥的运行时消费方尚未实现**(生产实测无 kb.key 引用),此为本手册遗留缺口(见 §7 已知限制) |
| 明文残余 | vstore 曾明文部署;已新增 `tooling/compile/compile_vstore.sh`(12 模块 .so + api.py 白名单)并纳入打包流水线;vstore 的 `.env` 含 VSTORE_KEY(Fernet 密文钥匙)不入包 |
| 生信内核 | `bioflow_kernel/`(528M)**不参与本流程**——它编译进 8 个 Docker 镜像(镜像内编译),镜像随 build_all 独立分发(`docker save/load`);目标机要生信还需 24 个镜像(8 bioflow-* + 16 plantai-env-*)与 BIOFLOW_DATA 数据根,详见 `docs/bioflow_image_build_playbook.md` |
| 打包产物目录 | `tooling/build/<VERSION>/install/`(VERSION 默认读 `tooling/tooling.env`,当前 2.0.0) |
| 已就绪辅助脚本 | `deploy/ollama_restart.sh`(本地嵌入模型拉起)、`vstore/vstore_restart.sh`(路径自推导重启)已入包 |
| nginx 代理模板 | `deploy/nginx/plantai.conf`(SSE 长连接 + 大文件上传适配,随安装包发布到 `install/nginx/`) |
| 176 已部署 | **2026-09-09 按本标准完成 176 部署**(隧道B 接入,`/srv/plantai` 布局:install + qa-db + data + docgen;nginx 80 唯一入口;无 GPU、无 docker、无生信);**2026-09-10 增补 MinerU CPU 裸部署(§5c)与 embedding 裸部署固化(§5d)** |
| QA 数据库裸进程 | **systemd 裸进程为最终部署标准**(无 docker):qdrant v1.18.3 + neo4j 5.15.0-community,全部 127.0.0.1;**端口 27433/27434 + 19888/19885**;数据在 `<部署根>/qa-db/`;安装脚本 `install/qa-db/install_qdrant.sh`、`install_neo4j.sh`(均免编译:官方静态二进制/tarball,支持本地 bin/ 离线分发);175 上 docker 容器(27333/19788)为其他项目共享服务、不动 |
## 1. 编译阶段(构建机)
### 1.1 预检
```bash
cd /home/xzq/5090/teamWorkspace/zh/PlantAI-rag-new
git status --short | head -5 # 应干净(或只有预期改动);有意外改动先报 TL
git log --oneline -1
PY=/home/xzq/5090/teamWorkspace/zh/plantai-rag-new-venv/bin/python
$PY -c 'import cython, setuptools; print("toolchain ok")'
df -h /home | tail -1 # 余量 ≥20G
```
### 1.2 五树编译(自动,约 10-20 分钟)
```bash
cd /home/xzq/5090/teamWorkspace/zh/PlantAI-rag-new
PY=/home/xzq/5090/teamWorkspace/zh/plantai-rag-new-venv/bin/python
# 逐树编译(也可直接跑 §2 的 build_all.sh,它会串行调全部)
for spec in "sdk sdk" "agent/qa qa" "agent/methodology methodology" "agent/legacy-rag legacy-rag"; do
set -- $spec
echo "== compile $2 =="
bash tooling/compile/compile_$([ "$2" = "legacy-rag" ] && echo legacy || echo "$2").sh "$1" "tooling/build/compiled-$2" "$PY"
done
# 注意 vstore 的编译脚本是 compile_vstore.sh:
bash tooling/compile/compile_vstore.sh vstore tooling/build/compiled-vstore "$PY"
# 每棵树编译完成后断言(关键质检点):
# - .so 数量 = 脚本输出 modules 数;- 无 .c 残留;- 白名单 .py 存在
find tooling/build/compiled-sdk -name '*.so' | wc -l # 期望 33(含私有资料模块)
find tooling/build/compiled-vstore -name '*.so' | wc -l # 期望 12
```
### 1.3 编译产物 import 冒烟(每树)
```bash
PY=/home/xzq/5090/teamWorkspace/zh/plantai-rag-new-venv/bin/python
( cd tooling/build/compiled-sdk && PYTHONPATH=. $PY -c "import app.main" )
( cd tooling/build/compiled-vstore && PYTHONPATH=. $PY -c "import vstore.api" )
( cd tooling/build/compiled-qa && PYTHONPATH=.:./src $PY -c "import scripts.qa_v1_1_73_server" )
```
## 2. 数据库加密阶段(构建机;SKIP_KB=1 时按「尚未定版」跳过,但必须人工确认跳过原因)
```bash
cd /home/xzq/5090/teamWorkspace/zh/PlantAI-rag-new
grep SKIP_KB tooling/tooling.env
# SKIP_KB=1 → 跳过本节并记录「数据库未定版」;SKIP_KB=0 → 执行:
df -h /home | tail -1 # ≥200G
ls -la tooling/build/2.0.0/kb.key # 若无:VERSION=2.0.0 bash tooling/crypto/init_keys.sh
bash tooling/kb/encrypt_kb.sh 2.0.0 # 1-2 小时,setsid 后台
# 产物断言:knowledge-2.0.0.tar + .sha256 存在;断言字段已 enc:v1 前缀
```
## 3. 一键打包阶段(build_all.sh 串行:编译→双审计→冒烟→KB→组装→SHA256+泄露门禁)
```bash
cd /home/xzq/5090/teamWorkspace/zh/PlantAI-rag-new
SKIP_KB=1 bash tooling/build_all.sh 2.0.1 2>&1 | tee /tmp/build_all.log
# 关键成功标志(逐条核对,任一不满足即失败):
grep -E 'compile\+harden complete|审计|泄露检查: 净|构建完成' /tmp/build_all.log
ls tooling/build/2.0.1/install/ # 应含 sdk qa methodology legacy-rag vstore ui start_all.sh ollama_restart.sh vstore_restart.sh SHA256SUMS
find tooling/build/2.0.1/install -name '*.so' | wc -l # 期望 371+12
find tooling/build/2.0.1/install -name '*.py' | wc -l # 期望 53(仅白名单)
```
## 4. 发布包组装(含镜像分发与 QA 运行面补齐)
```bash
cd /home/xzq/5090/teamWorkspace/zh/PlantAI-rag-new/tooling/build/2.0.1/install
# 4.1 QA 运行面补齐(编译树未自动含 prompts/env):
cp -r ../../../../agent/qa/prompts qa/prompts
cp ../../../../agent/qa/env/prod.env.example qa/env/prod.env.example
# 4.2 生信镜像导出(目标机要生信才做;约 20G+):
docker save -o /tmp/plantai-bioflow-images.tar $(docker images --format '{{.Repository}}:{{.Tag}}' | grep -E '^(bioflow|plantai-env)-')
# 4.2b 环境镜像全量同步原则(2026-09-14 起为部署原则,必做):
# 175 上全部 26 个环境类镜像(8 bioflow-* + 16 plantai-env-* +
# plantai-allenv + plantai-protected)必须部署到 176——扩展分析的
# runtime_image 可选范围 = 176 本地镜像全集。新增/修复镜像后必须同步,
# 并在 playbook 台账登记。流式传输(逐镜像,勿 tar 中转):
# docker save <ref> | ssh -i ~/.ssh/id_ed25519_p2p -p 26518 xzq@192.168.0.176 'docker load'
# 修复镜像覆盖同名 tag 前先回退保存旧 tag(如 :dev-before-xxx)。
# activate.d 陷阱:镜像构建后排查 conda env activate.d/*.sh 的宿主
# 绝对路径注入(PYTHONPATH 断链,panfamily 案例),必要时 COPY 模块进镜像。
# 4.3 目标机备份(同机/异机部署前备份;可选):
# 目标机:cp -r <deploy-root> <deploy-root>.bak.$(date +%s)
# 4.4 传输(示例 rsync;把 <target-host> 换成目标机地址):
rsync -av --progress tooling/build/2.0.1/install/ <target-host>:/srv/plantai/
```
## 5. 目标机部署
### 5.0 QA 数据库(systemd 裸进程,先于业务服务)
```bash
cd /srv/plantai/install
bash qa-db/install_qdrant.sh # 需 sudo;默认装 /usr/local/bin/qdrant + 27433/27434
bash qa-db/install_neo4j.sh # 需 sudo;默认装 /opt/neo4j-community-5.15.0 + 19888/19885
# 全新部署先按 5.0b 同步数据库文件再启动;换库用 qa-db/replace_db.sh
```
### 5.0b 数据库数据同步与整库替换(数据与安装包双制品分开传输)
> **数据库文件不进 install 安装包**(否则每次发布拖 82G 且整树覆盖会波及在用的库)。
> 全新部署/整库更新时,按本小节显式同步到目标机 `<部署根>/qa-db/`;程序发布只传 install/。
```bash
# 1) 从源机(现网/构建机)同步数据库到目标机(显式步骤,与 install 包传输并列):
rsync -av --progress <源机>:/<源部署根>/qa-db/qdrant/storage/ /srv/plantai/qa-db/qdrant/storage/
rsync -av --progress <源机>:/<源部署根>/qa-db/neo4j/data/ /srv/plantai/qa-db/neo4j/data/
# 2) 属主对齐(install 脚本也会做;rsync 后建议立即执行):
sudo chown -R <运行用户>:<运行用户> /srv/plantai/qa-db/{qdrant,neo4j}
# 3) 启动/重启裸进程(探活 curl 27433/collections 与 bolt 19888):
sudo systemctl restart qdrant-plantai neo4j-plantai
# 4) 整库替换(用户高频操作,数据目录换新,自动探活+回滚):
sudo bash qa-db/replace_db.sh qdrant <新storage目录>
sudo bash qa-db/replace_db.sh neo4j <新data目录>
```
- **位置约定**:数据根 = `<部署根>/qa-db/`(默认 /srv/plantai/qa-db;可用 QA_DB_ROOT env 覆盖,install 脚本与 replace_db.sh 都认)。
- **免编译说明**:qdrant 官方 release 静态二进制、neo4j 官方 community tarball,均无需编译;版本校验 = `qdrant --version`(v1.18.3)与 `neo4j version`(5.15.0)。受限网络用 `qa-db/bin/` 离线载体(install 包自带,无则自动回退下载含国内镜像源)。
- **运行用户**:RUN_USER 默认 xzq(sudo 装 systemd 时自动取 SUDO_USER);systemd 单元已配 WorkingDirectory 与重启策略。
### 5.1 环境准备
```bash
# - 安装 py3.12 venv 到 <安装根>/venv(start.sh/start_all.sh 均按 $ROOT/venv/bin/python 解析;175 旧路径为回退不再新建)
# - 依赖安装:pip install torch==2.13.0 --index-url https://download.pytorch.org/whl/cpu 先行(PyPI 无 +cpu 构建),
# 其余依赖用构建机 venv 的 pip freeze 清单安装
# - 编辑 .env(176 实战模板):
# sdk/.env = DOCGEN_LLM_*、VSTORE_BASE_URL=http://127.0.0.1:8094、BIOFLOW_ENABLED=false、
# BFF_NEO4J_URI_NORMALIZED=bolt://127.0.0.1:19888(裸进程机必填!默认值是 docker 口 19788)
# vstore/.env= VSTORE_KEY、VSTORE_ROOT=<部署根>/docgen、VSTORE_EMBED_URL=http://127.0.0.1:11434、VSTORE_EMBED_MODEL=bge-m3:567m
# 并复制一份到 <安装根>/.env(vstore_restart.sh 从安装根 source)
# qa|methodology|legacy-rag/env/prod.env = 路径类键(*_ROOT/PYTHONPATH/reranker/data)改目标机布局,密钥原样
# - methodology/legacy-rag 的 data 目录(data/evaluation/*、data/pc、data/split)是运行时依赖,须随部署复制
# - KB 密钥(若 SKIP_KB=0):把 kb.key 放到目标机并 chmod 600
cd /srv/plantai/install
bash ollama_restart.sh # 本地嵌入模型(已运行则跳过;缺失模型会提示 ollama pull bge-m3:567m)
bash start_all.sh # 五服务 + 生信自动编排(2026-09-12 起):先拉 ollama → QA/methodology/legacy/vstore → 探测同级 bioflow/bioflow_service/restart.sh(存在则启动/重启+8098 探活,不存在跳过)→ BFF
# 生信首次部署(仅新装;此后 start_all 自动拉起):
docker load -i /tmp/plantai-bioflow-images.tar
# 创建 bioflow_service 部署树 + .env(DOCGEN_LLM_*、BIOFLOW_IMAGE_MAP_PATH 指向 bioflow_kernel/assets 内的 stage_image_map.json)+ bash bioflow_service/restart.sh
```
- **Ollama 离线携带(目标机连不上 ollama.com 时唯一路径)**:只拷二进制不够——完整布局为 `/opt/ollama/bin/ollama`(实体)+ `/usr/local/bin/ollama`(symlink 指回它)+ `/opt/ollama/lib/ollama/`(llama-server 与 ggml/cuda runner;缺失时报 `llama-server binary not found`)。模型拷 `~/.ollama/models/`(bge-m3:567m 需 manifests + 对应 blob 两个文件,约 1.2G)。无 GPU 机器嵌入为 CPU 推理(单次约 50s,功能正常)。
- **下载源可达性(2026-09-09 实测)**:pypi/github/gh-proxy.com/ghfast.top/download.pytorch.org/apt 可达;ollama.com、registry.ollama.ai、dist.neo4j.org、objects.githubusercontent.com 不可达——neo4j 用 `qa-db/bin/` 离线 tarball(已入包),qdrant 下载走 gh-proxy 回退。
- **内网直连传输**:两机同内网时用目标机 26518 端口 + 专用密钥(如 `id_ed25519_p2p`)直连 rsync,不经跳板机;`/srv/plantai` 须先在目标机建好并 chown 部署用户。
## 5b. nginx 安装与 80 端口代理(目标机,从零开始)
> 目标机此前没有 nginx。本节让 80 端口成为唯一客户入口;8090 等全部服务只回环可达。
> **切换顺序是硬约束**:先装 nginx 并经 80 验证反代正常,再执行 BFF 改绑,避免客户访问断档。
```bash
# 1. 安装(apt/yum 两系通用探测;需 root 或 sudo)
command -v apt-get >/dev/null && (apt-get update && apt-get install -y nginx) \
|| (yum install -y nginx)
# 2. 放置配置模板(conf.d 目录因发行版而异:先看 nginx -t 输出里的 include 路径,
# 常见 /etc/nginx/conf.d/ 或 /etc/nginx/conf.d/*.conf)
cp /srv/plantai/install/nginx/plantai.conf /etc/nginx/conf.d/plantai.conf
# 如 server_name 需要固定域名/内网 IP:sed -i 's/server_name _;/server_name 192.168.x.x;/' /etc/nginx/conf.d/plantai.conf
# 3. 语法校验 + 启用
nginx -t
systemctl enable --now nginx 2>/dev/null || nginx # 无 systemd 的发行版直接 nginx 启动
# 4. 防火墙放行 80(如目标机有 ufw/firewalld)
ufw allow 80/tcp 2>/dev/null || true
firewall-cmd --add-service=http --permanent && firewall-cmd --reload 2>/dev/null || true
# 5. 经 80 验证反代(此时 8090 仍是旧绑定也没关系——80 → 127.0.0.1:8090 已通)
curl -s http://127.0.0.1:80/ | grep -o '<title>[^<]*'
curl -s http://127.0.0.1:80/api/health | python3 -m json.tool | head -5
# 6. BFF 改绑 127.0.0.1(start_all.sh 与 bff_restart.sh 默认已是 127.0.0.1):
# 用新版 start_all.sh 重启,或 bash deploy/bff_restart.sh(无参即 127.0.0.1)
ss -tln | grep ':8090 ' # 期望 127.0.0.1:8090,不再有 0.0.0.0
# 7. 内网最终验证
curl -s http://<目标机内网IP>:80/ | grep -o '<title>[^<]*'
```
nginx 模板关键适配(勿删勿改,详见 `deploy/nginx/plantai.conf` 注释):
- **SSE**(/api/qa/chat/stream、/api/bioflow/*):`proxy_buffering off` + `proxy_read_timeout 3600s`(> BFF 看门狗 1200s),否则问答/生信流会被 nginx 缓冲或掐断。
- **大文件上传**(上传无大小限制;生信基因组数百 MB):`client_max_body_size 0` + `proxy_request_buffering off` + `proxy_send_timeout 600s`。
- 静态资源由 BFF 自身托管(ui/dist),nginx 只做反代,不自管静态目录。
### 5c. MinerU CPU 裸部署(目标机,无 GPU/无 docker/无 conda)
> MinerU 以**裸服务**形态部署:构建机 conda env 目录整体搬运(env 内 bin/python 自包含,部署后不依赖 conda 二进制),
> 目标机仅需一个实体目录 + 一份模型缓存 + 一个 mineru.json 配置。2026-09-10 已在 176 全量验证(见下方冒烟)。
**形态与位置约定**:
- 环境目录:`/srv/plantai/mineru-cpu/`(175 `minerupy312` conda env 搬运,8.1G,含 torch 2.11.0+cu128 与 mineru 3.1.13);`MINERU_CMD=/srv/plantai/mineru-cpu/bin/mineru`。
- 模型缓存:`/srv/plantai/mineru-models/`(175 `~/.cache/modelscope` 同步,3.6G;PDF-Extract-Kit-1.0 与 MinerU2.5-Pro-2604-1.2B 两仓库)。
- 模型本地化配置:`~/mineru.json`(或 `MINERU_TOOLS_CONFIG_JSON` 指向任意路径),`models-dir` 把 pipeline/vlm 两个 repo 映射到实体路径——mineru 3.1.13 的 `MINERU_MODEL_SOURCE=local` 依赖此映射,缺失时报 `'NoneType' object has no attribute 'get'`。
**搬运步骤(175 构建机 → 176 目标机,P2P 内网直连)**:
```bash
# 1) 打包(注意 GNU tar 1.35 用 --numeric-owner 而非 --numeric-ids)
tar --numeric-owner -C /home/xzq/miniconda3/envs -cf /home/xzq/minerupy312-env.tar minerupy312
# 2) 传输(rsync 模板见 §5.1 内网直连;模型缓存与 env 包可并行传)
rsync -a --numeric-ids -e 'ssh -i ~/.ssh/id_ed25519_p2p -p 26518 -o BatchMode=yes' \
/home/xzq/minerupy312-env.tar xzq@192.168.0.176:/srv/plantai/minerupy312-env.tar
rsync -a --numeric-ids -e 'ssh -i ~/.ssh/id_ed25519_p2p -p 26518 -o BatchMode=yes' \
/home/xzq/.cache/modelscope/ xzq@192.168.0.176:/srv/plantai/mineru-models/
# 3) 解包(目标机;--strip-components=1 去掉 env 顶层目录名)
mkdir -p /srv/plantai/mineru-cpu
tar -xf /srv/plantai/minerupy312-env.tar -C /srv/plantai/mineru-cpu --strip-components=1
# 4) 重写 shebang(env 内 bin/* 的 shebang 是构建机绝对路径,不改则 bin/mineru 直接调用失败)
sed -i 's|#!/home/xzq/miniconda3/envs/minerupy312/bin/python|#!/srv/plantai/mineru-cpu/bin/python|' /srv/plantai/mineru-cpu/bin/*
```
**modelscope 缓存软链陷阱(必做)**:modelscope 缓存顶层是真名→转义名的**绝对路径软链**
(如 `PDF-Extract-Kit-1.0 -> /home/xzq/.cache/modelscope/hub/models/OpenDataLab/PDF-Extract-Kit-1___0`)。
rsync 原样保留后软链指向 175 旧路径,在目标机是断链。目标机上需删除软链、把转义名实体目录复制回真名:
```bash
cd /srv/plantai/mineru-models/hub/models/OpenDataLab
rm PDF-Extract-Kit-1.0 MinerU2.5-Pro-2604-1.2B
cp -a PDF-Extract-Kit-1___0 PDF-Extract-Kit-1.0
cp -a MinerU2___5-Pro-2604-1___2B MinerU2.5-Pro-2604-1.2B
```
**目标机 mineru.json**(运行用户家目录;或放部署根并用 `MINERU_TOOLS_CONFIG_JSON` 显式指向):
```json
{"models-dir": {
"pipeline": "/srv/plantai/mineru-models/hub/models/OpenDataLab/PDF-Extract-Kit-1.0",
"vlm": "/srv/plantai/mineru-models/hub/models/OpenDataLab/MinerU2.5-Pro-2604-1.2B"}}
```
**prod.env 增量键(methodology/legacy-rag 的 env/prod.env,只加键值不改其他)**:
```bash
MINERU_CMD=/srv/plantai/mineru-cpu/bin/mineru
MINERU_DEVICE_MODE=cpu
METHODOLOGY_MINERU_DEVICE_MODE=cpu # r3:显式设备开关(关键,见生效时序)
MINERU_MODEL_SOURCE=local
MINERU_TOOLS_CONFIG_JSON=/home/xzq/mineru.json
METHODOLOGY_MINERU_GPU_PREFLIGHT=0
METHODOLOGY_MINERU_RUNTIME_MODE=single
V15_MINERU_BACKEND=pipeline
# LLM 方法学提取阶段(r3,2026-09-10;两个 env 都加):
PLANTAI_GF_PYTHON_ENV=/srv/plantai/install/venv
DEEPSEEK_API_KEY=<与 sdk/.env DOCGEN_LLM_API_KEY 同款>
DEEPSEEK_BASE_URL=https://api.deepseek.com
DEEPSEEK_MODEL=deepseek-v4-flash
BBAV5_CAPABILITY_CATALOG=<部署根>/data/bbav5_capability_catalog.json
```
> **生效时序**:r3 修复(2026-09-10)后代码已显式注入设备模式——控制键是 `METHODOLOGY_MINERU_DEVICE_MODE`
> (设 `cpu` 时注入 `MINERU_DEVICE_MODE=cpu` 并清空 CUDA_VISIBLE_DEVICES;不设则自动探测,175 GPU 机行为不变)。
> 176 实测三个必须:①`MINERU_DEVICE_MODE` 直读键**不够**(被 build_mineru_runtime 忽略),必须加 `METHODOLOGY_MINERU_DEVICE_MODE=cpu`;
> ②extraction 主上游是 **legacy-rag 8093**(BFF 8000 不存在时回落 8093),methodology 与 legacy-rag 两个 prod.env 都要加;
> ③LLM 阶段另需(batch 脚本 2026-09-10 已随包补入 install/*/scripts/run_methodology_batch.py):
> `PLANTAI_GF_PYTHON_ENV=<目标机 venv>`(GF python 路径,默认是 175 路径必炸)、`DEEPSEEK_API_KEY`/`DEEPSEEK_BASE_URL`/`DEEPSEEK_MODEL`、
> `BBAV5_CAPABILITY_CATALOG=<目标机 catalog 路径>`(catalog 36K 数据文件随部署 rsync 到部署根 data/)。
> 176 全链实测:MinerU CPU 解析 22 页 ~67s → LLM 方法学提取 → completed 100%。
**CPU 冒烟(关键验收,176 实测通过)**:
```bash
env MINERU_DEVICE_MODE=cpu MINERU_MODEL_SOURCE=local \
MINERU_TOOLS_CONFIG_JSON=/home/xzq/mineru.json \
MODELSCOPE_CACHE=/srv/plantai/mineru-models CUDA_VISIBLE_DEVICES= \
/srv/plantai/mineru-cpu/bin/mineru -p <测试PDF> -o /tmp/mineru-smoke -b pipeline -l ch < /dev/null
# 断言:输出目录出现 *.md 且内容非空
find /tmp/mineru-smoke -name '*.md' -exec head -5 {} \;
```
- **stdin-EOF 陷阱**:mineru 3.1.13 CLI 内置本地 API 子进程带 `MINERU_API_SHUTDOWN_ON_STDIN_EOF=1` 关闭监视器;
后台冒烟若不显式 `< /dev/null`(或经 nohup/setsid 使 stdin 立即 EOF),服务会在任务中途主动 shutdown,报「Async task failed」且错误为空。
methodology 以 subprocess 调 MINERU_CMD 时同样要保证 stdin 非管道 EOF(修复代码会显式传 `MINERU_DEVICE_MODE` 并规避此问题)。
- 实测数据(176,AMD EPYC 7542 128 核 / 251G 内存):22 页英文 PDF 全程 67s(模型初始化约 4s);
输出 markdown 74KB 与 175 GPU 机基线**逐字节一致(md5 相同)**。CPU 比 GPU 慢数倍属预期(layout/OCR 全走 CPU 推理)。
- `mineru --version` 输出 `mineru, version 3.1.13`;torch 2.11.0+cu128 在无 GPU 机 import 正常(CPU 路径),无需重装 CPU torch。
- **备用路径(整目录搬运冒烟失败时才走)**:目标机 `python3 -m venv /opt/mineru-cpu-venv` 裸装(CPU torch + mineru 3.1.13),
模型缓存与 mineru.json 同上。本次整目录搬运一次通过,未启用。
### 5d. 本地向量库 embedding 模型部署(Ollama 裸服务,离线)
> 176 上 embedding 已裸部署(实体进程,非 docker):Ollama 官方二进制 + bge-m3:567m,vstore 已指向它。本节为文档固化。
**离线布局(Ollama 三件套)**:
- `/opt/ollama/bin/ollama`(实体二进制,38M)
- `/usr/local/bin/ollama -> /opt/ollama/bin/ollama`(symlink,进 PATH)
- `/opt/ollama/lib/ollama/`(llama-server + libggml-cpu-*.so runner + cuda_v12/cuda_v13;缺失时报 `llama-server binary not found`)
**bge-m3 模型位置**:`~/.ollama/models/`(manifests + blobs,1.1G;bge-m3:567m = 1 个 manifest + 对应 blob)。
**vstore env 指向**(vstore/.env,与 §5.1 一致):
```bash
VSTORE_EMBED_URL=http://127.0.0.1:11434
VSTORE_EMBED_MODEL=bge-m3:567m
```
**服务进程**:`/usr/local/bin/ollama serve` 常驻实体进程(pgrep -af ollama);拉起脚本 `install/ollama_restart.sh`。
**离线安装法**(目标机连不上 ollama.com/registry.ollama.ai 时唯一路径):
1. 源机把 `/opt/ollama/` 整目录与 `/usr/local/bin/ollama` symlink 打包(tar --numeric-owner);
2. 模型目录 `~/.ollama/models/` 一并 tar(注意属主:目标机同 UID 用户解包,否则 chown);
3. 目标机解包到相同路径,`ollama serve` 启动后 `ollama list` 应见 bge-m3:567m(否则 manifests 与 blobs 不匹配)。
**性能事实(176 无 GPU,CPU 推理)**:
- 短句(12 字)单次嵌入 4.1s;780 字长文本 1.0s(2026-09-10 实测,模型常驻后);
- 单篇文档全量嵌入(多 chunk 累计)约 52s 属历史实测值——功能正常,纯性能差距,无 GPU 机器属预期。
### 5e. 论文图片本地源部署(figures 双源,2026-09-11 起部署必做)
> BFF 答案配图走双源:本地 MinerU 图片优先,PMC 外网兜底。本地源三个条件缺一
> 即**静默降级**为纯 PMC(无报错、无日志,唯一症状是配图变少或全走外网)。
1. **env 两键**(sdk/.env,与 BFF 同 env,BFF 重启后生效):
```bash
PLANTAI_FIGURES_MINERU_ROOT=/srv/plantai/data/evaluation/methodology_upload_jobs
PLANTAI_FIGURES_REGISTRY=/srv/plantai/data/knowledge_base/paper_metadata_registry_v1_20260717/paper_metadata_registry_full.jsonl
```
2. **registry 文件传输**:源机 `knowledge_base/paper_metadata_registry_v1_20260717/
paper_metadata_registry_full.jsonl`(16.9MB)经 p2p 传到目标机上述路径;该文件
是 pmcid→paper_id 映射,缺失时本地索引即使有图也映射不出 PMCID。
3. **BFF 重启**:必须手动 kill 旧 BFF(见 §5f 陷阱)。
**验证**:用 /tmp 假任务目录受控测试——`demo_job/mineru/paper/txt/*_content_list.json`
(type=image 条目)+ `methodology_papers.csv`(paper_id 取 registry 内带 pmcid 的),
加载 figures 模块 `_build_local_index()` 应产出 `source=local` 条目;或部署后真实上传
一篇 registry 内论文后提问验证。
**已知限制(2026-09-11 实测)**:本地图片只对「paper_id 在 registry 中」的任务命中;
历史 demo_paper/e2e_check 测试任务不产生本地图片索引。
### 5f. 部署陷阱:BFF 重启必须手动 kill(start_all 不替换在跑进程)
**start_all.sh 不杀已运行的 BFF**:8090 被旧进程占住时,脚本内 uvicorn 新进程因端口
占用静默退出,自检看到的仍是旧进程(新 env 不生效)。步骤:
```bash
pgrep -f "uvicorn app.main:app" | xargs kill # 1. 杀旧 BFF(杀不掉用 kill -9;老进程可能活着但 socket 已失效,ss 看不到监听)
cd /srv/plantai/install && set -a && . sdk/.env && set +a # 2. 先回部署根 source env
( cd sdk && setsid /srv/plantai/install/venv/bin/python -m uvicorn app.main:app \
--host 127.0.0.1 --port 8090 > /tmp/plantai-sdk.log 2>&1 < /dev/null & ) && disown -a
# 3. 验证:ss 与 health 都要查(不能只看进程)
ss -tln | grep ':8090 ' && curl -s http://127.0.0.1:8090/api/health
```
**venv 路径陷阱(2026-09-15 实测)**:venv 在部署根 `/srv/plantai/install/venv`,而 BFF 必须
`cd sdk` 才能找到 `app.main:app`——**必须在 `cd sdk` 之前把 python 解析成绝对路径**
(或 `../venv/bin/python`),否则 `cd sdk && venv/bin/python` 解析成不存在的
`install/sdk/venv/bin/python`(`setsid: failed to execute venv/bin/python: No such file or directory`,
错误在 /tmp/plantai-sdk.log,端口静默 DOWN)。start_all 模板与此处均已按此纪律修正。
### 5g. r4 部署清单(2026-09-12,六项缺陷修复轮)
1. **QA 双域相关性阈值**(qa/env/prod.env,可选;不设则用代码默认值):
```bash
QA_MIN_VECTOR_SCORE=0.45 # 检索模式向量域(Qdrant score_threshold 同源)
QA_MIN_SOURCE_SCORE=0.25 # chat 模式最终域(rerank 后过滤)
```
2. **MinerU 依赖 accelerate**:目标机 mineru 环境需 `pip install accelerate`——缺失
症状为启动期失败「requires accelerate」(r4 的自动重试会报中文可读错误但不会自愈)。
3. **figures 两键 + registry**:见 §5e。
4. **vstore 内容去重**:vault.add 按 SHA256 覆盖旧 doc,无需 env;存量重复数据如需清理
走 rebuild-index/rebuild-chunks 端点。
5. **r4 前端 dist**:ui/dist 重构建(含 intent 词典「总结/概括/归纳」、KgPane 空态提示、
SourceRow 标题 fallback、DocPane 状态机修复)。
## 6. 部署后完整质检(agent 逐条执行并记录结果,全过才算部署成功)
```bash
# 6.1 进程与端口
ss -tln | grep -E ':(8090|8091|8092|8093|8094|8098)' # 8090=0.0.0.0,其余 127.0.0.1
# 6.0b QA 数据库裸进程专项
systemctl is-active qdrant-plantai neo4j-plantai # 均 active
ss -tln | grep -E ':(27433|27434|19888|19885)' # 全部 127.0.0.1
curl -s http://127.0.0.1:27433/collections | python3 -m json.tool | head -8 # 含 plant_srt_pc2025_v3_20260817_normalized
curl -s http://127.0.0.1:8091/health | python3 -m json.tool | head -8 # neo4j_entities 6438960 / qdrant_points 13040841
# 经 19888 bolt 登录验证(凭据用 QA 运行中进程的 NEO4J_PASSWORD,与容器一致):
/opt/neo4j-community-5.15.0/bin/cypher-shell -a bolt://127.0.0.1:19888 -u neo4j -p "$NEO4J_PASSWORD" "MATCH (n) RETURN count(n);"
# 6.1b nginx 专项
systemctl is-active nginx 2>/dev/null || pgrep -x nginx # nginx 存活
ss -tln | grep ':80 ' # 80 端口监听(0.0.0.0 或目标机内网 IP)
curl -s --max-time 5 http://127.0.0.1:80/ | grep -o '<title>[^<]*' # 经 80 首页可达
curl -s --max-time 10 -N -X POST http://127.0.0.1:80/api/qa/chat/stream \
-H 'Content-Type: application/json' \
-d '{"question":"水稻SPL基因家族的功能研究有哪些关键进展?","use_llm":true,"session_id":"nginx-smoke"}' \
| head -c 300 # 期望收到 "event: progress" 或 "event: complete" 帧(SSE 未被缓冲)
printf 'nginx 上传冒烟' > /tmp/nginx-upload-smoke.txt
curl -s --max-time 120 -X POST http://127.0.0.1:80/api/docgen/library \
-F 'file=@/tmp/nginx-upload-smoke.txt' -F 'name=nginx上传冒烟' -F 'as_template=false' | head -c 200
# 期望返回 ok:true + items(经 80 上传可用);记录 library_id 便于清理(DELETE /api/docgen/library/{id})
# 6.2 健康检查
curl -s http://127.0.0.1:8090/api/health | python3 -m json.tool # status ok、upstreams 全 ok
curl -s http://127.0.0.1:8094/api/vstore/health # ok、count 与 chunk_count
# 6.3 无明文代码核验(目标机部署树上执行,硬性门槛)
cd <deploy-root>
echo "剩余 .py 应为白名单(53 个以内):"; find sdk qa methodology legacy-rag vstore -name '*.py' | wc -l
echo "应无 .c 残留:"; find . -name '*.c' | wc -l
echo "应无宿主路径泄漏:"; grep -rn '/home/xzq' sdk/app/main.py sdk/app/config.py 2>/dev/null | wc -l # 期望 0
# 6.4 数据库加密核验(SKIP_KB=0 时)
# 断言:断言文件 enc:v1 前缀;kb.key 权限 600;目标机无明文 jsonl/parquet
# 6.5 功能冒烟(私有资料问答全链)
curl -s -X POST http://127.0.0.1:8090/api/qa/private -H 'Content-Type: application/json' \
-d '{"question":"批量导入压测样例 序号 38 是什么?"}' | python3 -m json.tool # hit:true + items + 带 [资料n] 的 answer
# 6.6 回归
curl -s -X POST http://127.0.0.1:8090/api/qa/chat/stream -H 'Content-Type: application/json' \
-d '{"question":"水稻SPL基因家族的功能研究有哪些关键进展?","use_llm":true,"session_id":"smoke"}' | head -c 200
curl -s 'http://127.0.0.1:8094/api/vstore/search?q=测试&as_template=false' | python3 -m json.tool # doc 级检索回归
```
### 6.7 功能自测(部署完成后必须实际跑通,非仅探活)
> §6.1-6.6 证明「起得来」,本节证明「用得了」。以下五项在 2026-09-09 的 176 部署上全部实跑通过;
> 新目标机部署完成后同样必须逐项执行并记录结果,任何一项不过不得交付。
**6.7.1 QA 完整 LLM 答案**(不能只收到 progress 帧就收工)
```bash
curl -s --max-time 300 -N -X POST http://127.0.0.1:80/api/qa/chat/stream \
-H 'Content-Type: application/json' \
-d '{"question":"水稻SPL基因家族的功能研究有哪些关键进展?","use_llm":true,"session_id":"selftest"}' \
| grep -E '^event:' | sort | uniq -c
# 期望:多个 progress + 至少 1 个 complete;complete 的 data.answer 为完整 markdown 答案(数百字以上)
```
**6.7.2 新文档入库闭环**(上传→嵌入→检索→清理,无 GPU 机器嵌入单次约 50s)
```bash
printf 'PlantAI 部署自测文档 独特标志串 QA_SELFTEST_SENTINEL 水稻氮素利用效率' > /tmp/selftest-doc.txt
LIB_ID=$(curl -s --max-time 120 -X POST http://127.0.0.1:80/api/docgen/library \
-F 'file=@/tmp/selftest-doc.txt' -F 'name=部署自测文档' -F 'as_template=false' \
| python3 -c 'import json,sys; print(json.load(sys.stdin)["items"][0]["library_id"])')
echo "library_id=$LIB_ID"
curl -s --max-time 30 'http://127.0.0.1:8094/api/vstore/search?q=水稻氮素利用效率&as_template=false' \
| python3 -m json.tool | head -20 # 期望命中「部署自测文档」
curl -s -X DELETE "http://127.0.0.1:8090/api/docgen/library/$LIB_ID" | head -c 100 # 清理
```
**6.7.3 legacy-rag 真实图谱查询**(注意冷缓存 COUNT 聚合 >60s,curl 超时必须 ≥120s)
```bash
curl -s --max-time 120 http://127.0.0.1:8093/api/v1/kg/stats | python3 -m json.tool
# 期望 status=success 且 relations 计数与源机一致(176 实测:SUPPORTED_BY 13092194 / ASSERTS 13040841)
curl -s --max-time 120 'http://127.0.0.1:8093/api/v1/kg/search?q=SPL&limit=3' | head -c 200
```
**6.7.4 methodology 真实任务流**
```bash
curl -s --max-time 60 http://127.0.0.1:8092/api/v1/methodology/status | python3 -m json.tool
# 期望 200 且 papers_table/batch_dir 指向目标机实际布局(相对路径已由 env 解析为绝对路径)
```
**6.7.5 生信(部署了生信时必做)**——镜像冒烟 + 金标 E2E 实跑
```bash
# a) 镜像冒烟 + 金标等价性(真实 docker 计算,约 5-15 分钟;无 GPU 更久):
cd <生信部署根>/bioflow_kernel
BIOFLOW_DATA=<生信部署根>/bioflow_data CPU=16 MEM=64g \
bash tests/smoke_golden.sh bioflow-gf:dev
# 期望 4 步全绿:[1/4] python 版本与镜像约定一致;[2/4] ldd 0 缺库;[3/4] CLEAN;
# [4/4] GOLDEN_DIFF_OK 24 IDs(与金标 expected ids diff 为空)
# b) 意图分流经 80 全链:
curl -s --max-time 90 -N -X POST http://127.0.0.1:80/api/bioflow/chat/stream \
-H 'Content-Type: application/json' \
-d '{"message":"帮我跑水稻 SPL 家族鉴定","session_id":"selftest-bioflow"}' | head -c 800
# 期望:event: plan_ready 携带 ≥2 条候选路径(stages 链非空);问答句应被分流到 QA 而非生信
```
**自测结论记录**:以上五项全部通过后,在部署记录中留一行「§6.7 功能自测 5/5 通过」,并注明金标 24 ID 结果与
两机一致性对比(QA 答案/图谱计数/金标 diff 与源机对照)。
### 6.8 normalized 图谱索引(缺陷 #32,2026-09-10 起部署必做)
背景:176 的 normalized 实例(Entity/ASSERTS,bolt 19888)没有 name_norm 索引,
BFF `/api/kg/entities` 前缀/等值查询会退化为全标签扫描(175 同源实例实测
634 万 Entity 等值/前缀约 2.8~2.9s,超出 S2 <1s 验收阈值)。索引脚本
`deploy/qa-db/create_neo4j_indexes.sh` 幂等补齐两个 RANGE 索引后命中索引。
```bash
# 显式传参(推荐;176 裸进程实例端口 19888):
bash deploy/qa-db/create_neo4j_indexes.sh bolt://127.0.0.1:19888 neo4j '<BFF_NEO4J_PASSWORD_NORMALIZED>'
# 或读环境变量:
NEO4J_URI=bolt://127.0.0.1:19888 NEO4J_PASSWORD='<密码>' bash deploy/qa-db/create_neo4j_indexes.sh
```
创建的索引(名称固定,BFF 健康探测按名校验):
- `plantai_normalized_entity_name_norm`(Entity.name_norm,RANGE,BFF 等值+前缀查询)
- `plantai_normalized_asserts_relation_category`(ASSERTS.relation_category,RANGE,
图谱关系类别概览聚合)
校验:`GET /api/health` 的 Neo4j 详情不再出现「name_norm 索引缺失」;
或直接跑脚本自带 SHOW INDEXES 输出,两索引 state 均 ONLINE。
## 7. 已知限制与遗留缺口(agent 不得自行补,报 TL 裁决)
1. **SKIP_KB=1**:数据库未定版,KB 加密打包当前跳过;真实交付前需 TL 拍板「定版」并执行 §2 全量加密(1-2 小时)。
2. **KB 解密密钥运行时消费方未实现**:加密包部署后尚无代码在启动时用 kb.key 解密断言数据(生产实测 QA 无 kb.key 引用)——**这是「加密后数据库可被系统使用」的前提缺口**,需要另行立项(QA 侧 .so 金标树改造或 BFF 解密层)。
3. **Neo4j normalized 数据(8.2G)按设计明文冷拷**(存储引擎限制,08-24 起无写入),属公开范围内例外,见 KB_ENCRYPT_GUIDE.md。
4. **构建机与目标机若为同一台机器**(内网 192.168.0.175 现状),部署就是替换现有 `plantai-rag-new-deploy-unified/` 部署树;执行前先备份、执行后按 §6 全量质检。
5. 本手册基于 2026-09-08 服务器实测编写;如工具链或目录结构有变,先对照仓库实际脚本修正本手册再执行。
6. **当前生产服务器(192.168.0.175)仍是 0.0.0.0:8090 直连形态,尚未执行 §5b 的 nginx 迁移**——启动脚本默认值已改为 127.0.0.1,但现网进程仍是旧绑定;迁移时按 §5b 顺序执行(先 nginx 后改绑),不要直接无参重启 bff_restart.sh 以免客户访问断档。
8. **start_all.sh 不会替换在跑的 BFF**(2026-09-11 实测):BFF 更新 env/代码后必须手动 kill + 重启,详见 §5f。
7. **175 上 QA 数据库 docker 容器(27333/19788)为其他项目共享服务,不随 PlantAI 部署流程管理**;PlantAI-rag-new 一律走裸进程(§5.0/5.0b);遗留库(26333/18788)现网无引用,清理需用户单独命令。