深色模式
博衍通 — RAG 知识库
定位:把研报/周报/规则等"文字结论"沉淀为本地知识库,经 LLM 检索分析后作为第 7 路投票因子融入交易机会扫描。 版本:1.0 | 配套文档:00-系统总览 · 02-技术架构 · 06-开发指南部署脚本目录:
server/rag/(Linux 服务器侧)+script/deploy_rag_api.ps1(Windows 开发机一键部署)
一、功能总览
1.1 客户端三个入口
| 入口 | 功能 | 使用节奏 |
|---|---|---|
| 📚 RAG 知识库(期货菜单) | 上传文档(PDF/DOCX/PPTX/XLSX/TXT/CSV/MD/JSON)、分类浏览、删除、立即索引、连接测试 | 一次性建库 + 不定期投喂 |
| 📝 品种周报(期货菜单) | 周报模板页:行情/基差/仓单自动预填 + 人工补写基本面/新闻/结论 → 保存即投喂 | 每周 |
| 🎯 交易机会(第 7 路投票) | 扫描时检索知识库 + LLM 提取因素 → 机会卡显示 📚 RAG 徽章 | 自动(扫描时) |
1.2 知识库内容建议
核心原则:量化的交给系统(行情/持仓/仓单自动采集),结论与规则交给知识库(研报/细则/周报)。
- ✅ 投喂:交易所合约细则、交割规则、产业链知识、券商/期货公司研报、USDA/官方报告、宏观报告、品种周报
- ❌ 不投喂:原始行情数据(K线/Tick)、纯图片 PDF、过期流水数据
- 首次建库:每品种"合约细则 + 1-2 份研报"起步;每周用周报页补周报
二、部署架构
2.1 最终拓扑(实测通过)
客户机(局域网)──RAG 全套──▶ 10.10.44.38:8090(Futures.Server.Api / rag-api)
├─ 文档/元数据 → 局域网 PG 10.10.44.35(srv_rag_* 表)
├─ 文件 → ~/futures-rag/rag_data/documents/
├─ 嵌入/检索/分析 → 本机 llama-server + Qdrant
└─ /rag/ingest → 本机 rag_ingest.py
└──行情/版本──▶ api.bywl.top / u.bywl.top(公网无算力,不参与 RAG)
后期 frp:10.10.44.38 跑 frpc 映射 8090 → 公网 VPS → 远程客户机改 RagServerUrl 即可2.2 两种运行模式
| 模式 | 触发条件 | 行为 |
|---|---|---|
| 本地单机 | 客户端 RagServerUrl 留空 | 文档/元数据/嵌入全在客户端,自产自用 |
| 远程共享 | 设置页勾选"使用远程共享知识库"填 RagServerUrl | 上传/周报/嵌入/检索/分析全走服务器,多客户机共享同一知识库 |
2.3 服务器组件
| 服务 | 端口 | 说明 |
|---|---|---|
| LLM(llama-server Qwen3.8-27B) | 8080 | 聊天补全/因素提取;必须加 --reasoning off(否则 Qwen3 思维链吃光 token,content 为空) |
| 嵌入(llama-server nomic-embed) | 8081 | 文本向量化;-ngl 0 强制 CPU 不抢显存 |
| Qdrant(1.12.5 musl) | 6333 | 向量存储与检索 |
| RAG API(Futures.Server.Api) | 8090 | /rag/* REST 端点,客户端唯一入口 |
2.4 RAG API 端点一览
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /rag/health | 三服务健康摘要 |
| GET/POST/DELETE | /rag/categories | 分类管理 |
| POST | /rag/documents | 上传(multipart,SHA256 去重) |
| GET/DELETE | /rag/documents/{id} | 列表/详情/删除 |
| POST | /rag/chat /rag/embed /rag/search | LLM/嵌入/检索代理 |
| POST | /rag/analyze | 品种 RAG 因素分析(检索+LLM 提取) |
| POST | /rag/ingest | 触发服务器 rag_ingest.py |
三、部署步骤
3.1 一键部署(推荐,Windows 开发机执行)
powershell
$env:RAG_DEPLOY_PWD = "<服务器密码>" # 密码走环境变量,不硬编码
powershell -ExecutionPolicy Bypass -File script\deploy_rag_api.ps1 `
-ServerHost 10.10.44.38 -ServerUser hushijian -PgHost 10.10.44.35 -ApiPort 8090脚本 6 步自动完成:① 本地 publish(linux-x64 自包含)→ ② 上传 ~/rag-api/ → ③ 生成 appsettings(PG 指局域网 + RAG 三件套 localhost)→ ④ 部署 rag_ingest.py + pip 依赖 → ⑤ systemd rag-api.service → ⑥ 健康检查。
前置:服务器已装 .NET 9 runtime 或使用自包含发布;sudo 可用(装系统级 systemd 单元用)。
3.2 RAG 三件套(服务器首次部署)
bash
cd server/rag
./download_models.sh --embed-only --mirror # 只下嵌入模型 + hf-mirror 国内镜像
./start_all.sh # 启动 Qdrant+嵌入;LLM 8080 已占用自动跳过
# 或手动:./start_qdrant.sh && ./start_embed.sh && ./start_llm.sh- 模型下载源(HuggingFace 不通时):hf-mirror.com 官方镜像 →
export HF_ENDPOINT=https://hf-mirror.com→ ModelScope(Qwen/嵌入均有,modelscope download) - Qdrant 从 GitHub 下载失败:
QDRANT_DOWNLOAD_URL="https://ghfast.top/https://github.com/qdrant/qdrant/releases/download/v1.12.5/qdrant-1.12.5-x86_64-unknown-linux-musl.tar.gz" ./start_all.sh,或本地下载 scp 上传 - 冒烟测试:
RAG_SERVICE_HOST=10.10.44.38 python3 smoke_test.py(复刻客户端调用面,全 ✅ 才算成功)
3.3 客户端启用(设置页填写指南)
设置页(系统 → 系统设置 → RAG 知识库 区块),二选一填写:
方式一:远程共享(推荐,多机共用知识库)
| 字段 | 填写 |
|---|---|
| ✅ 使用远程共享知识库(RAG 服务) | 勾选 |
| RAG 服务地址 | http://10.10.44.38:8090 |
| ✅ 启用 RAG 知识库 | 勾选 |
| LLM/嵌入/向量库地址 | 留空(远程模式不直连,全走服务器) |
勾选远程共享后会自动填入默认地址
http://10.10.44.38:8090。所有客户机填同一个地址 = 共享同一知识库。
方式二:本地直连(单机自用)
| 字段 | 填写 |
|---|---|
| ✅ 启用 RAG 知识库 | 勾选 |
| LLM 服务地址 | http://10.10.44.38:8080/v1 |
| 嵌入服务地址 | http://10.10.44.38:8081/v1 |
| 向量库地址 | http://10.10.44.38:6333 |
| 向量集合名 | rag_documents |
| 模型名 | Qwen3.8-27B-Q4_K_M |
| 远程共享勾选 | 不勾 |
填写后验证
- 点 Save → 保存后立即生效(清 RAG 缓存)
- 打开 📚 RAG 知识库 页 → 顶部显示
LLM ✅ 嵌入 ✅ 向量库 ✅即配置正确 - 交易机会页扫描时机会卡出现 📚 RAG 徽章
服务器已部署 RAG API(10.10.44.38:8090)的场景 → 直接方式一。
3.4 systemd 持久化(开机自启)
- 用户级(LLM/嵌入/Qdrant):
server/rag/install_systemd_user.sh(先sudo loginctl enable-linger hushijian) - 系统级(rag-api):
sudo cp server/rag/systemd/rag-api.service /etc/systemd/system/+sudo systemctl enable --now rag-api
三·B、RAG 数据验证(上传 + 索引后如何确认生效)
验证三步(全在客户端/API,无需碰数据库)
第 1 步:看文档状态(知识库页)
上传文档 → 点「⚡ 立即索引」后,文档列表该文档的状态:
| 状态 | 含义 | 下一步 |
|---|---|---|
已嵌入(embedded)+ 分块数 > 0 | ✅ 索引成功 | 进入第 2 步 |
待嵌入(pending) | 索引没跑 | 检查 RAG 服务(连接测试)+ 配置 |
失败(failed) | 索引报错 | 看错误信息(格式不支持/服务不可达/嵌入失败) |
第 2 步:连接测试(知识库页顶部)
点「🔌 连接测试」→ 应显示 LLM ✅ 嵌入 ✅ 向量库 ✅。
- 远程共享模式 → 实际是服务器
/rag/health的结果(三个端点都在 10.10.44.38) - 任一 ❌ → 服务未启动或网络不通,索引不可能成功
第 3 步:交易机会页看效果
期货 → 🎯 交易机会 → 扫描 → 对应品种(如传的是豆粕周报,看 M 品种)机会卡"期货判断"区出现 📚 RAG 徽章(Tooltip 显示因素明细)。
没出现 = ① 该品种检索无命中(知识库内容太少/不相关);② RAG 未启用;③ RAG 分析 4h 缓存未过期。
更直接的 API 验证(服务器或任意终端)
bash
# ① 检索验证:返回命中文本 = 向量已入库
curl -X POST http://10.10.44.38:8090/rag/search -H 'Content-Type: application/json' \
-d '{"query":"豆粕 供需","topK":3}'
# ② 分析验证:返回 factors 列表 = LLM 链路通
curl -X POST http://10.10.44.38:8090/rag/analyze -H 'Content-Type: application/json' \
-d '{"ProductId":"M"}'
# ③ 健康验证
curl -s http://10.10.44.38:8090/rag/health数据存储位置(上传的文档去哪了)
三层存储,各管一部分:
| 数据 | 存储位置(远程共享模式) | 说明 |
|---|---|---|
| 文档文件(原始 PDF/TXT) | 服务器 ~/futures-rag/rag_data/documents/ | 命名 {id}_{文件名},如 7_周报_2026W34_M.txt |
| 文档元数据 | PG srv_rag_documents 表(10.10.44.35) | 标题/品种/状态/分块数 |
| 嵌入向量 | Qdrant rag_documents 集合(10.10.44.38:6333) | 分块向量 + 文本 + 品种标签 |
本地单机模式:文档文件在客户端
安装目录/rag_data/documents/,元数据在本地rag_documents表。
验证数据真实落库(服务器上执行):
bash
# ① Qdrant 向量数(points_count > 0)
curl -s http://localhost:6333/collections/rag_documents | grep points_count
# ② 元数据表状态
PGPASSWORD=xxx psql -h 10.10.44.35 -U postgres -d futures \
-c "SELECT id, title, status, chunk_count FROM srv_rag_documents ORDER BY id DESC LIMIT 5;"
# ③ 文档文件
ls -la ~/futures-rag/rag_data/documents/四、最终部署状态(2026-08-20 实测通过)
4.1 服务清单
| 服务 | systemd 单元 | 类型 | 端口 | 状态 | 关键配置 |
|---|---|---|---|---|---|
| LLM | rag-llm.service(用户级) | llama-server Qwen3.8-27B | 8080 | ✅ active | --reasoning off(关键!) |
| 嵌入 | rag-embed.service(用户级) | llama-server nomic-embed | 8081 | ✅ active | -ngl 0 CPU |
| Qdrant | rag-qdrant.service(用户级) | qdrant 1.12.5 | 6333 | ✅ active | musl 静态版 |
| RAG API | rag-api.service(系统级) | Futures.Server.Api | 8090 | ✅ active | User=hushijian(关键!) |
服务器目录:~/rag-api/(API 产物)· ~/futures-rag/ingest/(rag_ingest.py + 依赖)· ~/futures-rag/rag_data/documents/(文档落盘)· ~/qdrant/(Qdrant)
4.2 全链路验证结果
✅ GET /rag/health → {"llmOk":true,"embedOk":true,"qdrantOk":true}
✅ POST /rag/documents → 上传 id=7, pending → ingest → embedded, chunkCount=1
✅ POST /rag/search → score 0.52 命中豆粕周报
✅ POST /rag/analyze → available:true, 3 factors, "震荡偏多", weightedScore:0.6
✅ Qdrant points_count → 1(向量真实落库)4.3 部署踩坑记录(运维必读)
| 问题 | 根因 | 修复 |
|---|---|---|
status=203/EXEC 服务起不来 | scp 丢执行位 + systemd 缺 User | chmod +x + 单元加 User=hushijian |
rag_ingest 报 psycopg2 ModuleNotFoundError | systemd 默认 root 跑子进程,~/.local 不在 PYTHONPATH | RagIngestRunner 注入 PYTHONPATH + User=hushijian |
/rag/documents 列表字段全空 | Dapper 不映射 snake_case 列 | SQL 显式别名 product_id AS ProductId |
索引报 文件缺失 | RAG_DATA_DIR 指向父目录,文件在 documents/ 子目录 | Path.Combine(_dataDir, "documents") |
索引报 Qdrant 写入失败 | upsert 用 POST 被路由到检索端点(官方契约是 PUT) | method="PUT" + id 放 points 内 |
analyze 返回 available:false | Qwen3 思考模式:token 全花在 reasoning_content | LLM 加 --reasoning off + 解析回退 reasoning_content |
| Qdrant 启动 panic | 版本降级读旧数据(1.14→1.12 格式不兼容) | 清空 ~/qdrant/storage 重建 |
五、运维要点
- 重启 LLM 中断 8080 约 1-2 分钟(131072 ctx 模型重新加载);就绪后
curl -s http://localhost:8080/v1/models有响应再测 analyze - Qdrant 备份:
~/qdrant/storage/纳入备份;升级版本必须清空 storage(格式不兼容) - 安全:llama-server/Qdrant 无鉴权,防火墙限制 8080/8081/6333 来源;frp 暴露时加 auth token
- 桌面发布:客户端代码改动后需
script\build_release.ps1重新出包(含 RAG 设置项) - frp 公网接入:10.10.44.38 跑 frpc 映射 8090 → 公网 VPS,客户端只改
RagServerUrl
六、常见问题
| 现象 | 排查 |
|---|---|
知识库页连接测试 ❌ 不可达 | 逐个探测 curl -v http://10.10.44.38:8080/v1/models、:8081、:6333/collections;查防火墙 |
立即索引 返回"嵌入服务不可用" | 确认 8081 已启动:curl -s -X POST http://localhost:8081/v1/embeddings -d '{"input":"ping"}' |
索引报 Qdrant 写入失败 | logs/qdrant.log;确认 6333 可达:curl -s http://localhost:6333/collections |
| 嵌入后搜索无结果 | 确认集合名一致(rag_documents) |
| 交易机会页无 RAG 徽章 | ① 知识库页连接测试是否全 ✅;② 该品种是否有已嵌入文档(无命中不投票是预期);③ RAG 分析缓存 4h |
| 交易机会页扫描很慢(>数分钟) | 2026-08-20 已优化:RagFactorService 改为扫描不阻塞——首次扫描 RAG 未命中即返回(后台 2 并发预热填 4h 缓存),扫描秒级完成;第二次扫描带 RAG 投票。若仍慢,检查服务器 LLM(10.10.44.38:8080)负载(/rag/health 三项是否 ✅) |
调度任务 RAG知识库嵌入 未执行 | 调度配置页确认 enabled 与 runDays=[2](每周二 20:00) |