临床试验协议:从自然语言到条件树
“未接受过既往靶向治疗,除非辅助治疗在复发前 12 个月以上完成。”协调员读这句话时,会自然追问:unless 修饰哪一段?12 个月从哪一个日期开始算?模型若直接给“符合/不符合”,这些决定都被藏进了一个不可检查的答案。
这类句子不是一个标签,而是一棵包含逻辑、时间、例外和医学概念的条件树。直接问模型“患者是否符合”会把解析错误与患者证据错误混在一起,无法定位。
来龙去脉与数据现实
Section titled “来龙去脉与数据现实”ClinicalTrials.gov API v2 提供结构化 study record 和 OpenAPI 描述,并按工作日刷新;客户端应记录 dataTimestamp 和协议版本。S06S07 但 eligibilityCriteria 仍包含大量自然语言。TrialGPT 将匹配拆到 criterion 级,是重要进步;生产系统还需先把每条 criterion 变成版本冻结、可人工校验的结构。S30S31
痛点包括:列表层级丢失、AND/OR 范围模糊、否定嵌套、时间窗参照点不清、单位省略、不同版本协议变化。PDF/OCR 还可能把项目符号和上下标读错。
第一阶段是忠实切分:保留章节、列表、原文、页码/字段路径和版本,不做医学推断。第二阶段是语义结构化:抽取 inclusion/exclusion、operator、children、concept、value/unit、temporal constraint、exception 和 required evidence。
解析器只提出草案;研究协调员在 UI 中对照原文修改并“冻结”。匹配任务永远引用冻结版本。协议更新时生成 diff,不覆盖旧树。
一个条件树节点示例:all(age >= 18, diagnosis == NSCLC, any(EGFR_exon19, EGFR_L858R), not(prior_targeted_therapy within lifetime))。这里 any 的作用域必须来自原文结构,不能由模型想当然补齐。
| 方案 | 强项 | 主要缺口 |
|---|---|---|
| ClinicalTrials.gov API | 官方、版本和结构入口 S06 | 标准文本仍需语义解析 |
| TrialGPT Matching | criterion 级解释 S30 | 需本地协议版本与人工冻结 |
| 纯正则 | 数字、单位、简单年龄稳定 | 嵌套逻辑和例外脆弱 |
| LLM structured output | 泛化强 | 必须 schema、原文定位和人审 |
python3 labs/run_lab.py --lab 09实验解析一个受控的小型语法:age>=18 AND (egfr=ex19 OR egfr=l858r),生成条件树并对 3 个合成病例求值。它不是通用协议 NLP;其价值是展示解析与求值分离、原文与 AST 同时保留。
先对照原句看一次 AST,再把括号或 OR 的位置改掉,最后比较哪一类病例的结果变化。实验重点不是让语法覆盖更多文本,而是直观看到“作用域错一层,资格就可能反过来”。
一句话里藏着多少工程决定
Section titled “一句话里藏着多少工程决定”考虑合成标准:“Age ≥ 18 years; histologically confirmed advanced NSCLC with EGFR exon 19 deletion or L858R; no prior EGFR-targeted therapy, except adjuvant therapy completed >12 months before recurrence.” 这里至少有年龄、疾病、分期、组织学确认、两种变异、既往用药、治疗意图、完成到复发的时间差和例外。
忠实切分阶段只恢复列表和原文跨度。语义阶段生成顶层 all:年龄条件、诊断条件、any(ex19,L858R) 和包含例外的既往治疗条件。每个叶节点声明 Evidence requirement。解析器若不知道“advanced”映射哪些 stage,不自行补充,写 concept_status=needs_review。如果复发日期缺失,例外无法求值,最终是 unknown。
这比把整句嵌入向量库多做了很多工作,却换来关键能力:协调员可以逐节点修改,verifier 可以逐节点测试,协议版本更新时可以显示 AST diff,错误能定位到解析、取证还是求值。
解析器的四层架构
Section titled “解析器的四层架构”第一层文档摄取处理 HTML/API/PDF,保留页码、标题和列表层级;第二层 criterion segmenter 区分 inclusion/exclusion 并生成稳定 id;第三层模型/规则把单条 criterion 转成 schema;第四层 semantic validator 检查单位、算子、逻辑作用域、否定、时间参照和术语。人审冻结后才进入 matching。
模型 structured output 适合第三层,但必须让它返回 source span。对于年龄、数值、单位等可确定部分,规则可与模型并行抽取并做差异检查。两者不一致时不自动选择模型。对每种错误建立 taxonomy:split、polarity、scope、concept、numeric、temporal、exception、hallucinated。评测不是只看整条 JSON 是否完全一致,而是分字段并特别关注会改变资格判断的严重错误。
协议版本与变更
Section titled “协议版本与变更”ClinicalTrials.gov 数据会更新,S06 研究协议也可能 amendment。ProtocolVersion 包含来源、NCT id、抓取时间、内容 hash 和状态;每条 TrialCriterion id 绑定版本。新版本进入 diff 队列:新增/删除/修改、逻辑变化、数值变化、文本排版变化。人审决定新版本何时生效,进行中的任务仍能重放旧版本。
若只是 API 格式变化,migration adapter 负责兼容;若 eligibility 语义变化,必须重新评估受影响任务。把两者混为“更新了一次数据”会让历史审计失真。官方迁移指南正说明客户端不能假定接口永久不变。S07
抗提示注入与不可信协议
Section titled “抗提示注入与不可信协议”协议是数据,不是指令。解析 prompt 将原文置于带来源的 data block,输出只能匹配 schema;document 中出现“ignore previous instructions”也只能成为文本。更重要的是解析 Agent 没有患者读取和写工具,即便被影响也无法扩大权限。chapter 22 的红队场景会把这条不变量变成测试。
人工审核界面左侧显示原文和定位,右侧显示条件树;点击叶节点高亮对应 span。审核者可改算子、概念、时间窗,所有修改以 patch 事件记录。冻结按钮展示版本、未解决 warning 和受影响 criteria,避免在模糊字段仍存在时误发布。机器 confidence 只作排序,不隐藏低置信结果。
章末练习:把本章示例中的 or 改成“EGFR exon 19 deletion or L858R with no prior targeted therapy unless…”,画出两种可能作用域并说明各自会影响哪些患者。只要原文无法唯一决定,答案就应是 protocol_ambiguous + 人工确认,而不是选择更常见的解释。
- 模型输出 JSON 通过 schema 不表示逻辑作用域正确。
- 不能用当前协议重算历史判断而不保留旧版本。
- OCR 置信度低、逻辑歧义和未知术语必须进入人工队列。
- 系统只做候选筛选,不把结构化树视为具有法律效力的协议替代物。
完整的来源类型、用途与边界见教材来源注册表。
资料与延伸阅读
Section titled “资料与延伸阅读”- ClinicalTrials.gov API 与 API Migration Guide:从官方字段、刷新与接口变更开始,不假设抓到的文本永远不变。
- TrialGPT 论文 与 作者代码:重点看 criterion 级匹配的拆法;本章额外要求版本冻结和人工审核。
- OWASP Top 10 for LLM Applications:协议、PDF 和网页都是不可信输入,提示注入防护不能只靠一段系统提示。