前言

IntelliHealth 药物禁忌分析 多说明书检索 + 段落打分 的技术说明。


1.原有方案

Neo4j 未命中时,系统会走 OpenFDA 冷启动链路:
拉 FDA 说明书 → 取英文相互作用 → LLM 写中文摘要 → 写入图库

早期实现非常直接:

// 旧逻辑
.queryParam("limit", 1)
return interactions.get(0);

这带来两个问题:

① 只看了 1 份说明书

OpenFDA 搜索 drug_interactions:aspirin+AND+ibuprofen 可能命中 数千份 label,但 limit=1 只返回排序第一的 一份
这份药的主药可能是 etodolac,只是在 drug_interactions 大段文字里顺带提到 aspirin,并不是 aspirin ↔ ibuprofen 的专述

② 偏题也硬返回

尽管没有在说明 药物A 和 药物B 的校验,LLM 只能基于偏题英文写摘要,结果变成「可能增加不良反应,不建议合用」这类笼统话,远不如人工清洗数据(如 布洛芬干扰阿司匹林抗血小板作用…)准确。


解决方案 :
我们需要先从多份说明书中找对段,再交给 LLM。


2. OpenFDA接口

请求示例:

GET https://api.fda.gov/drug/label.json?search=drug_interactions:aspirin+AND+ibuprofen&limit=10

响应结构:

{
  "meta": { "results": { "total": 4264 } },
  "results": [
    {
      "openfda": { "generic_name": ["..."] },
      "drug_interactions": "很长的一段英文,或 [\"段落1\", \"段落2\"]"
    }
  ]
}

要点:

概念 说明
limit 返回 几份药品说明书,不是几个字段
results[i] 某一份独立 label
drug_interactions 该 label 下与药物相互作用相关的 长英文,可能是一个字符串,也可能是字符串数组
total: 4264 同时含 aspirin 与 ibuprofen 关键词的 label 很多,只取 1 条极易偏题

我们真正需要的,是从 drug_interactions 里截出 同时讨论 drugA 与 drugB 的那一小段,而不是整篇或第一节。


3. 新方案

图库里没有这对药的记录时,才走 OpenFDA 冷启动。

系统会先拉 排名前 10 的说明书(limit=10),对每份的 drug_interactions 解析、切分,必要时用句窗口合并,再对每个候选段 scoreInteractionSegment 打分;

先在单份 label 里选最好的一段,再在 10 份之间取 全局最高分

低于 100 分(没有一段同时提到两种药)→ 返回 null,不写库;
≥100 分 → 规则分类 → LLM 中文摘要 → 写 Neo4j。

热门药对已 manual 入库的,仍 优先读 Neo4j,不经过本链路。


4. 实现细节

实现位于 DrugInteractionService.queryOpenFda 及若干提取 helper。

4.1 拉取 10 份说明书

private static final int OPENFDA_SEARCH_LIMIT = 10;

选取为10的原因: 在全量 4000+ 条里不可能全查;10 是在 准确度与 API 耗时 之间的折中。排名靠前的 label 更可能同时包含两种药名,比 limit=1 稳得多。若 10 条仍无合格段落,返回 null,宁可查无结果,也不返回偏题段落。

4.2 兼容字段形态

OpenFDA 的 drug_interactions 可能是:

  • 单个 String(一整篇)
  • List<String>(多段)

统一转成 List<String> 文本块,避免类型强转失败或漏数据。

4.3 切分长文

FDA 说明书常见两种结构:

  1. 双换行 \n\n 分段
  2. 句号 + 大写开头(如 Aspirin When…)分小节

先尝试空行切分,再尝试 FDA_SECTION_SPLIT 正则,把一整篇 interactions拆成多个 候选段落

4.4 句窗口合并

有时两种药名分散在 相邻几句 里,单句切分拿不到同时含 A、B 的段。
算法实现:
滑动合并连续 1~4 句,仅保留 同时出现 drugA 与 drugB 的窗口,再参与打分。

4.5 单条 label 内选最优段

对切分出的所有段落(及句窗口)调用 scoreInteractionSegment,取 该 label 内最高分 的一段作为候选。

4.6 10 条 label 全局择优

遍历 10 份 results,每份一个候选段,再取 全局最高分 作为最终英文原文,供后续分类与摘要使用。


5. 打分机制

5.1 药名命中(核心)

使用整词匹配(\baspirin\b,大小写不敏感):

条件 得分
同时提到 drugA drugB +100
只提到其中一个 +20
两个都没提到 0(直接淘汰)

100 分是可靠段落的门槛MIN_ACCEPTABLE_SCORE = 100)。
只提到一种药,说明可能是etodolac 说明书里顺带提 aspirin,不能通过

5.2 风险与机制关键词(附加分)

在药名得分之上累加,用于 10 条 label 之间的 tie-break(谁更像真正的相互作用描述):

英文关键词 附加分
not recommended / contraindicated / avoid / do not +15
antiplatelet / cardiovascular / bleeding / interaction +10
concomitant / administered with +5

例子 : aspirin + ibuprofen 合格段常含 antiplateletnot generally recommended,会在 100 分基础上再加分,更容易在多条 label 中胜出。

5.3 合格标准

if (best == null || bestScore < MIN_ACCEPTABLE_SCORE) {
    return null;  // 前 10 条均无可靠段落
}
bestScore 含义 行为
≥ 100 段落同时含两种药,可信 进入分类 + LLM 摘要 + 写 Neo4j
20~99 只提到一种药或勉强相关 丢弃
0 不相关 丢弃

6. 分类与摘要

提取到合格英文段落后,仍有两步:

  1. 规则分类 classifyByRules:从英文中识别 contraindicated / avoid / dose_adjustment 等(OpenFDA的监管用语)
  2. 规则定不了 → LLM 从枚举中选 type
  3. LLM 写 edge_summary:Prompt 中带入已确定的 type,要求摘要与风险等级一致

这样 英文提取风险类型 分工明确。


总结

OpenFDA 链路是长尾药对的补充,不是替代人工清洗

之所以对这个进行优化,是因为我们数据是根据目前最新的数据集清洗的,并不能保证OpenFDA后续不会更新数据集,保证软件禁忌分析功能持续可用。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐