Pharma Agent:从文档 QA 到智能监管合规助手
一、起因去年秋天,我们的 RA(法规事务)团队在准备一个 IND 申请,涉及某小分子靶向药的临床前安全性综述。团队里有个同事叫小林,她需要在 FDA 的 2000 多页 guidance document 里找到关于"杂质阈值"的具体条款,同时对比 ICH Q3A 和 Q3B 的差异。她给我发消息:“我在 CTRL+F,但不知道该搜什么关键词。”那一刻我意识到,问题不是"文档太多",而是人类语言和文档语言之间存在一道鸿沟。她不知道 FDA 用的是 “threshold of toxicological concern (TTC)”,而不是"杂质阈值"。这就是我们开始做 Pharma Agent 的原点。二、为什么不直接用 ChatGPT?这是每个甲方第一个问题,也是我被问烂了的问题。简单说三个字:不可信赖。医药行业的文档 QA 有几个硬约束:约束项通用 LLMPharma Agent答案溯源无法精确定位必须附 chunk 来源 + 页码幻觉风险高(训练数据截止)低(锚定本地文档)数据合规企业数据上传第三方私有部署 / VPC 隔离版本管控无guidance 版本必须对应多语言监管文本混淆中英日分库更关键的是:RA 的同事如果用 ChatGPT 给出了错误的合规建议,那这不是"AI 错了",是她的职业责任。所以我们做的系统,每一个答案必须有来源,来源必须可点击跳转,原文必须可复核。这不是好不好的问题,是能不能用的问题。三、系统架构:先看整体架构图:图注:整体分为三层——文档摄入层(Ingestion Pipeline)、检索增强层(RAG Core)、Agent 决策层(Agentic Loop)。生产环境部署在私有云,LLM 走 Azure OpenAI 的 VPC Endpoint。核心设计决策有几个,我逐一拆开说:3.1 文档分层策略(这是最容易被忽视的地方)医药文档不是普通 PDF。FDA guidance、ICH 指南、SOP、CTD 模块、临床方案——它们的结构完全不同,不能用同一套 chunking 策略。我们最终采用的是语义感知的层级分块:文档类型 Chunk 策略 Chunk Size ───────────────────────────────────────────────────────────── FDA Guidance 按 Section 标题分块 + overlap 800 tokens ICH Guidelines 按条款编号分块(如 Q3A 3.1) 600 tokens 内部 SOP 按步骤编号分块 400 tokens 临床方案 (CSP) 按章节 + 表格独立提取 1000 tokens这个配置是跑了 200+ 次 RAGAS 评估才稳定下来的,不是拍脑袋的。3.2 向量化:用什么 Embedding 模型我们测试过三种方案:OpenAItext-embedding-3-large:效果最好,但数据出境,Pass。BAAI/bge-m3(本地):多语言强,中英日混合文档表现稳定,最终选用。sentence-transformers/all-MiniLM-L6-v2:速度快,但专业术语召回差,淘汰。选 bge-m3 的关键原因:我们有大量中文注册申报文件和日本 PMDA 提交文本,多语言支持是刚需。3.3 检索策略:Hybrid Search + Reranker单纯的向量检索在专业术语上表现很差。比如"ICH Q3A"这个查询,向量相似度把它当成普通词,但它是一个精确的文档编号,BM25 关键词检索更准。所以我们用的是Hybrid Search(向量 + BM25)+ BGE-Reranker 精排:用户 Query │ ├──→ 向量检索(Top-20)──┐ │ ├──→ RRF Fusion(Top-40)──→ BGE-Reranker──→ Top-5 └──→ BM25 检索(Top-20)─┘RRF(Reciprocal Rank Fusion)是个简单但有效的融合算法,不需要学习参数。四、核心代码4.1 文档摄入 Pipeline# pharma_ingestion.py# 医药文档摄入 Pipeline(脱敏版)# 依赖:langchain, pymupdf, sentence-transformers, qdrant-clientimportfitz# PyMuPDFimportrefromdataclassesimportdataclassfromtypingimportList,Optionalfromlangchain.text_splitterimportRecursiveCharacterTextSplitter@dataclassclassPharmaChunk:"""结构化 chunk,带有监管文档专属元数据"""content:strsource_file:strdoc_type:str# "FDA_GUIDANCE" / "ICH" / "SOP" / "CSP"section_id:str# 如 "3.1.2" 或 "Section IV.B"page_num:intchunk_index:intlanguage:str# "en" / "zh" / "ja"classPharmaDocumentProcessor:""" 医药文档处理器 核心设计:不同文档类型用不同策略,而非一刀切 """# 按文档类型配置 chunk 参数CHUNK_CONFIG={"FDA_GUIDANCE":{"chunk_size":800,"chunk_overlap":150},"ICH":{"chunk_size":600,"chunk_overlap":100},"SOP":{"chunk_size":400,"chunk_overlap":80},"CSP":{"chunk_size":1000,"chunk_overlap":200},"DEFAULT":{"chunk_size":700,"chunk_overlap":120},}# ICH 文档的条款编号正则(如 Q3A, Q7, E6 等)ICH_SECTION_PATTERN=re.compile(r'^(\d+\.[\d\.]*|[A-Z]\.|Appendix\s+[A-Z\d]+)',re.MULTILINE)def__init__(self,embedding_model_path:str="BAAI/bge-m3"):self.embedding_model_path=embedding_model_path# 实际项目中在这里初始化 embedding model# self.embedder = FlagModel(embedding_model_path, ...)defdetect_doc_type(self,filename:str,first_page_text:str)-str:""" 自动识别文档类型 实际项目中还会结合文件路径命名规范(如 /sop/ 目录) """filename_upper=filename.upper()ifany(kwinfilename_upperforkwin["FDA","GUIDANCE","DRAFT_GUIDANCE"]):return"FDA_GUIDANCE"ifre.search(r'\bICH\s+[EQS]\d+',first_page_text,re.IGNORECASE):return"ICH"if"SOP"infilename_upperor"STANDARD_OPERATING"infilename_upper:return"SOP"ifany(kwinfilename_upperforkwin["PROTOCOL","CSP","CLINICAL_STUDY"]):return"CSP"return"DEFAULT"defextract_section_id(self,text_block:str,doc_type:str)-str:"""从文本块开头提取章节编号"""ifdoc_type=="ICH":m=self.ICH_SECTION_PATTERN.match(text_block.strip())returnm.group(0).strip()ifmelse"N/A"# 通用:提取开头数字编号m=re.match(r'^(\d+(\.\d+)*)\s',text_block.strip())returnm.group(1)ifmelse"N/A"defprocess_pdf(self,pdf_path:str)-List[PharmaChunk]:""" 处理单个 PDF,返
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2510780.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!