# Nihaisha RAG — 合并部署与水平扩容分析 > **版本**: v1.0 > **日期**: 2026-07-28 > **目标**: 将倪海厦 RAG 代码合并到药典助手项目 `D:\project\AIyaodianzhushou`,分析部署方案和扩容路径 --- ## 目录 1. [现有项目对比](#1-现有项目对比) 2. [合并策略](#2-合并策略) 3. [代码合并方案](#3-代码合并方案) 4. [部署拓扑](#4-部署拓扑) 5. [水平扩容路径](#5-水平扩容路径) 6. [数据库规划](#6-数据库规划) 7. [API 设计](#7-api-设计) --- ## 1. 现有项目对比 | 维度 | 药典助手 (AIyaodianzhushou) | 倪海厦 RAG (nihaisha-nishi-tcm) | |------|:---:|:---:| | **数据依赖** | 2025 年版中国药典 | 倪海厦中医课程资料(22 个 PDF)| | **框架** | Spring Boot 3.3 + JPA/Hibernate | 计划 Spring Boot 3.3 + MyBatis | | **Java** | 21 | 21 | | **数据库** | PostgreSQL 16 + pgvector | PostgreSQL 16 + pgvector(新) | | **缓存** | Redis 7 | Redis 7(可共用) | | **LLM** | 百炼 DashScope (Qwen) | Ollama 主 + 百炼降级 | | **向量嵌入** | DashScope text-embedding-v3 (1024维) | BGE-M3 ONNX Runtime 本地 (1024维) | | **重排序** | 规则型 Reranker(关键词+权重) | BGE-Reranker ONNX Runtime 本地 | | **鉴权** | Spring Security + JWT | 需共用药典的鉴权体系 | | **端口** | 9000 | 计划 8080 | | **构建** | 单模块 Maven | 计划单模块 Maven | | **部署** | Standalone JAR + supervisord + nginx | — | | **ORM** | JPA/Hibernate | MyBatis | ### 关键冲突点 | 冲突 | 分析 | |------|------| | **ORM 不一致** | 药典用 JPA,倪海厦计划用 MyBatis。同一项目混用两套 ORM 维护成本高 | | **Embedding 模型不同** | 药典用 DashScope API(远程),倪海厦用 ONNX(本地)。两个模型是独立的,不冲突 | | **端口不同** | 如果不合并,需要两个端口、两个 JAR、两套部署 | --- ## 2. 合并策略 ### 2.1 推荐方案:统一 ORM + 独立模块 ``` AIyaodianzhushou/ ├── backend-java/ # 现有项目(保持 JPA) │ ├── pom.xml │ ├── src/main/java/com/pharmacopoeia/ │ │ ├── PharmacopoeiaApplication.java │ │ ├── entity/ # 药典实体(JPA) │ │ ├── repository/ # JPA Repository │ │ ├── service/ # 药典服务 │ │ ├── controller/ # 药典 Controller │ │ ├── config/ # 共用配置 │ │ ├── security/ # 共用鉴权 │ │ │ │ │ └── nihaisha/ # ★ 新增:倪海厦 RAG 子包 │ │ ├── entity/ # 倪海厦实体(JPA,统一风格) │ │ ├── repository/ # JPA Repository │ │ ├── service/ # 倪海厦检索服务 │ │ ├── controller/ # 倪海厦 Controller │ │ ├── engine/ # ONNX Runtime 引擎 │ │ └── config/ # 倪海厦专属配置 │ │ │ └── src/main/resources/ │ ├── application.yml │ └── db/migration/ # Flyway 迁移(新增倪海厦表) ``` ### 2.2 为什么改 JPA 而不是 MyBatis | 维度 | 用 JPA(推荐) | 混用 MyBatis + JPA | |------|:---:|:---:| | 维护成本 | ✅ 一套规范,团队只学一个 | ❌ 两套规范,排查问题分裂 | | 代码一致性 | ✅ entity/repository 模式统一 | ❌ entity + mapper.xml 双模式 | | 连接池 | ✅ 共用 HikariCP | ✅ 可共用 | | 事务管理 | ✅ 统一 `@Transactional` | ⚠️ 需注意跨 ORM 事务 | | Spring Data 生态 | ✅ 审计、分页、Specification 统一 | ❌ 各用各的 | | 动态查询 | JPA Criteria / QueryDSL | MyBatis XML 更灵活(但药典已有 JPA) | **结论**:药典项目已经用 JPA 跑了生产,强行引入 MyBatis 会增加认知负担。倪海厦的 11 张表查询用 JPA + Native Query(向量搜索、全文搜索本来就是写 SQL 的)完全够用。 ### 2.3 共用 vs 独立 | 共用 | 独立 | |------|------| | Spring Security + JWT 鉴权 | ONNX Runtime 引擎(BGE-M3 + Reranker) | | Redis 缓存 | 数据层(独立的表,不跨库 join) | | HikariCP 连接池 | Controller(独立路径 `/api/v1/nihaisha/`) | | Flyway 迁移 | 100 MB 日志文件(追加到 logback) | | 日志框架 (Logback) | | | 同一个 PostgreSQL 实例 | | | 同一个 Nginx 反向代理 | | | 同一个 supervisord 进程 | | --- ## 3. 代码合并方案 ### 3.1 目录结构 ``` backend-java/ ├── pom.xml # 加入 ONNX Runtime 依赖 ├── src/main/java/com/pharmacopoeia/ │ ├── PharmacopoeiaApplication.java │ │ │ ├── config/ # 现有共用配置 │ │ ├── SecurityConfig.java │ │ ├── WebConfig.java │ │ └── RedisConfig.java │ │ │ ├── entity/ # 药典实体 (不变) │ ├── repository/ # 药典 Repository (不变) │ ├── service/ # 药典服务 (不变) │ ├── controller/ # 药典 Controller (不变) │ │ │ ├── nihaisha/ # ★ 倪海厦 RAG │ │ ├── entity/ │ │ │ ├── NihMeta.java │ │ │ ├── NihDocument.java │ │ │ ├── NihParagraph.java │ │ │ ├── NihRetrievalUnit.java │ │ │ ├── NihVectorEmbedding.java │ │ │ ├── NihKnowledgeUnit.java │ │ │ ├── NihGuideNode.java │ │ │ ├── NihEvidenceRecord.java │ │ │ ├── NihGraphEntity.java │ │ │ └── NihRelation.java │ │ │ │ │ ├── repository/ │ │ │ ├── NihMetaRepository.java │ │ │ ├── NihDocumentRepository.java │ │ │ ├── NihParagraphRepository.java │ │ │ ├── NihRetrievalUnitRepository.java │ │ │ ├── NihVectorEmbeddingRepository.java │ │ │ ├── NihKnowledgeUnitRepository.java │ │ │ ├── NihGuideNodeRepository.java │ │ │ ├── NihEvidenceRecordRepository.java │ │ │ ├── NihGraphEntityRepository.java │ │ │ └── NihRelationRepository.java │ │ │ │ │ ├── service/ │ │ │ ├── NihSearchService.java # 单通道检索 │ │ │ ├── NihHybridSearchService.java # 混合检索 + RRF │ │ │ ├── NihKnowledgeService.java # 知识三元组 │ │ │ ├── NihGraphService.java # 知识图谱 │ │ │ ├── NihEvidenceService.java # 证据链 │ │ │ ├── NihGuideNodeService.java # 导航节点 │ │ │ ├── NihQueryRewriterService.java # 查询改写 │ │ │ ├── NihEmbeddingService.java # ONNX BGE-M3 │ │ │ └── NihRerankerService.java # ONNX BGE-Reranker │ │ │ │ │ ├── controller/ │ │ │ ├── NihSearchController.java # /api/v1/nihaisha/search │ │ │ ├── NihKnowledgeController.java # /api/v1/nihaisha/knowledge │ │ │ ├── NihGuideController.java # /api/v1/nihaisha/guide │ │ │ ├── NihGraphController.java # /api/v1/nihaisha/graph │ │ │ └── NihEvidenceController.java # /api/v1/nihaisha/evidence │ │ │ │ │ ├── engine/ │ │ │ ├── OnnxEmbeddingEngine.java # BGE-M3 ONNX 会话 │ │ │ ├── OnnxRerankerEngine.java # BGE-Reranker ONNX 会话 │ │ │ └── BertTokenizer.java # HuggingFace Tokenizer │ │ │ │ │ ├── dto/ │ │ │ ├── request/ │ │ │ └── response/ │ │ │ │ │ ├── enums/ │ │ │ ├── UnitType.java │ │ │ ├── KnowledgeUnitType.java │ │ │ └── ... │ │ │ │ │ └── config/ │ │ └── OnnxRuntimeConfig.java │ │ │ └── security/ # 共用鉴权(需补充倪海厦路径) │ ├── src/main/resources/ │ ├── application.yml │ ├── application-dev.yml │ ├── application-prod.yml │ ├── logback-spring.xml │ └── db/migration/ │ ├── V1__init_core.sql # 现有药典表 │ ├── V2__init_exam.sql │ ├── V3__init_nihaisha_extensions.sql # ★ pgvector + pg_trgm │ ├── V4__init_nihaisha_core.sql # ★ meta, documents, paragraphs │ ├── V5__init_nihaisha_retrieval.sql # ★ retrieval_units, vector_embeddings │ ├── V6__init_nihaisha_knowledge.sql # ★ knowledge_units, guide_nodes │ ├── V7__init_nihaisha_graph.sql # ★ evidence_records, entities, relations │ └── V8__init_nihaisha_fulltext.sql # ★ tsvector + trigram 索引 │ ├── models/ # ONNX 模型文件 │ ├── bge-m3-fp16/ │ │ ├── model.onnx │ │ └── tokenizer.json │ └── bge-reranker-fp16/ │ ├── model.onnx │ └── tokenizer.json │ └── Dockerfile # ★ 新增:应用容器化 ``` ### 3.2 ORM 选择:JPA + Native Query 向量搜索和全文搜索需要写原生 SQL,用 `@Query(nativeQuery=true)` 或 `JdbcTemplate`。药典项目已经在 `RetrieverService` 中这样做了(pgvector `<=>` 通过 JdbcTemplate 执行)。 ```java // NihVectorEmbeddingRepository.java public interface NihVectorEmbeddingRepository extends JpaRepository { // pgvector 近似搜索 — 原生 SQL @Query(value = """ SELECT ru.*, p.title, p.text, p.source_path, p.page_start, p.page_end, ve.embedding <=> CAST(:queryVector AS vector) AS distance FROM nih_vector_embeddings ve JOIN nih_retrieval_units ru ON ru.unit_id = ve.unit_id JOIN nih_paragraphs p ON p.paragraph_id = ru.paragraph_id ORDER BY ve.embedding <=> CAST(:queryVector AS vector) LIMIT :limit """, nativeQuery = true) List searchByVector(String queryVector, int limit); } ``` ### 3.3 表名前缀 为避免与药典现有表冲突,倪海厦表统一加 `nih_` 前缀: | 原始表名 | 实际表名 | |----------|----------| | `meta` | `nih_meta` | | `documents` | `nih_documents` | | `paragraphs` | `nih_paragraphs` | | `retrieval_units` | `nih_retrieval_units` | | `vector_embeddings` | `nih_vector_embeddings` | | `knowledge_units` | `nih_knowledge_units` | | `guide_nodes` | `nih_guide_nodes` | | `evidence_records` | `nih_evidence_records` | | `entities` | `nih_entities` | | `relations` | `nih_relations` | | `eval_cases` | `nih_eval_cases` | > 药典已有 `entities` 相关概念?检查后没有冲突,但仍加前缀隔离,便于未来分库。 --- ## 4. 部署拓扑 ### 4.1 单机部署(当前) ``` ┌─────────────────────────────────────────────────────────┐ │ 线上服务器 │ │ │ │ ┌──────────────────────────────────────────────────┐ │ │ │ Nginx (:80/:443) │ │ │ │ /api/v1/chat/* → localhost:9000 (药典对话) │ │ │ │ /api/v1/drug/* → localhost:9000 (药典药品) │ │ │ │ /api/v1/nihaisha/* → localhost:9000 (倪海厦 RAG) │ │ │ │ /api/v1/exam/* → localhost:9000 (药典考试) │ │ │ └──────────────────────┬───────────────────────────┘ │ │ │ │ │ ┌──────────────────────┴───────────────────────────┐ │ │ │ pharmacopoeia-ai.jar (:9000) │ │ │ │ │ │ │ │ ┌─────────────┐ ┌────────────────────────────┐ │ │ │ │ │ 药典服务 │ │ 倪海厦 RAG (nihaisha/) │ │ │ │ │ │ ├ Chat │ │ ├ Search (四通道) │ │ │ │ │ │ ├ Drug │ │ ├ Knowledge │ │ │ │ │ │ ├ Exam │ │ ├ Graph │ │ │ │ │ │ └ Admin │ │ ├ Evidence │ │ │ │ │ └──────┬───────┘ │ └───────────┬──────────────┘ │ │ │ │ │ │ │ │ │ │ │ ┌──────┴──────────┴─────────────┴──────────────┐ │ │ │ │ │ 共用基础设施 │ │ │ │ │ │ ├ Spring Security (JWT 鉴权) │ │ │ │ │ │ ├ Redis (QA 缓存 + Embedding 缓存) │ │ │ │ │ │ ├ Flyway (数据库迁移) │ │ │ │ │ │ ├ ONNX Runtime (BGE-M3 + Reranker) │ │ │ │ │ │ └ HikariCP (连接池) │ │ │ │ │ └────────────────────┬──────────────────────────┘ │ │ │ └───────────────────────┼────────────────────────────┘ │ │ │ │ │ ┌───────────────────────┴──────────────────────────┐ │ │ │ PostgreSQL 16 + pgvector │ │ │ │ ├ pharmacopoeia 库(药典: drugs, drug_chunks...)│ │ │ │ └ nih_* 表(倪海厦: 11 张表) │ │ │ └──────────────────────────────────────────────────┘ │ │ │ │ ┌──────────┐ ┌──────────┐ │ │ │ Redis 7 │ │ 模型文件 │ │ │ │ (缓存) │ │ bge-m3/ │ │ │ │ :6379 │ │ bge-reranker/ │ │ └──────────┘ └──────────┘ │ └─────────────────────────────────────────────────────────┘ │ │ HTTP (查询改写) ▼ ┌─────────────────────────┐ │ 本地 GPU 服务器 │ │ Ollama │ │ ├ qwen2.5:14b (主) │ │ └ :11434 │ └─────────────────────────┘ │ 降级 ▼ ┌─────────────────────────┐ │ 百炼 API (阿里云) │ │ qwen-plus (兜底) │ └─────────────────────────┘ ``` ### 4.2 Nginx 路由配置 ```nginx # 倪海厦 RAG API location /api/v1/nihaisha/ { proxy_pass http://127.0.0.1:9000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_read_timeout 60s; # 混合搜索可能较慢 } # 药典对话(现有,不变) location /api/v1/chat/ { proxy_pass http://127.0.0.1:9000; proxy_buffering off; # SSE 流式 proxy_read_timeout 300s; } ``` ### 4.3 资源配置 | 组件 | 内存 | 说明 | |------|------|------| | JVM 堆 | **3 GB**(原 2GB) | 新增 ONNX 模型 + 倪海厦检索 | | ONNX 模型 (FP16) | ~1.7 GB | BGE-M3 + Reranker 常驻内存 | | PostgreSQL | ~2 GB | shared_buffers | | Redis | ~0.5 GB | 缓存 | | OS | ~1 GB | | | **合计** | **~8 GB** | 线上服务器需 8-16 GB 内存 | --- ## 5. 水平扩容路径 ### 5.1 阶段 1:单体(当前 → 上线初期) ``` Nginx │ ┌───────────┴───────────┐ │ :9000 │ │ pharmacopoeia-ai.jar │ │ (药典 + 倪海厦) │ └───────────┬───────────┘ │ ┌───────────┴───────────┐ │ PostgreSQL (单机) │ └───────────────────────┘ ``` - **适用**:QPS < 100,数据量 < 100 万条 - **操作**:无需任何改动,合并代码部署即可 ### 5.2 阶段 2:读写分离(流量增长) ``` Nginx │ ┌───────────┴───────────┐ │ 应用服务器 × 2 │ │ (药典 + 倪海厦) │ │ :9000 :9000 │ └───────────┬───────────┘ │ ┌───────────────┼───────────────┐ │ │ │ ┌───────┴───────┐ ┌─────┴─────┐ ┌───────┴───────┐ │ PG 主库 (写) │ │ PG 从库 1 │ │ PG 从库 2 │ │ │ │ (只读) │ │ (只读) │ └───────────────┘ └───────────┘ └───────────────┘ ``` **Java 侧改动**:Spring 读写分离数据源配置 ```yaml spring: datasource: master: url: jdbc:postgresql://pg-master:5432/pharmacopoeia slaves: - url: jdbc:postgresql://pg-slave-1:5432/pharmacopoeia - url: jdbc:postgresql://pg-slave-2:5432/pharmacopoeia ``` - 搜索请求走从库(倪海厦 RAG 是读密集型) - 对话记录写入走主库 - **适用**:QPS 100-500,读多写少 ### 5.3 阶段 3:服务拆分 + 独立扩容 ``` Nginx / API Gateway │ ┌───────────────┼───────────────┐ │ │ │ ┌───────┴───────┐ ┌─────┴──────┐ ┌──────┴──────┐ │ 药典对话服务 │ │ 倪海厦 RAG │ │ 考试服务 │ │ × 2 实例 │ │ × 3 实例 │ │ × 1 实例 │ │ :9001 │ │ :9002 │ │ :9003 │ └───────┬───────┘ └─────┬──────┘ └──────┬──────┘ │ │ │ └───────────────┼───────────────┘ │ ┌───────────┴───────────┐ │ PostgreSQL 集群 │ │ + Redis 集群 │ └───────────────────────┘ ``` **拆分方式**(与当前单模块不冲突,渐进式演进): ``` backend-java/ # 当前单体 ├── pharmacopoeia-chat/ # 未来:拆为独立模块 ├── pharmacopoeia-exam/ # 未来:拆为独立模块 ├── nihaisha-rag/ # 未来:拆为独立模块 └── pharmacopoeia-common/ # 共用:entity, security, config ``` **拆分触发条件**: | 指标 | 阈值 | 说明 | |------|------|------| | 倪海厦 RAG QPS | > 200 | 单独扩容倪海厦实例 | | 药典对话 QPS | > 100 | 单独扩容药典实例 | | ONNX 模型内存压力 | JVM 堆 > 4GB | 倪海厦独立部署,独占 ONNX 模型 | | 数据库连接数 | > 80 | 加从库或分库 | ### 5.4 分库路径 倪海厦和药典的数据天然隔离(不跨库 join),随时可分库: ```yaml # 未来分库配置 spring: datasource: pharmacopoeia: url: jdbc:postgresql://pg-pharma:5432/pharmacopoeia nihaisha: url: jdbc:postgresql://pg-nihaisha:5432/nihaisha_rag ``` 当前阶段放在同一个 PostgreSQL 实例、同一个 database 内(`nih_` 前缀隔离),减少运维复杂度。 --- ## 6. 数据库规划 ### 6.1 当前数据库 ``` PostgreSQL 16 (pgvector) └── pharmacopoeia 库 ├── 药典表(现有 9 张) │ ├── conversations │ ├── messages │ ├── drugs │ ├── drug_chunks │ ├── users │ ├── knowledge_points │ ├── questions │ ├── answer_records │ └── user_progress │ └── 倪海厦表(新增 11 张,nih_ 前缀) ├── nih_meta ├── nih_documents ├── nih_paragraphs ├── nih_retrieval_units ├── nih_vector_embeddings # pgvector 向量 ├── nih_knowledge_units ├── nih_guide_nodes ├── nih_evidence_records ├── nih_entities ├── nih_relations └── nih_eval_cases ``` ### 6.2 存储增量 | 数据 | 磁盘占用 | |------|----------| | 药典现有数据 | ~2 GB | | 倪海厦段落 + 检索单元 | ~3 GB | | 倪海厦向量 (1024维 × 50万) | ~2 GB | | 倪海厦索引 (HNSW + GIN + trigram) | ~2 GB | | **总计(新增)** | **~7 GB** | | **数据库总大小** | **~9 GB** | --- ## 7. API 设计 ### 7.1 路径规划 | 领域 | 路径前缀 | 鉴权 | |------|----------|:---:| | 药典对话 | `/api/v1/chat/**` | ✅ JWT | | 药典药品 | `/api/v1/drug/**` | ✅ JWT | | 药典考试 | `/api/v1/exam/**` | ✅ JWT | | 药典管理 | `/api/v1/admin/**` | ✅ JWT + admin-token | | 药典快问 | `/api/v1/yaodian/**` | ❌ 公开 | | **倪海厦搜索** | **`/api/v1/nihaisha/search/**`** | **✅ JWT** | | **倪海厦知识** | **`/api/v1/nihaisha/knowledge/**`** | **✅ JWT** | | **倪海厦导航** | **`/api/v1/nihaisha/guide/**`** | **✅ JWT** | | **倪海厦图谱** | **`/api/v1/nihaisha/graph/**`** | **✅ JWT** | | **倪海厦证据** | **`/api/v1/nihaisha/evidence/**`** | **✅ JWT** | | 健康检查 | `/health` | ❌ 公开 | ### 7.2 Security 配置补充 在现有 `SecurityConfig` 中补充倪海厦路径: ```java // 现有公开路径 "/health", "/api/v1/auth/**", "/api/v1/yaodian/**", "/static/**" // 倪海厦搜索暂不单独公开,走 JWT 鉴权(与药典对话一致) // 所有 /api/v1/nihaisha/** 需要 Bearer Token ``` ### 7.3 前端问答框模型选择 前端需要一个问答框,支持选择模型。在现有药典对话系统基础上扩展: ``` POST /api/v1/nihaisha/search/hybrid Authorization: Bearer Request: { "query": "桂枝汤的组成和用法", "model": "nihaisha", // ★ 新增字段 "channels": ["vector", "text", "knowledge", "graph"], "limit": 20 } Response: { "results": [...], "tookMs": 380, "model": "nihaisha" // 回显模型选择 } ``` 前端对话页可以加一个下拉选择: - "药典助手" → 走 `/api/v1/chat/stream`(现有) - "倪海厦中医" → 走 `/api/v1/nihaisha/search/hybrid`(新增) --- ## 附录 A:pom.xml 新增依赖 在现有 `backend-java/pom.xml` 中追加: ```xml com.microsoft.onnxruntime onnxruntime 1.18.0 com.huggingface tokenizers 0.21.0 ``` > 不需要加 MyBatis 依赖,统一用 JPA;不需要加 Flyway 依赖,药典项目已有。 --- ## 附录 B:扩容决策树 ``` QPS < 100 且 内存 < 8GB? ├── 是 → 阶段 1:单体,不做任何拆分 └── 否 → 倪海厦 RAG QPS > 200? ├── 是 → 阶段 3:拆分倪海厦为独立服务 └── 否 → 数据库 CPU > 70%? ├── 是 → 阶段 2:加 PG 从库,读写分离 └── 否 → 阶段 2:加应用实例 + 负载均衡 ``` --- > **文档版本**: v1.0 | **下一步**: 确认方案后开始写代码