药品说明书是普通人接触最多、也最难读懂的专业文档之一。一份说明书中,适应症、用法用量、不良反应、禁忌、药物相互作用等内容分散在数十页文本里,而且大量使用医学术语。市面上的通用大模型直接回答用药问题时,很容易出现编造剂量、混淆药品名的情况,这在医疗场景是不可接受的。这篇文章用一个完整的案例,讲清楚如何搭建一个药品说明书解读Agent,让它在忠实于说明书原文的前提下,回答用户的用药疑问。

一、为什么通用大模型直接回答用药问题不可靠
先看一个真实的失败案例。用户提问:家里老人75岁,肾功能不太好,阿卡波糖应该怎么吃?通用大模型往往直接给出成人常规剂量,而说明书中明确写了严重肾功能不全患者禁用阿卡波糖。模型没有撒谎,它只是不知道这份说明书里写了什么,只给出了概率上最像答案的内容。
这就是医疗问答中最危险的幻觉来源:模型基于训练语料的泛化回答,而非基于具体文档的忠实回答。药品说明书还有几个特殊性加剧了这个问题。第一,同一种药不同厂家的说明书细节不同,缓释片和普通片的剂量规则完全不同;第二,说明书中大量信息是条件化的,例如“肝功能不全者减半服用”这种规则,模型容易丢掉前置条件;第三,剂量换算涉及体重、年龄、肌酐清除率等计算,纯文本生成很容易算错。
所以这个案例的Agent必须满足三条底线:答案必须能在说明书中找到原文依据;涉及计算的内容必须由代码完成而不是模型生成;无法回答时明确说不知道,而不是编造。围绕这三条底线,我们采用RAG加工具调用的混合架构。
二、系统架构设计:RAG、工具调用与安全兜底
整个Agent分为四层。第一层是文档处理层,负责把PDF或图片格式的说明书转成结构化文本。第二层是检索层,把说明书切块后建立向量索引。第三层是工具层,提供剂量计算、相互作用查询等确定性函数。第四层是生成层,由大模型基于检索到的原文生成回答,并通过提示词强制引用出处。
文档处理这一步经常被低估。药品说明书的PDF排版很乱,表格断行、页眉页脚混入正文是常态。我们用PaddleOCR做版面分析后,还需要一段清洗逻辑,把“用法用量”这样的章节标题识别出来,作为后续切块的边界。按章节切块比固定长度切块效果好得多,因为剂量规则往往是一整段,切断之后检索回来的片段就缺上下文了。
def split_by_section(text):
# 说明书常见章节标题
sections = ["适应症", "用法用量", "不良反应", "禁忌",
"注意事项", "药物相互作用", "特殊人群用药"]
boundaries = []
for name in sections:
for m in re.finditer(name, text):
boundaries.append((m.start(), name))
boundaries.sort()
chunks = []
for i, (pos, name) in enumerate(boundaries):
end = boundaries[i+1][0] if i+1 < len(boundaries) else len(text)
chunk_text = text[pos:end].strip()
if len(chunk_text) > 30: # 过滤误识别的标题
chunks.append({"section": name, "text": chunk_text})
return chunks检索层用通用向量模型效果一般,因为说明书里“禁用”“慎用”“忌用”这类近义词的语义区分度很低。实践中给每个chunk拼接章节名做伪文本增强,例如“【药物相互作用】+原文”,召回率能提升一成以上。检索时同时跑向量检索和关键词检索(针对药品名、成分名这类精确匹配需求),用RRF融合排序。
工具层是这个Agent区别于普通RAG问答的关键。剂量计算、体重换算、肌酐清除率估算(Cockcroft-Gault公式)这些都要注册成工具,模型只负责决定什么时候调用,绝不自己算数。药物相互作用查询则接一个本地药品数据库,因为常见联用禁忌是结构化知识,比从文本里检索更可靠。
三、核心流程的代码实现
下面是Agent主循环的简化实现,展示了模型如何在检索和工具之间做决策。用中文函数注册工具,模型调用时按名称匹配即可。
tools = [
{
"name": "search_instruction",
"description": "在药品说明书中检索原文,回答任何关于用法用量、"
"禁忌、不良反应的问题前必须先调用此工具",
"parameters": {
"type": "object",
"properties": {
"query": {"type": "string", "description": "检索问题"}
},
"required": ["query"]
}
},
{
"name": "calc_dose",
"description": "根据体重或年龄计算儿童剂量,禁止模型自行估算",
"parameters": {
"type": "object",
"properties": {
"mg_per_kg": {"type": "number"},
"weight_kg": {"type": "number"}
},
"required": ["mg_per_kg", "weight_kg"]
}
}
]
SYSTEM_PROMPT = """你是药品说明书解读助手,必须遵守:
1. 每个回答必须基于search_instruction返回的说明书原文,并标注所属章节
2. 说明书中没有的信息,明确回答"说明书中未提及",禁止补充外部知识
3. 所有剂量计算必须调用calc_dose,禁止心算
4. 涉及"是否可以服用"的判断,必须提示用户咨询医生或药师"""注意系统提示词的第二条,这是压制幻觉的核心约束。很多Agent失败的原因是模型太“热心”,检索不到就用自己的知识补,在医疗场景这种热心是致命的。同时第四条把最终决策权交还给专业人员,Agent只做信息解读,不做诊疗建议,这既是安全设计,也是产品定位。
一个典型的多轮对话流程是这样的:用户问“这个药饭前吃还是饭后吃”,模型调用检索工具拿到“用法用量”章节的原文,生成回答并附上出处;用户接着问“我孩子20公斤,吃多少”,模型识别到需要计算,调用calc_dose传入每公斤体重的剂量参数和体重值,拿到结果后再结合原文中的频次限制组织回答。整个过程中模型没有生成过任何一个数字。
四、测试方案与常见踩坑点
这类Agent的测试不能只靠人工体验,需要建一个评测集。我们从说明书中人工整理了三类问题:原文可直接回答的(约60%)、需要计算的(约20%)、说明书未提及的陷阱题(约20%)。陷阱题最关键,专门考察模型会不会编造,比如问某降压药是否影响血糖,说明书中根本没这条信息,正确答案是“未提及”,任何看起来专业的回答都算失败。
实测中我们踩过三个坑。一是OCR质量决定上限,一个表格识别错误的说明书,后面全链路都在错误数据上工作,所以文档处理层要留人工校验入口。二是模型倾向于跳过工具直接回答,尤其是问题看起来简单时,解决办法是在提示词里加“回答前必须调用检索工具”的硬性规则,并把工具调用次数纳入评测指标。三是多药品场景的上下文污染,用户上一轮问的是阿莫西林,下一轮问“这个药”,检索时必须携带当前会话绑定的药品ID,而不是只看最近一轮。
最后要提醒的是免责设计。所有回答末尾固定追加“本解读仅供参考,具体用药请遵医嘱”,页面上明确标注信息来源为说明书原文及其章节位置,让用户可以核对。垂直领域的Agent拼的不是模型多聪明,而是约束做得够不够严。把模型能做的事限定在忠实转述和调度计算范围内,这个药品说明书解读Agent的可靠度就能达到可用的水平。