作者:林煒 | 项目:面向全场景用药安全的医师助手 Agent | 2026.04.03


一、先说结论

在正式开始写代码之前,我们团队花了将近一周时间在争论一个看起来不大的问题:AI 模块到底用什么架构?

最终我们选定的方案是:Single-Agent + ReAct 工作流 + GraphRAG 工具调用

这篇博客想完整记录这个决策的推导过程——我们考察了哪些方案、为什么否定了它们、最终的架构长什么样。纯粹的技术介绍网上很多,我不想重复,只想说清楚"为什么是这个方案而不是别的"。


二、从问题本身出发

用药安全检查表面上是一个"查询-回答"问题,但细想之后有几个关键约束:

约束一:答案必须可溯源,不能靠 LLM 自由发挥。
布洛芬和华法林能不能一起吃?这个问题的答案是确定的,有明确的药理机制依据。如果 LLM 答错了,后果不是体验差,而是医疗事故。所以我们不能接受"LLM 凭记忆回答"的方案,必须有一个绝对可靠的事实来源。

约束二:用户输入是非结构化的自然语言,质量很差。
医生端可能输入"患者高血压,在用拜新同,最近又开了阿司匹林"。患者端可能输入"我在吃感冒药,还想吃布洛芬,可以吗"。这些描述杂乱,药名可能是商品名、通用名或俗称混用,患者标签(孕妇?肾病?)可能根本没提到。

约束三:信息不完整时系统不能崩,也不能硬性拒绝。
真实用药场景里,患者经常不知道自己有没有某种过敏史,或者描述不清楚。如果系统遇到信息缺失就直接返回"无法判断",实用性就大打折扣;但如果靠猜测给出结论,又不安全。

有了这三个约束,架构选型就有了清晰的评判标准。


三、我们考察过的方案

方案 A:直接调用 LLM,用 Prompt 包含知识

最简单的做法:把常见的配伍禁忌规则全部写进 System Prompt,然后让 LLM 根据用户输入直接回答。

为什么否定:
这违反了约束一。LLM 的参数记忆是概率性的,不是查表。同一个问题问两次可能得到不同答案,而且 LLM 没有办法"证明"它的答案来自哪条规则。更致命的是,随着知识库更新,我们不可能每次都重新训练或调整 Prompt——知识维护成本太高。

我们做了一个简单测试:把"布洛芬 + 华法林"这对经典的高风险配伍问题用不同措辞问了几次 GPT 类模型,结论的严重程度描述差异明显。这对医疗场景来说是不可接受的。

方案 B:向量 RAG(把药物文献向量化,用语义检索)

向量 RAG 是目前最流行的 LLM 增强方案:把医药知识文档切片、向量化,用户提问时先检索最相关的文本片段,再让 LLM 基于这些片段回答。

为什么否定(或者说,为什么不够用):
用药安全检查的核心问题是药物之间的关系,而不是关于单一药物的描述。

举个具体例子:问"布洛芬 + 华法林是否有冲突",向量检索可能分别找到"布洛芬的药代动力学"和"华法林的注意事项"两段文字,但这两段文字不一定包含它们之间相互作用的明确表述——特别是当这种相互作用是间接的(A 影响代谢酶 → 影响 B 的血药浓度)时,文本里往往是分散描述的,语义检索很难把它们关联起来。

图谱天然适合表达关系(边),向量适合表达相似性(距离)。对于这个场景,我们需要的是前者。

方案 C:Multi-Agent(多智能体协同)

有人提议做多智能体:一个"分诊 Agent"负责理解用户输入,一个"药师 Agent"负责查询知识库,一个"审核 Agent"负责最终裁决,各自有专属能力。

为什么否定:
多智能体的优势在于并行处理复杂任务、各自专注不同领域。但我们的核心流程是串行的:先理解输入,再查知识图谱,再整合输出,没有需要并行的子任务。

更实际的问题是复杂度。Agent 之间的通信协议、状态传递、异常处理,在一个三个月的学期项目里会把大量时间花在"框架搭建"上,真正的业务逻辑反而做不完。申请书里写的是 Single-Agent,我赞同这个判断——在这个规模和时间约束下,Single-Agent 足够,Multi-Agent 是过度设计。


四、最终方案:为什么是 ReAct + Single-Agent + GraphRAG

把上面的分析综合起来,最终方案的设计逻辑如下:

核心思路是:用 LLM 做"理解+推理",用知识图谱做"事实裁决",两者通过 Tool Calling 连接。

ReAct 解决了什么问题

ReAct(Reason + Act)的关键在于它让 LLM 的推理过程显式化。普通的 LLM 调用是黑盒——你问,它答,中间发生了什么你不知道。ReAct 强制 LLM 在每一步先写出 Thought(我需要做什么),再决定是否触发 Action(调用哪个工具),最后处理 Observation(工具返回了什么)。

这对我们的场景有两个直接好处:

  1. 可调试性。 当系统给出错误结论时,我们能看到是哪一步出了问题——是实体提取错了,还是工具调用参数错了,还是结果整合时推理出错了。

  2. 工具调用是按需触发的。 LLM 不会每次都盲目调用所有工具。它先判断"这个问题需要查图谱吗?需要查哪些药物对?"再有针对性地发起请求。这对控制延迟和 API 消耗都有好处。

GraphRAG 解决了向量 RAG 的关系盲区

我们把药物配伍禁忌、药食冲突、人群禁忌建模成图谱:节点是实体(药物名、疾病、食物、人群标签),边是关系(配伍禁忌、增强毒性、影响吸收等),每条边带有属性(危险等级、机制描述)。

Agent 提取出用药清单后,把药物名作为参数传给图谱查询工具。工具在图数据库里做路径遍历,找到两个药物节点之间是否存在"禁忌"边。这个过程是确定性的——图谱里有就是有,没有就是没有,不存在概率性的"大概是"。

LLM 的角色变成了:理解非结构化输入 → 提取标准化实体 → 把图谱返回的结构化规则,翻译成面向用户的自然语言解释。

这个分工是合理的:LLM 擅长的事(语言理解和生成)归 LLM,LLM 不擅长的事(精确事实判断)归知识图谱。

Single-Agent 在这里的定位

整个系统只有一个 Agent 实例,但它被赋予了两个角色的职责:意图解析(理解用户在问什么)和安全审核(判断用药方案是否有问题)。

这两个职责通过 System Prompt 的设计和不同的工具配置来区分,而不是拆成两个独立的 Agent。在我们目前的规模下,这样做足够清晰,也省去了多 Agent 之间状态同步的麻烦。


五、架构示意

用文字描述一下整个请求的流转过程,便于理解各模块的分工:

用户输入(自然语言)
    ↓
FastAPI 后端接收
    ↓
ReAct Agent 启动
    ├── Thought: 识别药物实体、患者标签
    ├── Action: 调用图谱查询工具(Tool Calling)
    │       ↓
    │   知识图谱路径遍历
    │       ↓
    │   返回冲突规则 / needs_clarification 状态码
    └── Thought: 根据图谱结果生成最终结论
    ↓
如果 needs_clarification → 前端触发追问弹窗(Human-in-the-loop)
如果信息充足 → 生成红色阻断 / 蓝色建议 / 降级免责医嘱
    ↓
Vue3 前端状态机渲染结果

三个层次的职责是清晰分离的:LLM 负责语言层,知识图谱负责事实层,状态机负责前端的交互逻辑层。这三层之间通过定义好的接口通信,任何一层的实现细节变化不会传染到其他层。


六、这个方案的代价

任何技术选型都有代价,我觉得有必要诚实地说出来。

代价一:图谱的覆盖范围决定了系统的天花板。
如果某个用药冲突没有被收录进知识图谱,系统就无法发现它——不管 LLM 有多聪明。所以图谱的质量和覆盖率是这个项目最大的风险点之一。在学期项目的规模里,我们只能覆盖最常见的处方药、OTC 及典型冲突场景,无法做到全量。

代价二:实体对齐是一个脏活。
用户说"拜新同",图谱里的节点是"硝苯地平控释片";用户说"999 感冒灵",图谱里的活性成分是"对乙酰氨基酚 + 扑尔敏"。中间这个对齐过程(商品名 → 通用名 → 成分)需要额外处理,否则图谱查询会频繁漏报。这一块我们目前还没有特别好的解决方案,是后续博客会继续讨论的问题。

代价三:ReAct 的循环次数在某些情况下难以控制。
如果 LLM 在 Thought 阶段判断不够果断,可能反复触发工具调用,导致延迟增加。需要在 System Prompt 层面加约束,或者在工程层面设置最大循环轮次的硬限制。


七、小结

做完这个架构选型之后,我的直观感受是:技术选型本质上是在约束条件下做取舍,不存在"最好的方案",只有"在这个场景下最合适的方案"

向量 RAG 不是不好,在文档问答、知识检索类场景里它非常合适;Multi-Agent 不是过度设计,在需要并行处理多个复杂子任务的系统里它有明确价值。但对于我们这个项目——核心诉求是关系型事实判断、信息来源必须可溯源、三个月内要做完——ReAct + Single-Agent + GraphRAG 是目前最合理的组合。


本文为山东大学软件学院 2023 级创新实训博客,项目:面向全场景用药安全的医师助手 Agent,团队:ColdX。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐