医疗知识RAG架构与边界
从零设计医疗知识 RAG:先划清边界,再谈技术选型
摘要:医疗知识问答最容易犯的错误,是把“模型能回答”误认为“系统可信”。本文从数据、检索、生成和审计四个层面,拆解一套可落地的 RAG 架构,并说明哪些问题不应该交给 RAG。
标签:RAG 医疗信息化 大模型 知识库 系统架构
一、为什么医疗知识场景更需要 RAG
通用大模型拥有较强的语言理解能力,但它的训练数据存在时间边界,也无法天然掌握机构内部制度、药品目录、编码规则和本地业务口径。RAG(Retrieval-Augmented Generation,检索增强生成)的核心思路,是在生成答案前先从可信知识库检索相关材料,再让模型依据材料组织答案。
它解决的不是“让模型知道更多”,而是三个更实际的问题:
- 知识可更新:指南或制度变化时,更新知识库即可,不必重新训练模型。
- 答案可追溯:回答能够附带出处、版本和页码,便于复核。
- 权限可控制:不同角色只检索自己有权查看的内容。
但要注意,RAG 不能自动保证答案正确。检索错误、文档过期、切分不合理或模型忽略证据,都会形成“有引用的错误答案”。
二、一套实用的四层架构
1. 数据层:先治理,再入库
知识来源可以包括诊疗指南、医保政策、院内制度、编码规则和产品说明书。入库时至少保存以下元数据:
{
"title": "文档标题",
"source": "发布机构",
"version": "版本号",
"effective_date": "生效日期",
"page": 12,
"department": "适用部门",
"security_level": "权限级别"
}
正文只是知识的一部分,版本、时效和权限同样重要。若只保存纯文本,后期很难回答“这条结论来自哪个版本”“是否仍然有效”。
2. 检索层:关键词与语义检索并用
医疗术语中有大量缩写、编码和近义表达。例如同一个概念可能同时出现中文名、英文名、缩写和编码。单纯依赖向量相似度,容易漏掉精确编码;单纯依赖关键词,又难以理解自然语言问题。
比较稳妥的方式是混合检索:
- BM25 负责精确术语、药名、编码和数字;
- 向量检索负责语义相近的表达;
- 重排模型对候选片段再次排序;
- 元数据过滤负责版本、时间、科室和权限。
3. 生成层:把“依据证据回答”写进流程
提示词不应只写“请准确回答”,而要明确约束:只能依据给定材料;材料不足时必须说明;重要结论需要引用编号;不同材料冲突时展示冲突而不是强行合并。
推荐让模型输出结构化结果:
{
"answer": "回答正文",
"citations": ["doc-12#page-8"],
"confidence": "medium",
"missing_information": []
}
结构化输出便于前端展示,也方便服务端校验“引用是否真实存在”。
4. 审计层:记录一次回答是怎样产生的
一次完整调用应记录问题、用户角色、检索条件、候选片段、重排结果、提示词版本、模型版本和最终输出。审计日志不等于无限期保存原始敏感内容,应根据数据分级做脱敏、加密和保留周期控制。
三、知识入库并不是一次性的“导文件”
不少 RAG 项目的第一版会写一个脚本,把 PDF 转成文本、切块、计算向量,然后批量写入向量库。这个流程可以完成演示,但离可维护系统还有很大距离。生产环境中的知识入库至少应包含以下阶段:
文件接收
→ 格式与病毒检查
→ 文本/表格/图片解析
→ 结构恢复与章节识别
→ 元数据补全
→ 内容切分
→ 质量校验
→ 向量与关键词索引
→ 抽样复核
→ 发布生效
每一个阶段都可能失败,而且失败后应该能够从当前阶段重试,而不是重新处理全部文件。可以为文档设置 received、parsed、indexed、published、failed 等状态,并记录失败原因。
表格是医疗知识解析中的高风险区域。若解析器把表头与单元格拆散,模型看到的数字虽然都存在,却无法知道它们之间的对应关系。对于关键表格,建议同时保存结构化表示和适合阅读的 Markdown 表格;对于扫描版文档,则需要将 OCR 置信度纳入质量检查。
文档更新时也不要直接覆盖旧版本。更稳妥的做法是先建立新版本索引,完成抽样和回归测试后再切换“当前生效版本”。旧版本保留只读状态,用于解释历史回答,但默认检索只查询最新有效版本。
四、查询理解:不是所有问题都走同一条链路
用户问题可以先做轻量分类,再选择不同策略:
- 精确查询:包含编码、药名或明确条款编号,增加关键词检索权重;
- 解释查询:询问概念、原因或流程,增加语义检索权重;
- 比较查询:需要同时检索多个对象并分别保留证据;
- 时效查询:强制过滤生效时间,并优先展示版本信息;
- 越权或高风险查询:不进入普通生成链路,直接触发安全策略;
- 闲聊或知识库外问题:明确告知系统能力边界。
查询改写可以帮助补全缩写或同义词,但必须保留原问题。错误改写有可能改变用户意图,因此检索日志中应同时记录原问题、改写结果和最终使用的查询条件。
例如用户询问“某编码今年还能不能用”,系统真正需要的不是生成一段编码介绍,而是识别出三个条件:具体编码、当前年度和有效状态。只有完成条件提取,后续检索才有意义。
五、冲突知识如何处理
知识库中经常同时存在国家规范、地区政策、院内制度和历史版本。简单地选择相似度最高的片段,会把“适用范围”问题变成随机排序问题。
可以为文档定义优先级,但优先级不应只依赖来源名称,还需要结合问题的业务范围。例如院内操作流程应优先使用当前院内制度,通用医学知识则应优先使用权威指南。若两份同级材料互相冲突,系统应展示来源和版本差异,并提示人工确认,而不是让模型自行裁决。
一种实用做法是让检索结果先按以下维度分组:
- 是否在有效期内;
- 是否匹配用户所在机构和业务范围;
- 来源权威级别;
- 文档版本;
- 与问题的文本相关度。
相关度不应凌驾于有效性和适用范围之上。
六、索引结构:正文、向量和治理字段要放在一起
下面是一份简化的 Elasticsearch Mapping。重点不是字段名,而是把可检索内容与版本、权限、时效字段同时建模:
PUT medical_knowledge_v1
{
"mappings": {
"properties": {
"chunk_id": { "type": "keyword" },
"document_id": { "type": "keyword" },
"title": { "type": "text", "fields": { "raw": { "type": "keyword" } } },
"heading_path": { "type": "keyword" },
"content": { "type": "text", "analyzer": "standard" },
"content_vector": { "type": "dense_vector", "dims": 1024, "index": true,
"similarity": "cosine" },
"version": { "type": "keyword" },
"effective_from": { "type": "date" },
"effective_to": { "type": "date" },
"tenant_id": { "type": "keyword" },
"security_tags": { "type": "keyword" },
"source_uri": { "type": "keyword", "index": false }
}
}
}
chunk_id 应稳定且可重建,例如 document_id + version + page + chunk_no。不要直接使用数据库自增 ID 作为业务引用,否则重新索引后引用会整体变化。
向量维度必须与 Embedding 模型一致。更换模型时不要在原字段上混写新旧向量,应新建索引或新字段,完成双写、回归评测和别名切换后再下线旧版本。
七、一个可测试的检索服务接口
检索层最好独立于大模型,先返回可解释的候选:
from dataclasses import dataclass
from datetime import date
@dataclass(frozen=True)
class RetrievalContext:
tenant_id: str
security_tags: frozenset[str]
as_of: date
@dataclass(frozen=True)
class Hit:
chunk_id: str
content: str
title: str
version: str
score: float
retrieval_source: str # lexical / vector / both
async def retrieve(question: str, ctx: RetrievalContext, top_k: int = 20) -> list[Hit]:
"""先权限过滤,再并行召回,最后融合;不在此处调用生成模型。"""
lexical, semantic = await run_parallel_retrievers(question, ctx)
fused = reciprocal_rank_fusion(lexical, semantic, rank_constant=60)
return fused[:top_k]
这样可以单独为 retrieve() 编写检索回归测试,也能在模型不可用时降级为“只展示相关文档”。生成层只接收已经授权、排序和裁剪后的 Hit。
上下文构建时可以给每个片段分配引用序号:
[S1] 标题:……;版本:……;页码:……
片段正文……
[S2] 标题:……;版本:……;页码:……
片段正文……
模型只允许返回 S1、S2 这样的局部引用。服务端再将它们映射回真实 chunk_id,并拒绝任何不在候选集合中的引用。
八、哪些问题不应直接交给 RAG
RAG 适合知识检索、制度解释、编码辅助和材料摘要,但不应单独承担以下任务:
- 无人工复核的诊断或治疗决策;
- 自动替代医生完成高风险处方;
- 在证据不足时推断患者个体结论;
- 绕过权限汇总敏感数据;
- 对相互冲突的政策给出唯一确定结论。
这些边界最好在产品层面实现,而不是只依赖一句提示词。例如高风险问题触发固定提示、限制输出形式,并进入人工复核流程。
九、如何验证第一版是否真的可用
第一版上线前,可以准备一套小而精的验证集。问题不必很多,但应覆盖不同类型:能够直接找到答案的问题、需要组合两段证据的问题、知识库没有答案的问题、文档版本冲突的问题,以及明确不应回答的高风险问题。
评测时把“检索”和“生成”分开:
- 如果正确片段没有进入 Top 20,优先改切分、索引或检索;
- 如果正确片段已进入 Top 5,但答案仍错误,检查提示词、上下文顺序和生成模型;
- 如果答案正确但引用错误,修复引用生成与服务端校验;
- 如果无答案问题仍被强行回答,增加拒答判定和最低相关度门槛。
同时观察响应时间和成本。一个答案准确但需要一分钟才能返回的系统,在真实工作流中往往很难被使用。可以将检索、重排和生成耗时分别记录,找到真正的瓶颈。
十、上线顺序比模型大小更重要
建议先选择一个范围清晰、资料稳定、可人工验证的场景,例如院内制度问答。第一阶段只做检索和引用展示;第二阶段再增加答案生成;第三阶段才接入权限、反馈闭环和自动评测。
一个可信的医疗知识 RAG,不是“向量数据库 + 大模型”的简单拼接,而是数据治理、检索策略、生成约束和审计机制共同构成的系统。模型决定表达能力,工程流程决定它能否被安全使用。
说明:本文讨论的是知识系统设计,不构成医疗建议。示例数据均为结构演示,不包含真实患者信息。

浙公网安备 33010602011771号