日期: 2026-07-20
分支: main
编译状态: ✅ BUILD SUCCESS (JDK 21, 64 source files)
变更范围: 7 个文件修改 + 1 个新建文件
项目存在两个并行后端(Python FastAPI 和 Java Spring Boot),共享 PostgreSQL 数据库。经过逐文件、逐端点的全面对比(65 个 Java 文件 vs 18 个 Python 文件),Java 端已覆盖约 95% 的功能。本次变更补齐剩余 5% 的差距,使 Java 后端可完全替代 Python 后端。
RerankerService.javabackend-java/src/main/java/com/pharmacopoeia/service/RerankerService.javaChatController 中提取,融入 Python reranker.py 的全部技术核心功能:
MIN_SIMILARITY_THRESHOLD = 0.3)
对应 Python 参考: backend-python/app/rag/reranker.py
ChatController.javabackend-java/src/main/java/com/pharmacopoeia/controller/ChatController.java变更点:
| 变更 | 说明 |
|---|---|
注入 RerankerService |
构造函数新增参数,替换内联 rerank() 方法 |
新增 SECTION_DISPLAY 常量 |
栏目中文名映射,用于 buildSources() 中格式化 section |
/ask 响应新增 conversation_id |
匹配 Python return ChatResponse(conversation_id=...) |
/ask-image 响应新增 conversation_id |
同上 |
/ask-multimodal 响应新增 conversation_id |
同上 |
/stream meta 事件新增 cid |
匹配 Python {"intent": ..., "sources": ..., "cid": ...} |
/stream-image meta 事件新增 cid |
同上 |
/stream-multimodal meta 事件新增 cid |
同上 |
新增 POST /upload-image |
multipart 文件上传 → base64 → 委托给 chatAskImage() |
新增 POST /upload-media |
multipart 文件上传 → 自动识别 image/video → 委托给 chatAskMultimodal() |
移除旧 rerank() 方法 |
逻辑已迁移至 RerankerService |
移除旧 toDouble() 方法 |
同上 |
buildSources() 增强 |
优先使用 DB JOIN 元数据(drug name, source_version, source_volume),回退到内容解析 |
新增端点详情:
POST /api/v1/chat/upload-image:
file (MultipartFile), message (可选), conversationId (可选)ImageChatRequest → 调用 chatAskImage()POST /api/v1/chat/upload-media:
MultimodalChatRequest → 调用 chatAskMultimodal()对应 Python 参考: backend-python/app/api/chat.py:301-330 (upload-image), :475-511 (upload-media)
ChatPersistenceService.javabackend-java/src/main/java/com/pharmacopoeia/service/ChatPersistenceService.java| 变更前 | 变更后 |
|---|---|
c.getTitle() != null ? c.getTitle() : "" |
c.getTitle() != null && !c.getTitle().isBlank() ? c.getTitle() : "新的对话" |
空标题或无标题对话统一显示为 "新的对话",匹配 Python chat.py:543 的行为。
DrugService.javabackend-java/src/main/java/com/pharmacopoeia/service/DrugService.javagetCategoryTree() 方法重写| 变更前 | 变更后 |
|---|---|
返回 List<String> 扁平分类列表 |
返回 List<Map<String, Object>> 层次化树 [{name, children}] |
{"categories": ["化学药", "中药"]} |
{"tree": [{"name": "化学药", "children": ["抗生素", ...]}]} |
匹配 Python drug.py:112-127 的返回格式。
DrugRepository.javabackend-java/src/main/java/com/pharmacopoeia/repository/DrugRepository.java变更: 新增 1 个查询方法
@Query("SELECT DISTINCT d.category, d.subcategory FROM Drug d WHERE d.category IS NOT NULL ORDER BY d.category, d.subcategory")
List<Object[]> findCategorySubcategoryPairs();
为层次化分类树提供 category + subcategory 配对查询。
DrugController.javabackend-java/src/main/java/com/pharmacopoeia/controller/DrugController.java| 变更前 | 变更后 |
|---|---|
Map.of("categories", ...) |
Map.of("tree", ...) |
返回键名从 categories 改为 tree,匹配 Python 响应格式。
AdminController.javabackend-java/src/main/java/com/pharmacopoeia/controller/AdminController.javaGET /api/v1/admin/stats 新增 3 个字段:
| 字段 | 说明 | 数据来源 |
|---|---|---|
top_drugs |
近 30 天查询最多的 10 个药品 | messages JOIN drug_chunks JOIN drugs |
daily_queries |
近 7 天每日查询量 | messages 按天 GROUP BY |
avg_response_time_ms |
平均响应时间(Phase 2 实现) | 当前固定返回 0 |
匹配 Python admin.py:9-16 的响应字段。
application.ymlbackend-java/src/main/resources/application.yml变更: +5 行
spring:
servlet:
multipart:
max-file-size: 50MB
max-request-size: 55MB
支持文件上传端点(图片最大 10MB,视频最大 50MB)。
| 端点 | Python | Java(变更后) |
|---|---|---|
POST /ask |
✅ conversation_id in response | ✅ 已添加 |
POST /stream |
✅ cid in meta | ✅ 已添加 |
POST /ask-image |
✅ conversation_id in response | ✅ 已添加 |
POST /stream-image |
✅ cid in meta | ✅ 已添加 |
POST /ask-multimodal |
✅ conversation_id in response | ✅ 已添加 |
POST /stream-multimodal |
✅ cid in meta | ✅ 已添加 |
POST /upload-image |
✅ multipart file upload | ✅ 新增 |
POST /upload-media |
✅ multipart file upload | ✅ 新增 |
GET /history |
✅ title fallback "新的对话" | ✅ title fallback "新的对话" |
GET /history/{cid} |
✅ | ✅ |
POST /feedback |
✅ | ✅ |
GET /admin/conversations |
✅ | ✅ |
| 端点 | Python | Java(变更后) |
|---|---|---|
GET /search |
✅ total_pages | ✅ 已有 |
GET /{drug_id} |
✅ sections as dict | ✅ 已有 |
GET /category/tree |
✅ {"tree": [{name, children}]} |
✅ 已修改 |
| 端点 | Python | Java(变更后) |
|---|---|---|
GET /stats |
✅ top_drugs, daily_queries, avg_response_time_ms | ✅ 已添加 |
GET /admin/knowledge/stats |
✅ | ✅ (Java 更完整) |
| 端点 | Python | Java |
|---|---|---|
| 全部 CRUD | Stub ("数据入库后可用") | ✅ 完整 DB 实现 |
| 端点 | Python | Java |
|---|---|---|
| 全部端点 | Stub | Stub (同等) |
| 端点 | Python | Java |
|---|---|---|
| 全部端点 | ✅ | ✅ (同等) |
| 组件 | Python | Java(变更后) |
|---|---|---|
| 意图分类 | classify_intent() 5 意图 |
classifyIntent() 5 意图 (同等) |
| 向量检索 | pgvector <=> |
pgvector <=> + drug name 精确匹配 (更优) |
| Embedding | DashScope text-embedding-v3 | DashScope text-embedding-v3 (同等) |
| Reranker | 阈值过滤 + n-gram 加权 + Jaccard 去重 | ✅ 已补齐 + section 加权 + 版本优先级 (更优) |
| Prompt | 6 意图模板 | 6 意图模板 (同等) |
| LLM Chat | Qwen 非流式/流式 | Qwen 非流式/流式 (同等) |
| VL 图片分析 | Qwen VL | Qwen VL (同等) |
| 视频分析 | Qwen VL | Qwen VL (同等) |
| 联网搜索 | enable_search | enable_search (同等) |
| 文件 | 条数 | 格式 | 可导入 |
|---|---|---|---|
wiki_merged.json |
13 | 临床专著(概述/适应症/药理...) | ✅ |
sample_drugs.json |
4 | 药典格式(性状/鉴别/检查...) | ✅ |
drug_guides.json |
4 | 用药指导 | ✅ |
drug_index.json |
1531 | 仅索引(药名+卷号+页码) | ❌ |
wiki_merged.json 包含的药品甲硝唑(24节)、布洛芬(12节)、对乙酰氨基酚(13节)、阿莫西林(10节)、二甲双胍(20节)、地西泮(4节)、呋塞米(10节)、缬沙坦(7节)、厄贝沙坦(5节)、头孢克洛(9节)、螺内酯(3节)、瑞舒伐他汀(10节)、乙胺嘧啶(16节)
甲硝唑 (24 sections):
├── 概述 ← 用户需求
├── 适应症 ← 用户需求
├── 细菌性阴道炎
├── 滴虫病
├── 梨形鞭毛虫病
├── 麦地那龙线虫病
├── 艰难梭菌结肠炎
├── 痢疾阿米巴
├── 早产
├── 缺氧放射增敏剂
├── 口周皮肤炎
├── 不良反应
├── 诱变性与致癌性
├── 史蒂芬斯-强森症候群
├── 神经毒性
├── 酒精
├── 药物相互作用
├── 作用机理
├── 药理 ← 用户需求
├── 耐药性
├── 历史
├── 品牌名称
├── 合成
└── 研究
源目录: 开发环境 D:\lcswork\202607\2025\,线上环境 /opt/2025
| 卷 | 子目录 | DOCX 数量 | 分类 |
|---|---|---|---|
| 一部 (output) | 成方制剂和单味制剂 | 1613 | 中药 |
| 药材和饮片 | 616 | 中药 | |
| 植物油脂和提取物 | 47 | 中药 | |
| 凡例/纲要 | 19+5 | 凡例纲要 | |
| 二部 (output2) | 品种正文 | 2930 | 化学药(含布洛芬、甲硝唑等) |
| 凡例/纲要 | 9 | 凡例纲要 | |
| 三部 (output3) | 各论/通则等 | 217+1+1 | 生物制品 |
| 四部 (output4) | 通用技术要求/指导原则 | 473 | 通则 |
| 药用辅料 | 387 | 辅料 | |
| 合计 | 6317 |
已确认存在的药品文件示例:
output2/品种正文/布洛芬.docxoutput2/品种正文/布洛芬缓释胶囊.docx ← 用户搜索目标output2/品种正文/布洛芬片.docxoutput2/品种正文/布洛芬糖浆.docxoutput2/品种正文/甲硝唑.docx (9 个相关文件)output2/品种正文/阿莫西林.docx (12 个相关文件)搜索请求: "布洛芬缓释胶囊"
│
▼ RetrieverService.search()
│ • classifyIntent() → "drug_query"
│ • embed(query) → DashScope API
│ • pgvector <=> 向量相似度检索
│ • extractDrugName("布洛芬缓释胶囊") → 查 drugs 表
│
▼ 结果: 空 / 无匹配
原因: 数据库中没有 drug_chunks 数据
三层根因:
| 层级 | 问题 | 详情 |
|---|---|---|
| 🔴 L1 | PostgreSQL 未运行 | Connection refused (localhost:5432) |
| 🔴 L2 | 6317 个药典 DOCX 未导入 | 数据在磁盘,未被 docx_ingest.py 学习 |
| 🔴 L3 | 13 个 Wiki 药品未导入 | wiki_merged.json 未被 import_all.py 导入 |
注意: 即使解决了 L1+L2,"布洛芬缓稀胶囊"(用户可能打错字:"稀"→应为"释")也不一定命中。当前 RetrieverService.extractDrugName() 做精确/前缀匹配,不做模糊纠错。这是后续优化项。
# 确保环境变量
export QWEN_API_KEY="你的DashScope API Key"
export POSTGRES_HOST=localhost
export POSTGRES_PORT=5432
export POSTGRES_DB=pharmacopoeia
export POSTGRES_USER=postgres
export POSTGRES_PASSWORD=pharma2025
cd /path/to/project/deploy
docker-compose up -d
# 确认 PG 已启动
pg_isready -h localhost -p 5432
psql -h localhost -U postgres -d pharmacopoeia -f database/schema.sql
cd data-pipeline
python import_all.py
预计耗时:约 1-2 分钟(13 个 Wiki + 4 个 sample + 4 个 guides,约 200 次 Embedding API 调用)
# 线上环境(默认路径)
python docx_ingest.py
# 开发环境(Windows 本地)
set DOCX_SOURCE_DIR=D:\lcswork\202607\2025
python docx_ingest.py
预计耗时:6317 文件 × 5-10 section → 3-6 万 chunk → 约 3000-6000 次 Embedding API(批量 10 条/次),需要数小时
线上路径
/opt/2025是脚本默认值,无需额外设置
# 确认数据入库
psql -h localhost -U postgres -d pharmacopoeia -c "
SELECT COUNT(*) AS drugs FROM drugs WHERE is_active=TRUE;
SELECT COUNT(*) AS chunks FROM drug_chunks WHERE vec IS NOT NULL;
SELECT name, source_version FROM drugs WHERE name LIKE '%布洛芬%';
"
export JAVA_HOME=/path/to/jdk-21
cd backend-java
mvn spring-boot:run
# 测试: curl "http://localhost:9000/api/v1/chat/ask" \
# -H "Content-Type: application/json" \
# -d '{"message":"布洛芬缓释胶囊的用法用量"}'
$ export JAVA_HOME="D:/programfiles/jdk-21.0.2"
$ cd backend-java && mvn compile
[INFO] Compiling 64 source files with javac [debug parameters release 21] to target/classes
[INFO] BUILD SUCCESS
[INFO] Total time: 5.756 s
无新增警告或错误。唯一的 warning 是 Drug.java:36 的 @Builder 注解,属于已有代码。
RetrieverService.extractDrugName() 增加编辑距离/拼音容错(如"缓稀"→"缓释")