医疗人工智能的Harness Engineering:面向安全、可控与合规的大模型系统工程(七) 第七章 可观测性与审计追踪:构建不可篡改的证据链7.1 引言:当每一次决策都可能对簿公堂医疗 AI 系统的输出不是社交媒体上的娱乐内容,而是可能被呈交至医疗事故鉴定委员会或法庭的证据。美国 HIPAA 安全规则 (45 CFR § 164.312(b)) 明确要求受保护实体“实施硬件、软件和/或过程机制,记录和检查包含或使用受保护健康信息的信息系统活动”。欧盟医疗器械法规 (MDR 2017/745) 同样强制要求制造商提供充分的临床评价和上市后监测数据,其中每一项 AI 辅助决策的完整输入输出记录都是不可或缺的溯源材料。然而,传统软件系统的日志往往是一种“尽力而为”的附属品:开发者在关键路径上随意添加print或logger.info(),日志格式不一致,级别混乱,在异常代码路径中可能被遗漏,且存储为纯文本文件,易于被意外删除或恶意篡改。当医疗 AI Harness 介入临床决策时,这种松散的日志文化将直接演变为法律责任风险。一条缺失的日志可能意味着无法证明医生是否阅读了 AI 建议,无法证明护栏是否曾被触发,无法证明模型输出的来源是否可靠——在诉讼中,这等同于“不能证明即未发生”。因此,可观测性(Observability)与审计追踪(Audit Trail)在医疗 AI Harness 中不是可选的运维辅助,而是与模型推理同等重要的一等公民功能。它们必须满足以下硬性要求:不可绕过性:任何通过 Harness 的数据访问、模型调用、工具执行、护栏拦截,无论正常或异常路径,都必须产生对应的审计事件。遗漏审计记录的代码必须在编译期或测试阶段被检测出来。完整性:审计记录必须包含关联本次交互所需的所有上下文:谁、何时、对哪个患者、执行了何种操作、输入什么、输出什么、引用了哪些证据、模型版本、是否经过人工确认。不可篡改性:日志一旦写入,必须防止任何主体(包括系统管理员)进行修改或删除。任何篡改尝试必须能被检测到。隐私保护:审计日志本身可能包含受保护健康信息(PHI),因此必须受到同等的加密和访问控制保护。在不损害可追溯性的前提下,应尽量将 PHI 最小化或脱敏存储。性能与成本效率:审计操作不能成为系统的吞吐量瓶颈。日志的采集、传输、存储、查询必须高效,并支持分级存储策略(热存储用于近期查询,冷存储用于长期归档)。本章将展示如何利用 Rust 的tracing生态、自定义类型系统扩展(AuditedT)、以及加密存储后端,在医疗 AI Harness 中构建一套符合上述五条原则的可观测性与审计追踪子系统。我们将从遥测基础设施开始,逐步深入到审计专用类型的设计,最后讨论长期存储与合规报告。7.2 可观测性基础:tracing生态与结构化遥测Rust 的tracingcrate 提供了一种结构化、低开销的诊断框架。与传统的logcrate 相比,tracing的核心抽象是span(表示一个有开始和结束的操作区间)和event(表示某个时刻发生的事件)。两者都可以携带丰富的结构化字段,并通过可组合的Subscriber/Layer将数据导出到各种后端(标准输出、文件、OpenTelemetry Collector、Jaeger 等)。7.2.1 为医疗领域定制 SpanHarness 的每个请求至少应包含以下 span 层次:request (request_id, user_id, patient_id) ├── authentication (user_id) ├── context_building (tokens_used, chunks_retrieved) ├── inference (model_id, prompt_tokens, completion_tokens) │ ├── tool_call (tool_name, params_hash, duration) │ └── guard_check (guard_name, violation) ├── guardrail_pipeline (rule_violations, factual_result) └── response (status_code, latency)通过#[tracing::instrument]属性宏,我们可以几乎无侵入地为函数添加 span:#[tracing::instrument( skip(self, token), fields( request_id = %ctx.request_id, user_id = %ctx.user.sub, patient_id = %ctx.patient_id.as_ref().map(|id| id.0.as_str()).unwrap_or("none"), ) )]pubasyncfnhandle_message(self,ctx:RequestContext,user_message:String,token:CancellationToken,)-ResultAgentResponse,AgentError{// ...}宏会自动在函数入口创建 span,并在函数退出时关闭,记录耗时。skip参数避免打印大对象的 debug 表示,而fields按值或引用记录关键业务标识符。7.2.2 与 OpenTelemetry 集成tracing-opentelemetrycrate 提供了将tracing的 span 和 event 导出为 OpenTelemetry 格式的Layer。通过配置tracing_subscriber,我们可以将遥测数据同时发送到多个后端:usetracing_subscriber::layer::SubscriberExt;usetracing_subscriber::Registry;usetracing_opentelemetry::OpenTelemetryLayer;useopentelemetry::sdk::{traceassdktrace,Resource};useopentelemetry_otlp::WithExportConfig;lettracer=opentelemetry_otlp::new_pipeline().tracing().with_exporter(opentelemetry_otlp::new_exporter().tonic().with_endpoint("http://otel-collector:4317")).with_trace_config(sdktrace::config().with_resource(Resource::new(vec![KeyValue::new("service.name","medical-harness"),]))).install_batch(opentelemetry::runtime::Tokio)?;lettelemetry_layer=OpenTelemetryLayer::new(tracer);letsubscriber=Registry::default().with(telemetry_layer)