RAG在医药行业为什么80%都翻车了?
去年我们组做了一个内部复盘,把过去两年参与过或评审过的23个医药RAG项目扒了一遍。结论让人有点沉默:只有4个真正上线并且持续运行超过6个月,另外5个处于「上线即告警」的边缘生存状态,剩下的14个,死在了各个阶段。这篇文章不是要劝你别做RAG,而是把坑说清楚。医药行业有它的特殊性——监管压力、专业术语密度、多语言混排、文档结构复杂——这些因素叠加在一起,让标准RAG教程里的那套方法论几乎直接失效。01 先说结论:翻车不是偶然失败的原因大致可以归成几个类别,比例大概是这样的:数据质量 / 治理:38%Chunking策略错误:22%Embedding模型不匹配:17%检索策略设计缺陷:13%幻觉 / 答案可信度:10%核心规律绝大多数失败不是算法的问题,而是「用通用方案套专业场景」的问题。RAG本身没有原罪,但照搬LangChain的默认参数,在临床试验报告上跑,基本等于自杀。02 五大死亡模式D1 垃圾进,垃圾出:把扫描版PDF直接喂进去,没有OCR质检,分子式变成乱码,剂量数字被识别成字母。D2 暴力分块:按512 token死切,把「禁忌症:…(见第4.3节)」切成两半,上下文完全断裂。D3 通用向量模型:用text-embedding-ada-002处理中文说明书,「不良反应」和「副作用」被映射到相距甚远的向量空间。D4 只用语义检索:问「伊马替尼的起始剂量」,语义检索召回了三篇关于「酪氨酸激酶抑制剂机制」的论文,没有一篇回答具体剂量。D5 幻觉穿透上线:LLM在没有足够上下文时,用置信的语气编造了一个「相似」的药物剂量,临床侧差点采信。03 Chunking的坑:医药文本不是普通文档医药文档有几个通用RAG方案完全没有考虑到的特点:交叉引用密集。药品说明书里「详见【药代动力学】」、「参考第5.1节」这类引用极其常见,切断后完全失去语义。表格信息量大。剂量调整表、不良反应分级表、药物相互作用矩阵——这些用纯文本切割后,行列对应关系彻底丢失。多语言混排。中文说明书夹英文INN名、拉丁文剂量单位、希腊字母(α受体、β阻断剂),普通tokenizer处理一团糟。下面是我们实际在用的一个分层分块策略,做了脱敏处理:# Python · 医药文档分块器importrefromdataclassesimportdataclassfromtypingimportList,OptionalfromenumimportEnumclassDocSection(Enum):INDICATION="适应症"DOSAGE="用法用量"CONTRAINDIC="禁忌"ADVERSE="不良反应"INTERACTION="药物相互作用"PHARMACOLOGY="药理毒理"PHARMACOKIN="药代动力学"@dataclassclassPharmaChunk:text:strsection:DocSection doc_id:strpage:intchunk_idx:int# 保留原始章节标题,检索时做 metadata filtersection_raw:strhas_table:bool=Falsecross_refs:List[str]=None# 被引用的章节列表classPharmaChunker:""" 分层分块策略: 1. 先按章节边界切分(语义完整优先) 2. 超长章节再按段落切,保留章节头作为 prefix 3. 表格单独处理,序列化为结构化文本 4. 记录交叉引用,检索时可做图扩展 """SECTION_PATTERN=re.compile(r'【(适应症|用法用量|禁忌|不良反应|药物相互作用|药理毒理|药代动力学)】')CROSSREF_PATTERN=re.compile(r'(?:详见|参见|见)\s*[【\[ ]?([^】\],。\n]{2,20})[】\]]?')MAX_CHUNK_TOKENS=400# 比通用场景更小,保留上下文密度OVERLAP_TOKENS=80# 跨块重叠,保住语义连续性defchunk(self,text:str,doc_id:str)-List[PharmaChunk]:sections=self._split_by_section(text)chunks=[]forsection_name,section_textinsections:section_enum=self._map_section(section_name)ifself._is_table_heavy(section_text):# 表格专用路径:保留结构,序列化行列chunks+=self._chunk_table_section(section_text,section_enum,doc_id,section_name)elifself._token_count(section_text)=self.MAX_CHUNK_TOKENS:chunks.append(self._make_chunk(section_text,section_enum,doc_id,section_name,len(chunks)))else:# 超长章节:段落切分 + 章节前缀保留chunks+=self._chunk_long_section(section_text,section_enum,doc_id,section_name)returnchunksdef_chunk_long_section(self,text:str,section:DocSection,doc_id:str,section_name:str)-List[PharmaChunk]:paragraphs=[p.strip()forpintext.split('\n\n')if
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2498771.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!