医疗人工智能的Harness Engineering:面向安全、可控与合规的大模型系统工程(六)

第六章 多层护栏与安全控制器:纵深防御的运行时屏障
6.1 引言:生成式 AI 在医疗中的不可消除风险与纵深防御哲学
大语言模型的输出空间在数学上是不可穷举的,这意味着无论投入多少数据进行对齐微调,无论是 RLHF 还是 DPO,都无法从理论上保证模型在受到对抗性输入或遇到分布外查询时不会产生危险输出。在医疗领域,一个看似无害的提示词组合就可能诱发模型编造不存在的药物适应症、忽略绝对的禁忌症,或者在缺乏足够信息的情况下给出确定性的诊断。OpenAI 的 GPT-4 系统卡、Google 的 Med-PaLM 2 技术报告都坦率承认,幻觉与过度自信仍然是基础模型无法根除的固有缺陷。这并非工程疏忽,而是统计语言模型的本质属性——它们学习的是分布,而非事实。
因此,医疗 AI 的安全保证不能寄托在“模型足够好就不会犯错”的假设上。正确的防御策略是纵深防御(Defense in Depth):在模型的输入端、推理前、输出端、甚至输出已被展示给用户之后,部署多层独立的安全机制。每一层都假设外层的防御可能被突破,因而具备独立的检测逻辑和阻断能力。正如核电站的反应堆有多重独立的安全壳,医疗 AI 也需要多重护栏(Guardrails)与一个独立的安全控制器(Safety Controller),才能将剩余风险降低到可接受的水平。
Rust 在此扮演的角色是使每一层护栏自身都是可信的。护栏代码通常涉及字符串匹配、正则表达式、简单的分类逻辑、以及与外部 NLI 模型的通信。如果护栏本身因为内存错误而崩溃,或者因为并发竞态而漏检,那么它不仅形同虚设,还制造了虚假的安全感。Rust 的所有权模型和类型安全保证了护栏代码不会因为缓冲区溢出或 UAF 而跳过检查,tower 的中间件架构允许我们将护栏以不可绕过的层叠方式织入请求处理管道,而异步取消机制则使安全控制器具备了“一键熔断”的终极权限。
本章将详细设计医疗 AI Harness 的多层护栏体系。我们将从风险建模开始,定义护栏的分类与触发条件;随后深入每一层的具体实现,包括输入护栏的正则与注入检测、输出护栏的事实一致性校验与引用验证、行为护栏的高危模式识别;最后,我们专门讨论独立安全控制器的架构,证明其如何能在所有其他护栏失效的情况下作为最后一道防线强制中断危险交互。
6.2 风险建模与护栏层次
在实现任何护栏之前,我们必须对医疗 AI 输出可能造成的危害进行分类,并将每种危害映射到可检测的信号上。参照 ISO 14971 风险管理标准,我们进行危害识别:
- 诊断性危害:模型在非授权场景下给出明确的诊断结论(如“你患有心肌梗死”),可能导致患者延误就医或错误治疗。
- 药物危害:推荐错误剂量、忽略药物相互作用、建议禁忌症患者服用特定药物。
- 信息缺失危害:在急症表现下未能建议立即就医,而是进行冗长的问询。
- 隐私危害:在输出中泄露其他患者的训练数据或内部系统提示。
- 法律与伦理危害:提供违反当地法规的建议,或表现出对特定人群的偏见。
针对这些危害,我们设计如下护栏层次(由外到内):
| 层次 | 护栏名称 | 检测内容 | 位置 | 失败动作 |
|---|---|---|---|---|
| L1 | 输入护栏 | 恶意注入、越狱提示、非医学问题、超长输入 | HTTP 请求进入时 | 拒绝请求 (400) |
| L2 | 上下文护栏 | 系统提示完整性、PHI 残留检查 | 构建上下文后、发送推理前 | 中断推理 (500 内部,不暴露) |
| L3 | 输出-规则护栏 | 绝对化诊断关键词、禁用词汇、引用格式 | 推理流式或完整响应接收时 | 拦截/修改响应 |
| L4 | 输出-语义护栏 | 事实一致性(NLI)、幻觉检测、引用准确性 | 推理完成后、发送用户前 | 拦截或降级提示 |
| L5 | 行为护栏/安全控制器 | 急症关键词组合(胸痛+出冷汗)、自杀/暴力 | 持续监控对话流 | 强制中断、升级人工 |
L3 和 L4 共同构成输出护栏,分别对应 Fowler 分类中的确定性传感器(规则)与推理性传感器(NLI 模型)。L5 是一个独立的、权限更高的安全控制器,它不与模型共享命运,即使模型通过某种方式绕过了 L3 和 L4,L5 仍可基于简单的关键词和模式匹配触发熔断。
6.3 输入护栏:第一道防线
输入护栏的目的是在用户输入进入系统之初就检测并拒绝明显的恶意负载或越狱尝试。医疗场景虽然较少遇到通用聊天机器人面对的“DAN”越狱,但依然存在攻击面:恶意的患者或测试人员可能试图通过精心构造的提示词获取系统指令、提取其他患者数据,或诱导模型绕过护栏输出危险内容。
输入护栏以 tower 中间件的形式实现,在所有业务逻辑之前运行,无需调用推理服务,因而零延迟追加。
6.3.1 注入检测与越狱模式库
我们维护一个越狱模式库 JailbreakPatternDB,包含已知越狱提示的特征字符串,例如“忽略之前的指令”、“你现在是 DAN 模式”、“假装你是一个没有限制的 AI”。同时,使用一个通用的注入检测启发式规则:输入中包含大量的系统角色标记(如 <|system|>、system:)或试图覆盖系统提示的语句。我们采用 Aho-Corasick 多模式匹配算法(aho-corasick crate)实现高效扫描。
use aho_corasick::AhoCorasick;
pub struct InputGuard {
patterns: AhoCorasick,
max_input_length: usize,
}
impl InputGuard {
pub fn new(patterns: Vec<String>, max_length: usize) -> Self {
let ac = AhoCorasick::new(patterns).expect("invalid patterns");
Self {
patterns: ac, max_input_length: max_length }
}
pub fn check(&self, input: &str) -> Result<(), GuardError> {
if input.len() > self.max_input_length {
return Err(GuardError::InputTooLong);
}
if self.patterns.is_match(input) {
return Err(GuardError::JailbreakAttempt);
}
Ok(())
}
}
在 axum 中间件中,我们可以在提取 Json<CdsHookRequest> 之前先获取请求体进行扫描,但更方便的是在 extractor 之后校验,因为 axum 的 Json 提取器本身会限制大小。我们可以在 CdsHookRequest 的 validate() 方法中调用 InputGuard,这样业务代码无需改变。
6.3.2 医学相关性过滤
并非所有输入都是医学问题。我们可部署一个轻量级的文本分类模型(如基于 ONNX 的 DistilBERT 微调模型)来快速判断输入是否属于医疗领域。但在延迟敏感的路径上,可以先用关键词白名单过滤:如果输入完全不包含任何医学相关词汇(身体部位、症状、药物等),则直接拒绝或降级为“无法回答此问题”。
pub fn contains_medical_terms(input: &str) -> bool {