根因: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>
37 KiB
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 预检
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 分钟)
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 冒烟(每树)
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 时按「尚未定版」跳过,但必须人工确认跳过原因)
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+泄露门禁)
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 运行面补齐)
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 裸进程,先于业务服务)
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/。
# 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 环境准备
# - 安装 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 改绑,避免客户访问断档。
# 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/(175minerupy312conda 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 内网直连):
# 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 旧路径,在目标机是断链。目标机上需删除软链、把转义名实体目录复制回真名:
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 显式指向):
{"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,只加键值不改其他):
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 实测通过):
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 一致):
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 时唯一路径):
- 源机把
/opt/ollama/整目录与/usr/local/bin/ollamasymlink 打包(tar --numeric-owner); - 模型目录
~/.ollama/models/一并 tar(注意属主:目标机同 UID 用户解包,否则 chown); - 目标机解包到相同路径,
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(无报错、无日志,唯一症状是配图变少或全走外网)。
-
env 两键(sdk/.env,与 BFF 同 env,BFF 重启后生效):
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 -
registry 文件传输:源机
knowledge_base/paper_metadata_registry_v1_20260717/ paper_metadata_registry_full.jsonl(16.9MB)经 p2p 传到目标机上述路径;该文件 是 pmcid→paper_id 映射,缺失时本地索引即使有图也映射不出 PMCID。 -
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 不生效)。步骤:
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,六项缺陷修复轮)
-
QA 双域相关性阈值(qa/env/prod.env,可选;不设则用代码默认值):
QA_MIN_VECTOR_SCORE=0.45 # 检索模式向量域(Qdrant score_threshold 同源) QA_MIN_SOURCE_SCORE=0.25 # chat 模式最终域(rerank 后过滤) -
MinerU 依赖 accelerate:目标机 mineru 环境需
pip install accelerate——缺失 症状为启动期失败「requires accelerate」(r4 的自动重试会报中文可读错误但不会自愈)。 -
figures 两键 + registry:见 §5e。
-
vstore 内容去重:vault.add 按 SHA256 覆盖旧 doc,无需 env;存量重复数据如需清理 走 rebuild-index/rebuild-chunks 端点。
-
r4 前端 dist:ui/dist 重构建(含 intent 词典「总结/概括/归纳」、KgPane 空态提示、 SourceRow 标题 fallback、DocPane 状态机修复)。
6. 部署后完整质检(agent 逐条执行并记录结果,全过才算部署成功)
# 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 帧就收工)
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)
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)
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 真实任务流
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 实跑
# 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 索引后命中索引。
# 显式传参(推荐;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 裁决)
- SKIP_KB=1:数据库未定版,KB 加密打包当前跳过;真实交付前需 TL 拍板「定版」并执行 §2 全量加密(1-2 小时)。
- KB 解密密钥运行时消费方未实现:加密包部署后尚无代码在启动时用 kb.key 解密断言数据(生产实测 QA 无 kb.key 引用)——这是「加密后数据库可被系统使用」的前提缺口,需要另行立项(QA 侧 .so 金标树改造或 BFF 解密层)。
- Neo4j normalized 数据(8.2G)按设计明文冷拷(存储引擎限制,08-24 起无写入),属公开范围内例外,见 KB_ENCRYPT_GUIDE.md。
- 构建机与目标机若为同一台机器(内网 192.168.0.175 现状),部署就是替换现有
plantai-rag-new-deploy-unified/部署树;执行前先备份、执行后按 §6 全量质检。 - 本手册基于 2026-09-08 服务器实测编写;如工具链或目录结构有变,先对照仓库实际脚本修正本手册再执行。
- 当前生产服务器(192.168.0.175)仍是 0.0.0.0:8090 直连形态,尚未执行 §5b 的 nginx 迁移——启动脚本默认值已改为 127.0.0.1,但现网进程仍是旧绑定;迁移时按 §5b 顺序执行(先 nginx 后改绑),不要直接无参重启 bff_restart.sh 以免客户访问断档。
- start_all.sh 不会替换在跑的 BFF(2026-09-11 实测):BFF 更新 env/代码后必须手动 kill + 重启,详见 §5f。
- 175 上 QA 数据库 docker 容器(27333/19788)为其他项目共享服务,不随 PlantAI 部署流程管理;PlantAI-rag-new 一律走裸进程(§5.0/5.0b);遗留库(26333/18788)现网无引用,清理需用户单独命令。