MedMind RAG:为什么我在医疗场景用 GraphRAG 做知识扩展
写在前面
做 MedMind 这个项目之前,我对 RAG 的理解停留在“把文档切块、向量化、检索、生成”这套流程。
这套流程在菜谱问答那个项目里跑得挺顺,但切到医疗场景之后,我发现一个问题一直解决不好:用户问的是感冒,但他真正需要的答案里还涉及发热处理和布洛芬用法,而这两块知识在知识库里是三个独立的条目。
单纯靠相似度检索,召回的永远是最像“感冒”的那几条,关联知识捞不出来。
这篇文章记录我为什么引入 GraphRAG,怎么设计的,以及踩了哪些坑。
说明:这个项目只用于医疗知识问答和 RAG 检索能力演示,不替代医生诊断,也不提供个人用药建议。
问题是怎么暴露的
MedMind 的知识库按疾病/药物组织,每个条目是一个独立的 chunk。比如:
内科-感冒:病因、症状、治疗原则内科-发热:分级、处理方法、就医指征药学-布洛芬:适应症、用法用量、注意事项
用户问“感冒了发烧怎么办”,理想的答案需要同时覆盖这三块内容。
但实际检索结果是:相似度最高的永远是 内科-感冒 这条,内科-发热 勉强能召回,药学-布洛芬 基本捞不到,因为用户的问题里没有出现“布洛芬”这个词。
多查询扩展能部分缓解这个问题,但治标不治本。用户问的是症状,不是药名,扩展出来的变体也大概率不包含具体药物名称。
为什么想到用图谱
医学知识本身就是有结构的:
感冒 -> 常见症状:发热 -> 退热用药:布洛芬 / 对乙酰氨基酚
这种关联关系不是语义相似,是明确的领域知识。向量检索擅长处理语义相似,但处理不了这种“A 会导致 B,B 的处理需要 C”的推理链。
图谱天然适合表达这种关系。
怎么实现的
我用 NetworkX 建了一个有向图,节点是知识库里的每个条目,边是条目之间的关联关系。
关联关系是我手动定义的,按照临床逻辑梳理:
1 | |
图谱扩展的逻辑很简单:混合检索拿到初始候选集之后,对每个召回节点找它在图上的邻居节点,把邻居对应的知识条目也加进候选集,再一起进 LLM 重排序。
1 | |
整个扩展在重排序之前完成,扩展进来的条目会和原始召回一起打分,由 LLM 决定最终保留哪几条。
效果怎么样
以“感冒发烧怎么办”为例,加图谱扩展之前:
| 召回顺序 | 条目 |
|---|---|
| 1 | 内科-普通感冒 |
| 2 | 内科-流行性感冒 |
| 3 | 内科-咳嗽 |
加图谱扩展之后,候选集里多了:
内科-发热(感冒的常见并发症)药学-布洛芬(发热的退热用药)药学-对乙酰氨基酚(发热的退热用药)
经过 LLM 重排序,最终进入生成阶段的是感冒 + 发热 + 布洛芬三条,回答的完整度明显提升。
踩过的坑
图谱规模不能太大。 我一开始想把所有疾病关联都建进去,结果扩展出来的候选集太大,噪音也多,重排序的负担变重,反而影响了精度。后来控制在每次最多扩展 3 个节点,效果更稳。
关联关系要靠谱。 手动定义关系的好处是可控,坏处是要花时间梳理。我参考了内科、急诊科的临床路径,把“常见并发症”“首选用药”“鉴别诊断”这几类关系单独区分,避免把不相关的条目扯进来。
图谱扩展不能替代检索。 有几次我测试“布洛芬和对乙酰氨基酚有什么区别”这类直接问药的问题,图谱扩展帮不上什么忙,还是要靠 BM25 的关键词匹配把药名精确召回。两者是互补关系,不是替代关系。
现在的状态
目前知识库覆盖 8 个科室,53 个知识条目,图谱有 31 个节点、28 条关联边。
GraphRAG 这块还有很多可以做的,比如关联关系自动抽取(现在是手动定义)、多跳推理的深度控制、图谱和向量的联合评分。这些留着后面有时间继续探索。
项目 demo 部署在博客上,感兴趣可以直接体验:zxyblog.top/medical-rag