OpenMed 无 PHI 遥测No-PHI Telemetry本地优先管线的聚合指标隐私边界设计与实践【免费下载链接】openmedLocal-first healthcare AI: clinical NER HIPAA PII de-identification that runs 100% on-device. 2,200 medical models, 21 languages, Apple MLX Python, no cloud, no patient data leaving your network. Apache-2.0项目地址: https://gitcode.com/GitHub_Trending/ope/openmed导读OpenMed 的定位是 local-first 医疗 AI临床 NER 与 HIPAA PII 去标识化全部在设备端运行患者数据不出本地网络。在这个前提下任何健康检查与延迟观测都必须同时满足两个条件——有用与不含受保护健康信息PHI。openmed.core.no_phi_telemetry.NoPHITelemetryExporter就是为此设计的一个刻意精简的遥测边界它只在内存中维护聚合计数器与固定分桶的延迟直方图export()仅做格式化、绝不发起任何网络调用。读完本文你将掌握如何用类型化方法安全记录管线健康指标、为什么所有维度都必须是有限白名单、异常如何只看类型不读消息地归类以及如何把同一份快照渲染为 JSON 或 Prometheus 文本供自有的采集器消费。设计动机本地优先管线需要什么样的遥测OpenMed 的管线会处理真实的临床文本其中包含姓名、病历号、诊断、检查结果等敏感内容。传统的遥测库往往允许任意 label、任意字段一旦调用方不小心把实体文本、prompt 或模型 ID 塞进指标这些数据就会随日志或监控系统流出设备直接破坏no PHI leaving your network的承诺。因此该模块在 openmed/core/no_phi_telemetry.py 的开头就明确了自身的定位Deterministic, aggregate-only telemetry for local OpenMed pipelines. This module is a deliberately small privacy boundary.面向本地管线的确定性、纯聚合遥测这是一个刻意缩小的隐私边界。核心承诺有三条不记录不落盘、不打日志、不发网络请求不持久化所有状态只存在于内存clear()即可清空不发送export()/export_json()/render_prometheus()都只是把快照格式化为普通字典、JSON 或文本真正的外送动作必须由应用自己显式完成。单元测试 tests/unit/core/test_no_phi_telemetry.py 甚至用monkeypatch.setattr(socket, socket, deny_socket)注入了一个禁止创建 socket 的桩函数然后验证export_json()仍然正常工作——用测试证明导出绝不触碰网络。快速上手类型化记录一个管线结果文档推荐优先使用类型化方法record_pipeline()。下面是最小可用示例from openmed.core.no_phi_telemetry import NoPHITelemetryExporter telemetry NoPHITelemetryExporter() telemetry.record_pipeline( stageemit, statussuccess, methodmask, latency_ms18.4, entity_count2, ) snapshot telemetry.export()stage管线阶段来自固定白名单见下文statussuccess/error/cancelled/rejectedmethod脱敏方法如mask、remove、replace、hash、shift_dates等latency_ms以毫秒传入的延迟内部转换为秒后再进入直方图entity_count本次调用识别出的实体数量聚合计数。export()返回一个普通字典结构为{schema_version: 1, counters: [...], latencies: [...]}。由于导出不含时间戳、不含原始文本任何两次内容相同的调用序列都会得到完全一致的输出这一确定性由 TelemetrySnapshot 的规范化校验保证。有限的指标面四个计数器族 一个直方图导出器暴露的指标面是固定且封闭的绝不因输入数据长出新标签。四个计数器族定义在CounterName枚举中openmed/core/no_phi_telemetry.py#L52-L58枚举成员指标名语义PIPELINE_RUNSopenmed_pipeline_runs_total管线运行次数PIPELINE_FAILURESopenmed_pipeline_failures_total失败次数status 为error/cancelled/rejected时同时累计PIPELINE_REJECTIONSopenmed_pipeline_rejections_total被拒次数仅 status 为rejected时累计PIPELINE_ENTITIESopenmed_pipeline_entities_total实体数量聚合值延迟则统一导出为一个固定分桶直方图openmed_pipeline_latency_seconds。默认分桶定义在DEFAULT_LATENCY_BUCKETS_SECONDSopenmed/core/no_phi_telemetry.py#L34-L48DEFAULT_LATENCY_BUCKETS_SECONDS: tuple[float, ...] ( 0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0, 60.0, )从 5ms 到 60s 共 13 个桶覆盖从单次实体识别到长文档批处理的典型延迟区间。构造时可通过latency_buckets_seconds(...)自定义桶边界但必须满足非空元组、桶数不超过 32、严格递增、均为有限正数由_coerce_latency_buckets强制校验openmed/core/no_phi_telemetry.py#L839-L860。维度白名单识别不出来的值一律归入other直方图和计数器允许的唯一维度键只有四个DimensionName枚举stagenormalize、language_script、doc_type_section、deterministic_detectors、fast_pii_model、clinical_phi_model、span_arbitration、policy_actions、safety_sweep、emit、pipelinestatussuccess、error、cancelled、rejectedmethodmask、aadhaar_mask、remove、replace、hash、shift_dates、format_preserveexception_categorycancelled、capacity、configuration、dependency、internal、network、timeout、validation。每个维度都有有限 allowlistopenmed/core/no_phi_telemetry.py#L72-L121。任何未列出的值——无论来自拼写错误还是恶意输入——都会被_normalize_dimension映射为other异常类别则归为unknown而不会成为新的 label。这一点在测试中有直接验证tests/unit/core/test_no_phi_telemetry.py#L30-L56把stagesynthetic-unbounded-stage、methodsynthetic-model-id传进去导出的维度全部变成other/unknown计数与延迟仍正常记录。文档提醒的深层含义维度值不是可自由取值的标签空间而是枚举语义的受控展开。模型 ID、实体文本这类高熵值在入口处就被归一化吞掉了。两类记录入口类型化方法 vs. mapping 事件record_pipeline()推荐的类型化入口参数全部为关键字参数签名如下openmed/core/no_phi_telemetry.py#L429-L440record_pipeline( *, stage: str pipeline, status: str success, method: str OTHER_DIMENSION_VALUE, latency_ms: Real | None None, latency_seconds: Real | None None, entity_count: int | None None, exception: object | None None, exception_category: object | None None, )值得注意的约束latency_ms与latency_seconds互斥同时传两个会抛TelemetrySchemaErrortelemetry latency has multiple unitsexception与exception_category互斥同时传会抛telemetry exception category has multiple sourcesentity_count上限为 1000 万MAX_ENTITY_COUNT单次record_pipeline会自动联动多个计数器runs 恒 1失败状态再 failuresrejected状态再 rejectionsentity_count 0时再 entities。底层原子性先校验、后生效绝不产生半次更新所有记录最终汇入_record_batch_lockedopenmed/core/no_phi_telemetry.py#L775-L836其工作方式是先完整投影、逐项校验全部通过后才在threading.RLock保护下一次性落账先把计数器增量按(counter, dimensions)聚合任何一条超过有符号 64 位上限MAX_COUNTER_VALUE (1 63) - 1就整体拒绝再投影延迟组的 count 与 sum内部用Decimal累加以避免浮点漂移超出MAX_AGGREGATE_LATENCY_SECONDS也整体拒绝全部校验通过后统一应用计数器增量与直方图更新。因此文档所说的被拒绝的事件不可能留下部分更新是源码级保证record()的输入映射会先经过_snapshot_mapping拷贝成有界 dictopenmed/core/no_phi_telemetry.py#L150-L175任何读取异常都转成不含原始内容的TelemetrySchemaError。测试 tests/unit/core/test_no_phi_telemetry.py#L226-L241 与 tests/unit/core/test_no_phi_telemetry.py#L305-L318 专门验证了失败后导出仍为空/仍为原值。record()/record_event()映射式入口schema 更严格在集成边界不方便调用类型化方法时可以使用 mapping 版本openmed/core/no_phi_telemetry.py#L606-L678。它只接受一份已文档化的封闭 schema允许的顶层键是有限的 13 个amount、counter、dimensions、entity_count、exception、exception_category、latency_ms、latency_seconds、method、name、stage、status、value_EVENT_KEYSopenmed/core/no_phi_telemetry.py#L123-L139。telemetry.record( { name: CounterName.PIPELINE_RUNS.value, value: 2, stage: emit, status: success, method: mask, latency_ms: 5, } )counter与name等价amount与value等价但同时出现会被判为重复字段出现未批准的键抛出UnapprovedTelemetryKeyErrorTelemetrySchemaError的子类错误消息是通用文案如 telemetry event contains unapproved fields绝不回显被拒绝的键或值。测试 tests/unit/core/test_no_phi_telemetry.py#L59-L90 用secret_key synthetic_patient_identifier验证了异常文本中不包含该键名。record_pipeline_result()只读聚合字段不触碰文本当管线返回结构化结果对象时用record_pipeline_result()直接从结果中提取聚合字段openmed/core/no_phi_telemetry.py#L506-L604telemetry.record_pipeline_result(result, methodmask)它只读取两个属性stage_durations_ms映射把每个阶段的毫秒时长转成秒后分别作为带stage维度的直方图观测同时对各阶段求和额外记一条stagepipeline的聚合延迟spans只取len(spans)作为实体数绝不拷贝、哈希或序列化其中的文本。其余属性包括可能的原文、脱敏文本一律不读。校验还包括stage_durations_ms必须是 Mapping 且条目数不超过 64MAX_RESULT_STAGE_DURATIONSspans必须是序列字符串/字节/映射会被拒绝见测试 tests/unit/core/test_no_phi_telemetry.py#L272-L302。毫秒转秒的行为在 tests/unit/core/test_no_phi_telemetry.py#L208-L223 中验证{emit: 250.0}毫秒导出为emit与pipeline两条sum_seconds 0.25。异常处理只按类型归类永远不读消息exception接受异常实例或异常类归类完全基于类型openmed/core/no_phi_telemetry.py#L178-L226 的sanitize_exception_category异常类型归类asyncio.CancelledErrorcancelledTimeoutErrortimeoutMemoryErrorcapacityConnectionErrornetworkImportErrordependencyValueError/TypeError/KeyErrorvalidation其他BaseException子类internal字符串/其他对象/无法识别unknown实现中str(value)永远不会被调用因此包含 prompt、患者标识或实体文本的异常消息不可能进入遥测载荷或错误文本。字符串类别输入长度超过 64 也直接归unknown。测试 tests/unit/core/test_no_phi_telemetry.py#L93-L110 传入ValueError(secret_message)随后断言secret_message既不出现在export_json()中也不出现在render_prometheus()输出里。快照与导出确定性字典、canonical JSON 与 Prometheus 文本export()与export_json()export()→ 全新字典结构固定为{schema_version: 1, counters: [...], latencies: [...]}export_json()→ 调用TelemetrySnapshot.to_json()openmed/core/no_phi_telemetry.py#L336-L345sort_keysTrue 固定分隔符 allow_nanFalse保证稳定键序与样本序——测试证明记录顺序无关两个以相反顺序记录的导出器输出完全一致tests/unit/core/test_no_phi_telemetry.py#L345-L353。公开的快照类型CounterSample/LatencySample/TelemetrySnapshot在__post_init__中做全套校验openmed/core/no_phi_telemetry.py#L229-L345指标名必须是批准的枚举、维度必须是白名单内且按序去重、桶边界必须单调、桶数必须等于len(buckets)且最后一个必须是(Inf, count)、快照样本总数不超过 40000MAX_SNAPSHOT_SAMPLES。也就是说即使调用方手工构造快照也无法注入任意标签或破坏规范序。render_prometheus()格式化为文本但不接触采集器telemetry.render_prometheus()该方法把同一份白名单快照渲染为标准的 Prometheus 文本格式openmed/core/no_phi_telemetry.py#L729-L773每个计数器族输出一条# HELP与# TYPE注释及对应样本行直方图输出_bucket{le...}、_count与_sum系列label 值中的\、\n、均被转义。它不配置、不连接任何采集端点属于纯粹的格式化工具——文档与源码都强调It does not configure or contact a collector itself. 采集器地址、抓取间隔等完全由应用自己的监控体系决定。边界数值速查以下是本模块所有硬性上限定义于 openmed/core/no_phi_telemetry.py#L24-L49测试逐条覆盖常量值含义SCHEMA_VERSION1快照 schema 版本不匹配即拒绝MAX_COUNTER_VALUE(1 63) - 1单个计数器/直方图 count 上限MAX_ENTITY_COUNT10,000,000单次调用的实体计数上限MAX_LATENCY_SECONDS604,800.07 天单次延迟观测上限MAX_AGGREGATE_LATENCY_SECONDS1e15直方图 sum 累计上限MAX_RESULT_STAGE_DURATIONS64stage_durations_ms最大条目数MAX_LATENCY_BUCKETS32自定义分桶最大桶数MAX_SNAPSHOT_SAMPLES40,000单次快照样本总数上限集成建议与边界声明把该导出器接入自有监控栈的推荐模式是本地聚合 拉取式导出在管线进程内创建单例NoPHITelemetryExporter内部锁保证多线程安全各阶段结束时调用record_pipeline(...)或record_pipeline_result(...)由应用侧定时任务如每 30 秒调用export_json()或render_prometheus()把结果推送到自有采集端点或写入本地文件——这一步是唯一可能产生网络/磁盘行为的地方且完全由应用掌控监控系统只消费固定指标名与有限维度值无法从指标中重建任何 PHI。文档在结尾给出了明确的使用边界This utility provides an aggregate telemetry contract; it is not a compliance certification or a clinical decision guarantee.——它提供的是一个聚合遥测契约既不是合规认证也不是临床决策担保。它负责的是遥测本身不携带 PHI而流程合规仍需由 docs/compliance 目录下的策略与审计体系来保证。参考实现核心实现openmed/core/no_phi_telemetry.py单元测试含网络禁用、键不回显、原子性、边界溢出等用例tests/unit/core/test_no_phi_telemetry.py关联文档docs/operations/no-phi-telemetry.md【免费下载链接】openmedLocal-first healthcare AI: clinical NER HIPAA PII de-identification that runs 100% on-device. 2,200 medical models, 21 languages, Apple MLX Python, no cloud, no patient data leaving your network. Apache-2.0项目地址: https://gitcode.com/GitHub_Trending/ope/openmed创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考