十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

医疗垂直场景RAG调优实战:分段、混合检索、重排与溯源

医疗垂直场景RAG调优实战:分段、混合检索、重排与溯源 做医疗垂直场景RAG调优这件事我前后迭代了21个大版本最后能稳定跑通并上线的版本是21.7。这个版本最核心的变化不是某个模型换了个更大的参数而是把知识分段、混合检索、重排、溯源这四段链路当成一个整体重新捋了一遍。如果你也正在做医疗领域的RAG知识库比如药品说明书问答、临床指南助手、院内制度问答这类应用应该能感受到通用RAG方案在医疗场景下有多别扭看似检索到了内容答案却经常错在关键条件上模型回答得滴水不漏但找不到这句话到底出自哪本指南。这些问题不是单点模型能解决的而是链路设计问题。这篇文章就把我在这轮调优里踩过的坑、验证过的方案、以及最终落地的做法一起拆开讲清楚。1. 医疗垂直场景的RAG到底难在哪三个反直觉的真相1.1 真相一医疗知识不是文档而是决策依据我最早做通用知识库的时候把一份PDF丢进去按固定窗口切好就完事了。到了医疗场景这套逻辑立刻失效。药品说明书里最典型的一句话是老年患者及肝肾功能不全者慎用严重肝功能不全者禁用。如果按500字硬切前半句可能落到上一个片段后半句落到下一个片段。用户问肝功能不全能用这个药吗检索回来的片段只有老年患者及肝肾功能不全者慎用模型看到慎用就回答需要慎用不是禁用但实际上严重肝功能不全应该是禁用。这种情况在通用文档里不会被当成大问题但在医疗场景里一个慎用和禁用的差别足以产生完全不同的决策后果。医疗知识库的每个片段本质上不是一个内容块而是一条可执行的决策依据。它通常由三部分组成适用人群或条件谁、动作或建议做什么、限制条件什么时候不能做。分段的目标不是让句子看起来完整而是让条件-动作-限制这个三元组尽量不分离。一旦理解了这个底层逻辑就不会再用纯字数去切分医疗文本。1.2 真相二相似不等于正确检索召回只是起点向量检索有一个天然缺陷它衡量的是语义相似度而不是逻辑正确性。在医疗场景里相似背景的文本往往代表完全不同的临床含义。举个我优化前的实际bad case用户问阿司匹林用于什么向量检索召回了阿司匹林用于解热镇痛的机制这个片段而知识库里其实还有阿司匹林用于急性冠脉综合征的抗血小板治疗和阿司匹林在儿童病毒感染时的Reye综合征风险。从语义相似度上看第一个片段和阿司匹林用于什么确实最匹配但用户的真实意图可能是说明书里的适应症是什么也可能是这药在什么病里用来抗血小板。如果只看embedding分数正确片段可能排在第三第四位。这说明在医疗RAG里召回阶段允许一定的语义发散但精排阶段必须把逻辑正确性纳入排序依据。所谓逻辑正确包括问题里的禁忌条件是否与片段冲突、问题里的疾病实体是否与片段的适应症一致、用法用量的单位是否对应。这些光靠向量相似度很难学到需要关键词精确匹配、医学实体匹配以及重排模型的综合作用。所以混合检索和重排不是可选项而是医疗RAG的刚需。1.3 真相三溯源不是附加功能而是医疗RAG的命门我接触过很多做医疗产品的团队一开始都把精力放在让模型答得更聪明上对溯源只留了一个引用来源的占位功能。后来用了真实场景的问卷测试才发现医生用户对RAG的信任度很大程度上取决于答案能不能快速回溯到原文。医生不需要你解释那么多机制他只想说这句话是在哪本指南里写的、哪一页、基于哪个证据等级如果溯源做不出来答案再准确也不敢用。溯源的意义不仅是展示出处更是一个内部校验机制。当系统强制每个关键结论都映射到一个知识片段时很多幻觉在生成阶段就会被拦住。比如模型想编造一个FDA批准用于妊娠期高血压的某药物但由于Prompt要求所有结论必须引用给定片段模型中根本没有这样的片段可引用于是只能回答未找到相关信息或者根据现有资料不支持该结论。这种拒绝回答在医疗场景里不是失败而是安全兜底。2. 知识分段改造把切豆腐块升级为按语义边界切2.1 通用分段参数在药品说明书上的失效现场我最初用的是一套通用参数chunk_size500chunk_overlap100按字符硬切。跑通后测试了几个药品交互问题效果惨不忍睹。最典型的失败案例是一款复方感冒药说明书里面包含【成份】【适应症】【用法用量】【不良反应】【禁忌】【注意事项】【药物相互作用】【孕妇及哺乳期妇女用药】【儿童用药】【老年用药】等多个条目。硬切后出现的情况是一个片段里既有【适应症】的最后两行又有【用法用量】的开头一行。用户问这药一天吃几次检索回来的片段前半段是用于缓解普通感冒及流行性感冒引起的发热后半段是口服。一次1袋一日3次。从纯文本看片段包含答案但源标题层级的上下文已经丢失导致模型无法判断一次1袋对应的是哪粒药丸的描述尤其是当同一说明书里还有另一个剂型时直接串了。另外通用分段工具对表格的处理也很差。医疗说明书里大量的年龄-剂量对照表、肝功能分级-用药调整对照表表格结构一旦被硬切成碎片语义就全丢了。有些表甚至包含多个条件列光把文本提出来根本没法看。后来我把表格转成Key-Value格式的文本并保留表头中的条件描述再作为一个整体片段处理这个问题才解决。2.2 结构感知与医学语义单元的分段策略第二轮调优里我把医疗知识分段的核心从按字符数切改为按语义单元切。具体实现分三步第一步结构解析。所有医疗文档先经过一个针对文档类型的解析器药品说明书走说明书解析模板临床指南走指南解析模板院内制度走制度解析模板。解析出标题层级、段落、表格、条目。这一步我用的是在线式规则没有依赖太重的模型因为医疗文档的格式普遍规整规则足够应对。第二步原子片段识别。把每个最小内容块标上类型适应症内容、用法用量内容、禁忌内容、注意事项内容、相互作用内容、不良反应内容、药理毒理内容、临床研究内容、参考文献内容。对于指南类文档还会单独识别推荐建议证据等级推荐强度这样的三件套。这些内容块不再按字数强行合并而是按医学逻辑聚合。比如【用法用量】下的所有段落合并成一个候选片段如果太长再按年龄分组细分【禁忌】和【注意事项】永远不合并到同一个片段因为它们在检索语义上属于不同意图。第三步长片段二次分割。如果某个语义单元还是超过模型上下文窗口比如1200 token再按条件句边界切分。切分依据包括句号、分号以及关键词启始模式如对于……患者合并……时如出现……。我实际用下来医疗文档里这类条件句是最频繁出现的高价值检索单元单独成段准没错。2.3 分段粒度的量化评估用检索命中率说话分段好坏不能靠肉眼感觉得用数据评估。我构建了一个小型的问题-片段对齐集数量不多大概150条覆盖说明书和指南的常见问法。每条标注结构是{query, relevant_chunk_id}。比如孕妇能使用布洛芬吗对应的片段是那条包含孕妇及哺乳期妇女用药本品禁用的原子片段。有了这个对齐集我就可以计算不同分段策略下的单片段命中率检索Top5召回结果中是否包含标注的那个片段。这个指标直接反映分段边界是否和问题匹配。测试结果很有说服力。固定500字切分时Top5命中率只有51%结构感知分段后Top5命中率提升到78%再配合语义单元内二次切分达到83%。有意思的是单纯调大chunk_size并不会改善命中率反而会让很多条目混在一起导致重排时难以判断答案来源。合理的粒度应该是一个片段尽量只回答一个完整医学问题。如果一个片段里包含太多信息后面做引用溯源时也没法精确标到某一行所以宁可切碎一点点也不能让边界模糊。2.4 分段与Embedding模型的配合细节分段方式和embedding模型是互相影响的。我在22B embedding模型上测试发现给片段加一个类型前缀会让向量空间的区分度明显变好。具体做法是在每个片段文本前拼上标签比如[药品 - 用法用量]或者[指南 - 治疗建议 - 证据等级A]。这样embedding在编码时会把片段类型也纳入向量检索每天吃几次时包含用法用量标签的片段会比同语义的适应症片段排得更靠前。这个方法实现成本极低但效用非常明显推荐直接复用。另外医疗领域缩写和实体多通用中文embedding对ACEIqdbid这类词的表征并不好。我的做法是构建了一个小型医疗词典在embedding之前对query和片段做一次术语标准化把缩写扩写为全称把同义词映射到标准词再喂给embedding模型。这一步放在检索预处理里具体会再讲到。总之一句话先保证分段符合医学语义逻辑再考虑embedding怎么优化否则embedding模型再强也救不回来。3. 混合检索的多路召回向量、关键词、本体一个都不能少3.1 单路向量召回在医疗表达鸿沟前的无力感医疗问答里的表达鸿沟比通用领域严重得多。用户口语常说血压药降压片地平类药物文档里写的是钙离子通道阻滞剂硝苯地平控释片氨氯地平片。向量模型泛化能力还行但遇到精确匹配需求时就会露馅。比如qd是每日一次的缩写po是口服的缩写如果文档片段里只有口服没有po用户直接用po提问向量召回经常给不到正确答案因为embedding并没有把这些专业缩写和全称准确对应起来。还有一种情况是数字和单位。病历或咨询中会问这个药一天100mg行吗但文档里写的是一次50mg每日2次。这类数值条件向量检索基本是靠文本重合硬凑效果很差。我意识到医疗检索需要的不只是语义匹配还有精确的术语匹配、数值条件匹配和实体关系匹配。于是把检索架构改成了多路召回。3.2 多路召回的整体架构与召回源设计最终落地的检索模块包含三个召回源向量召回使用领域微调过的embedding模型对query做向量化后在片段库中搜TopK我取Top50。负责捕捉语义相似和同义改写。BM25全文召回先把query做分词并做同义词扩展再用BM25检索取Top30。负责精确词匹配、缩写匹配、药名匹配。本体/知识图谱召回基于医疗本体的知识图谱从query中识别实体药品、疾病、症状、检查再检索与实体相关的三元组或规则片段取Top20。负责处理A药物与B药物的相互作用某疾病的禁用药物这类关系型问题。三条召回路径的结果统一进入一个融合模块之后交给重排。这个设计看起来多了一层复杂度但它真正解决了向量漏掉精确条件的问题。比如用户问地高辛和呋塞米能不能一起吃向量召回可能返回一堆同组药物知识但知识图谱召回能直接返回药物相互作用关系对精准度完全不一样。3.3 同义词、缩写与Ontology映射的工程实现这里我把热词里的ontology rag落地了。本体映射不是一个巨大的模型而是一套可控的词典集群。我没有自己从零构建医疗本体而是用公开药品/ICD术语表整理出三个映射表缩写表qd - 每日一次、bid - 每日两次、tid - 每日三次、po - 口服、iv - 静脉注射、ACEI - 血管紧张素转化酶抑制剂。同义词表阿司匹林 - 乙酰水杨酸、扑热息痛 - 对乙酰氨基酚、降压药 - 抗高血压药。实体类型表标识一个词是药品名、疾病名、症状名、检查名。在query预处理时先做分词再查映射表生成一个扩展query。扩展后的query同时用于BM25检索、embedding模型输入以及实体识别。这一步需要控制扩展范围否则会把一个简单问题扩展成一段包含大量无关同义词的噪声query。我设了一个规则扩展后的同义词只在原词出现时才加入且扩展项总数不超过3个缩写必须按全称加入因为缩写出现在临床提问里的概率本来就高。实测这样的扩展策略让BM25的Recall10提升约18%。另外本体图谱召回使用的是实体关系图谱比如地高辛节点关联洋地黄中毒低钾血症与呋塞米合用增加毒性等关系。这一类数据不用太多覆盖高频药物和高危相互作用即可。它的价值在于稳定不会因为文本表达变化而失效。3.4 结果合并、去重与动态权重的取舍多路召回结果合并我推荐使用RFFReciprocal Rank Fusion原因非常简单不同召回器的打分尺度完全不一致向量相似度是0到1的浮点数BM25是几百上千的原始分图谱召回可能是0或1的命中标记。如果直接线性加权BM25会把其他路淹没。RFF不关心原始分只看每个文档在各自列表里的排序位置计算公式是score 1 / (60 rank)。这个60是经验参数可以根据TopN规模调整。RFF最稳效果也说得过去。还有一个必须处理的环节是去重。向量召回和BM25经常召回同一份文档的不同片段我的做法是计算候选片段之间的向量相似度如果两段内容相似度大于0.92就视为重复保留来源更权威的那个片段例如指南优先于百科。去重之后再按RFF得分取Top30进重排。动态权重这块我尝试过给不同意图的query分配不同权重但成本效果比不划算后来改用了一个简单规则如果query命中药品实体且包含相互作用/合用/同服等词则图谱召回的片段在融合时额外加一个小权重如果query是什么是/机制是这种机制类问题则向量召回的权重更大。说白了就是用一个非常轻量的规则分类器切换权重优先级不高但对相互作用类问题的提升肉眼可见。如果人力有限先别做动态权重把RFF和去重做好就够了。4. 重排环节的调优用轻量模型做医疗场景的精准排序4.1 粗排和精排的分工为什么粗排之后还要重排召回阶段的目标是即使不精确也先把可能的片段捞回来所以Top50的候选里混着大量相关性不高但语义沾边的片段。重排的任务是从这50个里挑出最有可能支撑答案的前3到5个。为什么粗排不能直接用向量分数排序因为向量相似度是全局语义的相似而医疗问答更在意的是条件是否匹配。比如query是哮喘人群用美托洛尔的情况一个片段讲美托洛尔用于心衰对支气管哮喘患者可能诱发支气管痉挛另一个片段讲美托洛尔用于心梗后的二级预防常见副作用包括心动过缓。从向量相似度看两个片段都相关但第一个片段直接命中哮喘禁忌的组合这才是正确答案需要的。这种细粒度关系只有让模型同时看query和候选片段cross-encoder才能更准确地判断。重排模型还有一个隐藏优势它能吸收更多的任务指令。我在重排时会把医疗安全性要求写进任务描述比如让模型特别关注片段中是否包含禁忌慎用禁用不推荐等关键词如果query提到了某类人群片段中对应的限制条件要作为最高权重。这样重排就不再是简单的相似度打分而是一个带规则偏置的排序器医疗场景下非常管用。4.2 重排模型选型通用重排器与医疗微调模型的实测目前社区常用的重排器有这么几类bge-reranker-large、通义系列的2B/4B重排模型、以及自己训练的医疗cross-encoder。我把这几个在自建的医疗评测集上跑过一轮结论比较清晰模型参数量医疗评测集NDCG5单条延迟GPU A10备注bge-reranker-large约0.3B0.6120ms左右通用场景很强医疗复杂排序一般通义2B重排模型2B0.68150ms左右性价比高普通医疗问答够用通义4B重排模型4B0.72270ms左右复杂条件/相互作用排序更稳自训练医疗cross-encoder0.3B0.7025ms左右领域数据微调后效果可逼近4B很多人问通义2B和4B重排模型差距大吗我的实测结论是在大部分普通问答上差距不大但在需要理解多重条件冲突的case上比如孕妇肝功能不全正在服药这种复合场景4B的表现明显更稳测试集上大概高4到5个点。不过医疗场景的线上并发通常不高延迟差异可以接受如果服务器资源允许我优先推荐4B。如果只能用小模型自训练一个医疗cross-encoder会更划算效果上限取决于训练数据的质量。4.3 医疗重排模型的样本构造与训练要点如果是自己训练重排模型数据构造是重中之重。我用的训练数据来自三个渠道检索日志里的用户点击弱标签、人工标注的相关性分数、以及对bad case的修正标注。训练样本格式是一个一个三元组{query, feedback, label}label是0到2的整数2表示强相关且包含答案1表示弱相关0表示不相关。最难的部分是难负样本的构造。普通负样本随便从知识库里抽就行难负样本要从向量召回里挑这些片段和query相似度很高但不含答案或条件冲突。比如query是肾病高血压用什么药难负样本是高血压患者使用某药物的说明书片段但里面明确写着严重肾功能不全者禁用。模型只有学会把这样的片段排到后面才能真正提高医疗场景的排序精度。训练loss我用的是pairwise ranking loss每次输入一个query和两段候选让模型判断哪个更相关。这个方案实施简单效果也不错。另外样本里的禁忌词是医疗重排的胜负手。我在数据标注时专门标记了片段中的禁忌句并在训练阶段通过一个辅助目标让模型学习问题条件与片段限制词的关系。比如query里出现孕妇片段里有孕妇禁用则加分有儿童慎用则属于弱相关。如果你不想自训练也可以在prompt里让通用重排模型关注这些词实测也能带来一定提升。4.4 重排效果评估别被RecallK蒙住眼睛很多团队评估重排只盯着Recall10这是远远不够的。医疗场景里最终喂给LLM的片段最多就前3到5个正确答案排在第6和第排在第100对最终答案来说是灾难和无关的区别。所以我建立了一套针对精排的评估指标NDCG5衡量排序质量尤其看正确片段是否排在前面。答案引用命中率最终LLM答案引用的片段里包含正确答案的比例。这个指标直接关联端到端效果。冲突片段检出率如果知识库中存在与query条件冲突的限制句重排是否把它排到后面。这个指标是医疗场景特有的我会在测试集里专门放一些禁忌问题。我踩过的一个坑是刚开始只看Recall重排后的Recall甚至略低于粗排但NDCG5大幅上升端到端答案准确率却明显提升。原因很简单粗排把正确答案排在前面几位的概率不高但Recall衡量的是Top50内是否出现重排虽然没改变Top50的构成却把正确答案提到前3位这是最有价值的优化。如果你自己调优建议直接以端到端答案正确率为准不要只看中间指标。5. 溯源与证据链落地让每个答案都有出处可查5.1 引文级溯源的数据结构与Prompt约束溯源要落地首先在数据入库时就得把来源信息做成结构化的元数据而不是检索后再去猜。我建了一个source_meta字段包含doc_id、version、section、page_number、line_range、source_type说明书/指南/规章制度、approval_date、original_text。每个知识片段在生成时都绑定这些元数据检索时跟着片段一起传给LLM。Prompt里我会做非常明确的约束。示例片段你现在是一个医疗知识库问答助手。你只能使用上下文中给出的片段回答。 回答时每个关键结论的末尾必须用[引用编号]标注编号对应上下文中支持该结论的片段。 如果上下文没有覆盖用户的问题请直接回答根据现有资料无法确认。 严禁基于常识或记忆补全专业医学内容。同时在发送给模型的上下文里我把每个片段编号并按格式输出[1] 来源XX药品说明书2023-11版第2节【用法用量】原文每次1袋每日3次温水冲服。 [2] 来源XX临床指南2024版第5.2节原文对于妊娠期高血压患者推荐使用拉贝洛尔。模型在生成时如果写了[2]前端就能通过编号直接展示片段原文、来源和所在章节。这个引文级溯源看起来简单但如果没有前面的语义单元分段编号可能对应一大段混合内容根本没办法精确展示。所以分段是溯源的前提不是两件独立的事。5.2 置信度评分与拒绝回答兜底策略医疗RAG不能为了答而答。我在检索和重排阶段会额外输出一个证据置信度分由几部分组成重排最高分、引用片段之间的一致性、片段与query条件的覆盖度。规则如下如果重排最高分低于阈值0.55直接返回证据不足无法回答。如果Top3片段中关于核心问题的结论互相矛盾比如一个说可用一个说禁用则返回知识库中存在冲突信息建议咨询临床医生确认。如果query里包含某个特定条件如妊娠期但所有候选片段都没有明确提及该条件词则降低置信度优先触发拒绝回答。这套策略上线后系统的宁可不说不可乱说原则才算真正落地。刚开始产品经理担心拒绝率太高会影响体验实测下来因为前面分段和重排已经做了大量工作真正触发拒绝回答的只有5%左右而这5%里大部分本来就是超出知识库覆盖的高危问题。对医疗场景来说这个兜底是合格的。5.3 Agentic RAG在医疗场景的探索何时该让模型多跳最近Agentic RAG很火我理解它的核心价值是让模型自行决定检索什么、什么时候再检索。在医疗场景确实存在需要多跳的问题用户说我有高血压和糖尿病正在吃硝苯地平和二甲双胍现在想加一个感冒药有什么风险吗这个问题需要先识别两种疾病和两种药再查感冒药与两类药物的相互作用最后结合说明书里的禁忌条件综合判断。单轮RAG很难一次检索齐。我尝试在单轮链路稳定后增加了一条ReAct风格的Agent路径模型先生成检索计划拆出几个查询比如硝苯地平 感冒药 相互作用二甲双胍 感冒药 相互作用多次调用检索接口汇总后再回答。这个路径确实能解决一部分复杂问题但代价也很明显延迟从1秒变成3到5秒而且多跳中间步骤发生幻觉的概率更高。所以我目前的策略是先用规则判断包含多个实体或相互作用类意图时才进入Agent路径简单知识问答仍然走单轮快速链路。如果你刚开始做医疗RAG建议先把单轮链路打磨稳再上Agent否则Agent会把前面所有环节的问题放大。5.4 Bad Case复盘驱动的迭代闭环最后分享一个让我少走很多弯路的机制Bad Case复盘表。每周我固定从线上日志和人工评测里收集错误case然后按环节归类。分类表大概是这样的Bad Case现象根因环节修复动作答案引用了慎用片段但问题是禁用知识分段/定义不清晰将禁忌句拆成独立片段增加禁忌词标签用户问缩写qd没召回每日一次混合检索/预处理在缩写表中补充并加入query扩展两个相似片段排序颠倒正确片段排第二重排增加条件冲突难负样本重训模型回答内容正确但引用编号指向错误章节溯源/元数据在解析器中对章节号做校验修正元数据这个复盘不理想化它非常枯燥但每一轮下来系统都会实打实变好。21.7这个版本就是从第21轮复盘里一路把分段的边界条件、混合检索的权重、重排的难负样本、溯源的元数据修出来的。如果让我给后来者一个最重要的经验我会说不要指望一个神仙模型解决所有问题RAG是一条工程链医疗场景更是如此。先把分段做成可追溯的语义单元再把检索做成多路互补的召回然后上重排做精排最后用溯源把答案锁死在证据链上。每一步的工程质量都比单点模型的参数大小更值得投入。
返回列表