
OpenMed OpenSearch 摄取脱敏处理器本地优先的文档级 PII 脱敏桥接实践【免费下载链接】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/openmedOpenMed 提供了一款面向 Python 侧 OpenSearch 摄取桥ingest bridge的轻量本地优先适配器OpenSearchRedactionProcessor。它不依赖opensearch-py、不创建客户端、不发起任何网络请求只接收一份文档映射mapping、返回一份脱敏副本让桥接程序把返回值直接写回摄取文档。本文将围绕该处理器讲解字段选择、边界约束、诊断报告、默认脱敏器行为与可注入自定义脱敏器并结合仓库源码与测试用例说明其安全设计原理帮助你在 Elasticsearch/OpenSearch 摄取管线中落地数据不出本机的 HIPAA PII 脱敏。设计定位为什么需要无客户端的摄取脱敏器在 OpenSearch/Elasticsearch 的摄取管线ingest pipeline中数据通常先进入 broker 或脚本化的桥接层再写入索引。若在桥接层直接引入opensearch-py并构造客户端会带来额外的依赖、凭据管理与网络出口若直接调用云端脱敏服务则把敏感原文送出了本机网络。OpenMed 的解法是提供一个纯文档级函数式处理器。从源码模块 docstring 可以看到它实现 Python 侧 OpenSearch 摄取桥所需的小型文档契约不导入opensearch-py或创建客户端只处理显式配置的文本字段。其核心 API 为见 openmed/interop/opensearch.pyOpenSearchRedactionProcessor处理器类一次构造、多次复用execute(document)摄取桥入口方法返回脱敏副本process(document)与execute等价的别名process_with_report(document)返回(脱敏副本, RedactionReport)诊断对redact_document(...)一次性one-shot便捷函数内部构造处理器后直接process见 openmed/interop/opensearch.py。此外模块末尾还导出了别名OpenMedRedactionProcessor OpenSearchRedactionProcessor见 openmed/interop/opensearch.py两者等价。处理器注册在 OpenMed 的互操作适配器注册表中adapter_spec(opensearch)返回的元数据声明其extra为空、不引入第三方依赖见 openmed/interop/init.py并可通过get_adapter(opensearch)惰性加载。测试 tests/unit/interop/test_opensearch.py 明确验证了注册表中暴露 opensearch 且无客户端依赖这一性质。配置选择字段dotted 路径、缺失字段与边界约束构造参数一览处理器全部参数均为关键字参数见 openmed/interop/opensearch.py参数默认值说明fields(text,)一个字段名或字段名序列支持a.bdotted 路径寻址嵌套对象fieldNonefields的单数别名二者不可同时传入policyhipaa_safe_harborOpenMed 策略配置文件名或别名构造时即校验methodmask传给脱敏器的脱敏方法ignore_missingFalse选中字段缺失时是否跳过为False时抛出OpenSearchRedactionErrordeidentifierNone可选本地可调用对象替代 OpenMed 默认脱敏器用于预加载模型或离线测试deidentify_kwargsNone传给本地脱敏器的额外选项映射字段名校验规则来自_normalize_fields见 openmed/interop/opensearch.py最多 64 个字段、路径最多 512 字符、最多 32 段、每段最多 128 个可打印字符且不允许重复字段。选中字段的值必须是字符串、None或由字符串与None组成的 list/tuple其他类型如嵌套 dict、嵌套序列会以稳定错误selected field must contain text拒绝测试见 tests/unit/interop/test_opensearch.py。基本用法示例from openmed.interop.opensearch import OpenSearchRedactionProcessor processor OpenSearchRedactionProcessor( fields(message, clinical.note), policyhipaa_safe_harbor, ) document { message: Synthetic Person called synthetic-555-0100, clinical: {note: Synthetic Person has a follow-up visit.}, index_tag: keep unchanged, } redacted_document processor.execute(document)执行后只有message与clinical.note被处理index_tag及所有未选字段原样保留原始输入document不被修改——处理器总是先做一次有界、分离的深拷贝_copy_document见 openmed/interop/opensearch.py再在副本上写入结果。测试 tests/unit/interop/test_opensearch.py 验证了仅脱敏选中的嵌套字段且输入保持不变。缺失字段处理默认情况下选中字段在文档中不存在时会抛出不含任何原文的OpenSearchRedactionError(selected field is missing)。当字段为可选字段时设置ignore_missingTrue即可跳过。测试 tests/unit/interop/test_opensearch.py 还演示了在完全阻断 socket 出网monkeypatch 掉socket.create_connection与socket.connect的前提下处理器依然正常工作实证了无网络出口承诺。文档边界约束处理器对不可信输入执行严格的边界检查常量定义见 openmed/interop/opensearch.py文档嵌套深度最多32 层_MAX_DOCUMENT_DEPTH容器条目总数最多10,000_MAX_DOCUMENT_ITEMS所有字符串键与文本载荷的 UTF-8 编码总量最多32 MiB_MAX_DOCUMENT_TOTAL_BYTES数值必须是有限值且落在有符号 64 位整数范围内float(nan)、float(inf)、1 80等会被拒绝键必须是字符串且不可重复循环引用如cyclic[self] cyclic会被拒绝。任何越界都会在脱敏开始前以稳定错误终止document could not be copied且错误信息不含原文。对应实现为_copy_document_value见 openmed/interop/opensearch.py测试见 tests/unit/interop/test_opensearch.py 与 tests/unit/interop/test_opensearch.py。策略与方法构造期校验的policy与methodpolicy构造时经_normalize_policy调用canonical_policy_name见 openmed/core/policy.py解析别名并校验。合法的策略名来自CANONICAL_POLICY_NAMES见 openmed/core/policy.py并支持POLICY_ALIASES中的别名。非法策略在构造期即抛出不含原文的policy is invalid测试见 tests/unit/interop/test_opensearch.py。method_normalize_method会去除首尾空白并转小写见 openmed/interop/opensearch.py随后原样透传给脱敏器。OpenMed 核心脱敏函数deidentify见 openmed/core/pii.py支持多种脱敏方法处理器默认使用mask替换为[NAME]、[PHONE]等占位符此外还有remove、replace、hash、shift_dates、format_preserve、aadhaar_mask等策略可供选择keep_year、confidence_threshold默认 0.7等参数可通过deidentify_kwargs透传。诊断与本地测试process_with_report与RedactionReport当桥接程序需要处理器级诊断时使用process_with_reportredacted_document, report processor.process_with_report(document) print(report.to_dict())RedactionReport见 openmed/interop/opensearch.py是一个 frozen dataclassto_dict()输出的 JSON 结构固定为{ adapter: opensearch, policy: hipaa_safe_harbor, fields: [message, clinical.note], values_seen: 2, values_redacted: 2, spans_redacted: 2 }报告只包含选中字段名、策略名与聚合计数values_seen被检查的文本值数量、values_redacted发生变更的值数量、spans_redacted脱敏片段数。它从不包含源文本、脱敏文本、映射或异常细节——这是刻意的隐私设计to_dict()的 docstring 明确写着返回不含源值的 JSON 兼容诊断。测试 tests/unit/interop/test_opensearch.py 验证了报告字符串中不存在任何源标记Synthetic Person、synthetic-555-0100。若自定义脱敏器返回的对象同时携带pii_entitieslist/tuple其有界长度会被用作报告中的 span 计数见_result_span_countopenmed/interop/opensearch.py每个值最多计入 10,000 个 span。默认脱敏器缓存优先的离线安全配置处理器默认使用 OpenMed 的deidentify经_default_deidentifier惰性导入见 openmed/interop/opensearch.py并强制套用一组只读的离线配置见_redact_textopenmed/interop/opensearch.pyconfig OpenMedConfig(local_onlyTrue, hf_token)cache-only 配置不读取凭据环境变量见_offline_configopenmed/interop/opensearch.pykeep_mappingFalse、auditFalse禁用映射与审计留存use_safety_sweepTrue保持确定性安全扫描开启policy与method始终由处理器注入调用方无法覆盖。效果是缺失模型时在本地失败而不是触发下载——即离线优先、绝不静默出网。测试 tests/unit/interop/test_opensearch.py 通过注入假deidentify捕获 kwargs验证了默认行为local_onlyTrue、auditFalse、keep_mappingFalse、use_safety_sweepTrue、policyhipaa_safe_harbor、methodmask。注入自定义脱敏器对于已预加载模型或离线测试场景传入deidentifier可调用对象。契约如下见 openmed/interop/opensearch.py 与_result_textopenmed/interop/opensearch.py接收text、policy、method以及deidentify_kwargs中的显式条目返回脱敏字符串或返回带字符串属性/键deidentified_text的对象/映射可选地返回pii_entities供报告使用 span 计数。def local_deidentifier(text, *, policy, method, **kwargs): # 使用已加载的本地模型或测试桩 return model.redact(text) # 返回 str 或 {deidentified_text: ...} processor OpenSearchRedactionProcessor( fieldmessage, deidentifierlocal_deidentifier, deidentify_kwargs{confidence_threshold: 0.85}, )测试中的fake_deidentifiertests/unit/interop/test_opensearch.py即演示了这一模式。脱敏失败的安全降级任何脱敏异常含默认脱敏器加载失败都会被转换为稳定消息redaction failed源文本不会进入日志、异常或报告。测试 tests/unit/interop/test_opensearch.py、tests/unit/interop/test_opensearch.py 分别验证了脱敏器抛错与默认加载器失败两种情况。同时KeyboardInterrupt/SystemExit会被原样透传见 tests/unit/interop/test_opensearch.py保证解释器控制流不被吞掉。输出膨胀有界脱敏输出长度受min(_MAX_TEXT_CHARS, max(_MIN_OUTPUT_CHARS, len(text) * 8))约束_MAX_OUTPUT_EXPANSION 8、_MIN_OUTPUT_CHARS 4096见 openmed/interop/opensearch.py防止恶意/异常的脱敏器把单字段文本膨胀成巨大输出。对应测试见 tests/unit/interop/test_opensearch.py。deidentify_kwargs数据安全的冻结快照机制deidentify_kwargs不是简单透传而是经过_snapshot_deidentify_kwargs见 openmed/interop/opensearch.py做深度冻结的纯数据快照最多64 个顶层选项、4,096 个嵌套值、16 层深度每个字符串/字节值最多1 MiB全部选项 UTF-8 编码总量最多4 MiB允许的值类型null、布尔、有符号 64 位整数、有限浮点、字符串、bytes、list、tuple、字符串键字典可调用对象与活对象被拒绝如lambda、float(inf)、1 80、循环引用保留键method、policy、text等不可覆盖使用默认脱敏器时audit、config、keep_mapping、use_safety_sweep也会被忽略每次process调用都会从冻结快照恢复一份全新的选项副本调用方或脱敏器对返回 dict 的修改不会串扰下一次调用。测试 tests/unit/interop/test_opensearch.py 验证了深度快照与每次调用新鲜实例的行为tests/unit/interop/test_opensearch.py 验证了不安全值的拒绝tests/unit/interop/test_opensearch.py 验证了保留选项在快照前即被忽略。桥接落地示例与源码导读最小摄取桥from openmed.interop.opensearch import OpenSearchRedactionProcessor processor OpenSearchRedactionProcessor( fields(message, clinical.note), ignore_missingTrue, ) def on_ingest_document(doc: dict) - dict: OpenSearch/Elasticsearch 摄取桥回调返回脱敏副本写回文档。 return processor.execute(doc)桥接层只需把execute的返回值赋给摄取文档即可未选字段与原始输入均保持不变。相关文件处理器实现openmed/interop/opensearch.py适配器注册表opensearch条目openmed/interop/init.py策略名解析openmed/core/policy.py默认脱敏器deidentifyopenmed/core/pii.py单元测试tests/unit/interop/test_opensearch.py核心不导入适配器的保证测试tests/unit/interop/test_core_does_not_import_adapters.py总结OpenSearchRedactionProcessor的价值在于把脱敏压缩成一个无副作用、无网络、无客户端依赖的纯函数显式字段选择 dotted 路径寻址、严格的有界文档/选项边界、value-free 的稳定错误与聚合报告、缓存优先的默认离线配置以及可注入的本地脱敏器。它不替代 OpenSearch 摄取管线的调度而是为桥接层提供一个可审计、可测试、确定性的脱敏单元——配合 tests/unit/interop/test_opensearch.py 中的边界测试可以在不引入任何出网流量的前提下把 HIPAA PII 脱敏安全地嵌入搜索索引链路。【免费下载链接】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),仅供参考