版本: v1.0 日期: 2026-07-28 目标: 将倪海厦 RAG 代码合并到药典助手项目
D:\project\AIyaodianzhushou,分析部署方案和扩容路径
| 维度 | 药典助手 (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、两套部署 |
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 迁移(新增倪海厦表)
| 维度 | 用 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 的)完全够用。
| 共用 | 独立 |
|---|---|
| Spring Security + JWT 鉴权 | ONNX Runtime 引擎(BGE-M3 + Reranker) |
| Redis 缓存 | 数据层(独立的表,不跨库 join) |
| HikariCP 连接池 | Controller(独立路径 /api/v1/nihaisha/) |
| Flyway 迁移 | 100 MB 日志文件(追加到 logback) |
| 日志框架 (Logback) | |
| 同一个 PostgreSQL 实例 | |
| 同一个 Nginx 反向代理 | |
| 同一个 supervisord 进程 |
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 # ★ 新增:应用容器化
向量搜索和全文搜索需要写原生 SQL,用 @Query(nativeQuery=true) 或 JdbcTemplate。药典项目已经在 RetrieverService 中这样做了(pgvector <=> 通过 JdbcTemplate 执行)。
// NihVectorEmbeddingRepository.java
public interface NihVectorEmbeddingRepository extends JpaRepository<NihVectorEmbedding, String> {
// 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<Object[]> searchByVector(String queryVector, int limit);
}
为避免与药典现有表冲突,倪海厦表统一加 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相关概念?检查后没有冲突,但仍加前缀隔离,便于未来分库。
┌─────────────────────────────────────────────────────────┐
│ 线上服务器 │
│ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ 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 (兜底) │
└─────────────────────────┘
# 倪海厦 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;
}
| 组件 | 内存 | 说明 |
|---|---|---|
| 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 内存 |
Nginx
│
┌───────────┴───────────┐
│ :9000 │
│ pharmacopoeia-ai.jar │
│ (药典 + 倪海厦) │
└───────────┬───────────┘
│
┌───────────┴───────────┐
│ PostgreSQL (单机) │
└───────────────────────┘
Nginx
│
┌───────────┴───────────┐
│ 应用服务器 × 2 │
│ (药典 + 倪海厦) │
│ :9000 :9000 │
└───────────┬───────────┘
│
┌───────────────┼───────────────┐
│ │ │
┌───────┴───────┐ ┌─────┴─────┐ ┌───────┴───────┐
│ PG 主库 (写) │ │ PG 从库 1 │ │ PG 从库 2 │
│ │ │ (只读) │ │ (只读) │
└───────────────┘ └───────────┘ └───────────────┘
Java 侧改动:Spring 读写分离数据源配置
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
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 | 加从库或分库 |
倪海厦和药典的数据天然隔离(不跨库 join),随时可分库:
# 未来分库配置
spring:
datasource:
pharmacopoeia:
url: jdbc:postgresql://pg-pharma:5432/pharmacopoeia
nihaisha:
url: jdbc:postgresql://pg-nihaisha:5432/nihaisha_rag
当前阶段放在同一个 PostgreSQL 实例、同一个 database 内(nih_ 前缀隔离),减少运维复杂度。
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
| 数据 | 磁盘占用 |
|---|---|
| 药典现有数据 | ~2 GB |
| 倪海厦段落 + 检索单元 | ~3 GB |
| 倪海厦向量 (1024维 × 50万) | ~2 GB |
| 倪海厦索引 (HNSW + GIN + trigram) | ~2 GB |
| 总计(新增) | ~7 GB |
| 数据库总大小 | ~9 GB |
| 领域 | 路径前缀 | 鉴权 |
|---|---|---|
| 药典对话 | /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 |
❌ 公开 |
在现有 SecurityConfig 中补充倪海厦路径:
// 现有公开路径
"/health", "/api/v1/auth/**", "/api/v1/yaodian/**", "/static/**"
// 倪海厦搜索暂不单独公开,走 JWT 鉴权(与药典对话一致)
// 所有 /api/v1/nihaisha/** 需要 Bearer Token
前端需要一个问答框,支持选择模型。在现有药典对话系统基础上扩展:
POST /api/v1/nihaisha/search/hybrid
Authorization: Bearer <jwt>
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(新增)在现有 backend-java/pom.xml 中追加:
<!-- ONNX Runtime — BGE-M3 Embedding + Reranker 本地推理 -->
<dependency>
<groupId>com.microsoft.onnxruntime</groupId>
<artifactId>onnxruntime</artifactId>
<version>1.18.0</version>
</dependency>
<!-- HuggingFace Tokenizers — 加载 tokenizer.json(Rust JNI) -->
<dependency>
<groupId>com.huggingface</groupId>
<artifactId>tokenizers</artifactId>
<version>0.21.0</version>
</dependency>
不需要加 MyBatis 依赖,统一用 JPA;不需要加 Flyway 依赖,药典项目已有。
QPS < 100 且 内存 < 8GB?
├── 是 → 阶段 1:单体,不做任何拆分
└── 否 → 倪海厦 RAG QPS > 200?
├── 是 → 阶段 3:拆分倪海厦为独立服务
└── 否 → 数据库 CPU > 70%?
├── 是 → 阶段 2:加 PG 从库,读写分离
└── 否 → 阶段 2:加应用实例 + 负载均衡
文档版本: v1.0 | 下一步: 确认方案后开始写代码