资讯动态

我在药企用DeepSeek+RAG搭了个药品说明书问答系统

发布时间:2026/9/29 1:55:36 来源:尧图企业网站定制
去年年底我们CTO突然在周会上说我们也要做AI。我当时心里清楚他的意思是我也要在汇报PPT里写AI。但既然任务来了就得干出点真东西。于是我选了个最能跑通、最有业务价值的场景——药品说明书智能问答。三个月后这个系统在医学信息部门上线了QA满意度从靠人工查文档的下午可能回复你变成了秒级响应。这篇文章是我踩坑之后的真实复盘。1. 为什么偏偏选这个场景药企的AI场景有很多花哨的选项靶点预测、分子生成、临床试验优化……但这些要么依赖专业数据集要么需要和监管部门解释清楚AI推荐的分子你们敢批吗。而药品说明书问答这个场景有几个天然优势**数据是现成的且合规闭环。**说明书是NMPA批准的官方文件用它做知识库不存在数据授权问题答案有据可查可以溯源。如果AI回答有偏差你能精确找到是哪段说明书出了问题而不是模型幻觉这口黑锅甩不清楚。**用户场景真实、频繁。**医学信息部门每天要回答几十到几百个来自医生、患者的电话问询比如这个药能跟XXX联用吗、“肾功能不全要减量吗”这些问题80%都在说明书里有答案但人工查需要5-15分钟。关键洞察RAGRetrieval-Augmented Generation最适合的场景是答案在文档里但文档太多太散。药品说明书完美符合这个特征——结构化程度高权威性强边界清晰。2. 整体架构别急着写代码很多同学一上来就问用什么向量库其实架构想清楚了选型反而是最简单的事。我们最终的架构分三层数据层↓ 离线构建一次入库文档解析PDF → 结构化文本块元数据提取药品名、适应症、章节标签向量化存储Embedding Milvus检索层↓ 在线实时混合检索向量召回 BM25 重排上下文过滤药品名实体匹配Rerankcross-encoder精排生成层DeepSeek-R1私有化部署 / APIPrompt模板角色定义 合规约束引用溯源答案 原文段落 章节这套架构没有什么革命性的东西但每一层都有几个小决策值得说清楚。3. 文档解析PDF说明书的折磨说明书PDF是这个项目里最脏的数据。药品说明书的PDF有几种来源药企自己排版的结构良好的版本以及NMPA官网下载的扫描件版本。两种完全不同的处理策略。结构良好的PDF用pdfplumber基本够用但要注意表格提取——说明书里有大量表格比如不良反应发生率、用药剂量表如果把表格打散成文本块检索效果会很差。我们的处理方式是把表格单独提取并序列化成 Markdown 格式存储# 文档解析核心逻辑脱敏版importpdfplumberimportrefromdataclassesimportdataclassfromtypingimportList,OptionaldataclassclassDocChunk:drug_name:strsection:str# 如禁忌、不良反应、药物相互作用content:strchunk_type:str# text | tablepage_num:intsource_file:str# 说明书标准章节定义基于国内说明书格式规范STANDARD_SECTIONS[药品名称,成份,性状,适应症,规格,用法用量,不良反应,禁忌,注意事项,孕妇及哺乳期妇女用药,儿童用药,老年用药,药物相互作用,药物过量,药理毒理,药代动力学,贮藏,包装,有效期]classPIParser:产品说明书Package Insert解析器def__init__(self,drug_name:str):self.drug_namedrug_name# 章节识别正则兼容【适应症】和适应症两种格式self.section_patternre.compile(r【(|.join(STANDARD_SECTIONS)r)】|^(|.join(STANDARD_SECTIONS)r)[:],re.MULTILINE)defparse(self,pdf_path:str)-List[DocChunk]:chunks[]withpdfplumber.open(pdf_path)aspdf:forpage_num,pageinenumerate(pdf.pages,1):# 先提取表格避免和正文混淆tablespage.extract_tables()fortableintables:md_tableself._table_to_markdown(table)ifmd_table:chunks.append(DocChunk(drug_nameself.drug_name,sectionself._infer_section(page_num),contentmd_table,chunk_typetable,page_numpage_num,source_filepdf_path))# 提取正文并按章节切分textpage.extract_text()ortext_chunksself._split_by_section(text,page_num)chunks.extend(text_chunks)returnchunksdef_table_to_markdown(self,table)-str:ifnottableornottable[0]:returnheader | .join(str(cor)forcintable[0])sep | .join([---]*len(table[0]))rows[ | .join(str(cor)forcinrow)forrowintable[1:]]return\n.join([header,sep]rows)⚠️踩坑记录扫描版PDF千万别用pdfplumber直接提取全是乱码或者空字符串。我们用的是paddleocr的版面分析能力先识别页面区域标题/段落/表格再对每个区域做OCR。速度慢但准确率高。如果你们的说明书是原生PDF可以跳过这一步。4. 向量库选型与Embedding向量库我们选的是 Milvus原因很简单支持元数据过滤。这一点在医药场景极其重要——用户问这个药的禁忌是什么你不能把所有药的禁忌章节都扔进去检索必须先按药品名过滤再做向量相似度搜索。Embedding模型的选择经过了几轮评测。用OpenAI的text-embedding-3-large效果好但贵而且数据上传境外合规存疑。最终选择了 BAAI 的bge-large-zh-v1.5中文语义理解效果和专业术语覆盖都不错私有化部署数据不出内网。# 向量入库核心逻辑脱敏版frompymilvusimportconnections,Collection,FieldSchemafrompymilvusimportCollectionSchema,DataType,utilityfromsentence_transformersimportSentenceTransformerimporthashlib EMBED_MODELSentenceTransformer(BAAI/bge-large-zh-v1.5)VECTOR_DIM1024defbuild_collection():fields[FieldSchema(nameid,dtypeDataType.INT64,is_primaryTrue),FieldSchema(namedrug_name,dtypeDataType.VARCHAR,max_length200),FieldSchema(namesection,dtypeDataType.VARCHAR,max_length100),FieldSchema(namechunk_type,dtypeDataType.VARCHAR,max_length20),FieldSchema(namecontent,dtypeDataType.VARCHAR,max_length4000),FieldSchema(namepage_num,dtypeDataType.INT32),FieldSchema(nameembedding,dtypeDataType.FLOAT_VECTOR,dimVECTOR_DIM),]schemaCollectionSchema(fields,description药品说明书知识库)returnCollection(drug_pi_chunks,schema)defembed_and_insert(chunks:list[DocChunk],collection:Collection):# BGE模型需要加指令前缀才能发挥最优效果QUERY_INSTRUCTION为这个句子生成表示以用于检索相关文章texts[QUERY_INSTRUCTIONc.contentforcinchunks]# 批量编码batch_size根据GPU显存调整embeddingsEMBED_MODEL.encode(texts,batch_size32,normalize_embeddingsTrue,# 余弦相似度必须归一化show_progress_barTrue)data{id:[int(hashlib.md5(c.content.encode()).hexdigest(),16)%(10**18)forcinchunks],drug_name:[c.drug_nameforcinchunks],section:[c.sectionforcinchunks],chunk_type:[c.chunk_typeforcinchunks],content:[c.contentforcinchunks],page_num:[c.page_numforcinchunks],embedding:embeddings.tolist()}collection.insert(data)collection.flush()5. DeepSeek接入与Prompt工程这里有一个决策用API还是私有化部署我们最终选了私有化部署DeepSeek-R1-7B原因是医药数据敏感性虽然说明书是公开文档但问询内容涉及患者信息同时内网延迟更低。如果你们公司没有GPU资源用API也完全够用效果区别在实际场景中感知不大。Prompt工程是这个项目里我投入最多时间的部分没有之一。医药场景的Prompt有几个必须处理的要点# RAG检索生成核心流程脱敏版fromopenaiimportOpenAI# DeepSeek兼容OpenAI接口fromdataclassesimportdataclass# 私有化部署地址替换为实际地址clientOpenAI(base_urlhttp://your-deepseek-server/v1,api_keynot-needed)SYSTEM_PROMPT你是一名专业的医学信息专员负责根据已批准的药品说明书回答关于药品的问题。 【核心规则】 1. 只基于下方提供的【参考说明书片段】回答不得使用你的预训练知识补充说明书以外的内容 2. 如果参考片段中没有相关信息明确回复根据当前说明书暂无相关记载不要猜测 3. 回答后必须注明来源章节格式来源[章节名]第X页 4. 涉及用法用量时提醒用户具体用药请遵医嘱 5. 遇到明显超出说明书范围的医疗建议请求如这个药能治癌症吗礼貌说明此类问题需咨询医生 【回答格式】 - 直接给出答案 - 最后附上 信息来源... defretrieve_context(question:str,drug_name:str,collection:Collection,top_k:int5)-list[dict]:query_vecEMBED_MODEL.encode(为这个句子生成表示以用于检索相关文章question,normalize_embeddingsTrue).tolist()# 元数据过滤只检索指定药品的内容resultscollection.search(data[query_vec],anns_fieldembedding,param{metric_type:IP,params:{nprobe:64}},limittop_k*2,# 多取一些后续rerankexprfdrug_name {drug_name},output_fields[content,section,page_num,chunk_type])return[{content:hit.entity.get(content),section:hit.entity.get(section),page_num:hit.entity.get(page_num),score:hit.score}forhitinresults[0]]defanswer_question(question:str,drug_name:str,collection:Collection)-str:contextsretrieve_context(question,drug_name,collection)# 构建上下文文本带章节标注context_text\n\n.join([f【{c[section]}第{c[page_num]}页】\n{c[content]}forcincontexts])user_messagef问题{question}【参考说明书片段 -{drug_name}】{context_text}请根据以上说明书内容回答问题。responseclient.chat.completions.create(modeldeepseek-r1-7b,# 或 deepseek-chat (API版)messages[{role:system,content:SYSTEM_PROMPT},{role:user,content:user_message}],temperature0.1,# 医药场景要低temperature减少创造性max_tokens1024)returnresponse.choices[0].message.content6. 合规与幻觉控制这是药企命门这一块是普通AI项目和医药AI项目差距最大的地方没有之一。我们做了三道防线**第一道召回验证。**如果检索到的top-1文本块相似度低于阈值我们设的是0.72直接拒绝生成返回在说明书中未找到相关信息。这比让模型去胡编要好得多。**第二道生成后验证。**模型生成答案后用一个轻量级的校验逻辑检查答案中的关键数字剂量、浓度、时间是否出现在检索到的原文里。如果答案里出现了原文中没有的数字触发人工审核队列。**第三道用户侧透明度。**每个答案都附带点击查看原始说明书段落的链接让用户可以自行核验。这个设计让医学信息部门的专家接受度大幅提升——他们知道系统在展示而不是在发明。风险类型具体场景应对策略实现方式召回漏失问题答案在文档中存在但没召回混合检索RerankBM25 向量双路模型幻觉答案中出现说明书没有的内容低temperature 数字校验temperature0.1正则比对文档过期说明书更新但知识库未同步版本管理 定期触发重建每季度比对NMPA更新边界问题用户问医疗建议而非说明书内容意图分类拒绝超范围问题小模型前置分类7. 上线效果与数据核心数据指标94%标准问题答案准确率 (人工抽查200题)3.2s平均响应时间 (含检索生成)78%问询被系统直接解决无需人工介入82%用户满意度评分 (5分制≥4分比例)真实反馈上线两周后医学信息部的同事跟我说有个医生打电话问完之后说这个系统回答比你们某些人工客服靠谱多了而且还给我说来源。这句话让我觉得这几个月没白费。8. 还没解决的问题诚实说写这篇文章的时候这个系统还有几个明显的问题我不想假装它们不存在**多药联用问题处理很弱。**用户问A药和B药能不能一起用需要同时从两个药的说明书里检索并进行联合推理。我们目前的方案是拆解成两次查询再合并但模型在综合推断上仍然不稳定。**罕见问题的召回率差。**说明书里有一些章节比如药物过量内容极少对应的向量稀疏相似度检索效果不好。这类问题我们暂时靠关键词匹配兜底但体验一般。**说明书更新同步有延迟。**现在是人工触发重建理想的状态应该是监控NMPA数据库变更后自动更新这块还没做完。如果你在读完这篇文章后发现我漏掉了什么重要的点或者你们有更好的解决方案欢迎在评论区聊。医药AI这个领域坑很多但也有很多真正的价值在里面——不是PPT上的那种是能帮到医生和患者的那种。技术栈总结Python 3.11 · pdfplumber / PaddleOCR · BAAI/bge-large-zh-v1.5 · Milvus 2.4 · DeepSeek-R1私有化 · FastAPI本文所有代码已脱敏不含任何真实药品名、内网地址及业务数据。如果这篇对你有帮助点个收藏。有问题欢迎评论我尽量回。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑