OpenMed 桥接实践通过 openmed.interop 将临床 PII 检测接入 Presidio、spaCy 与 LangChain【免费下载链接】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 是一套本地优先local-first的医疗 AI 工具链其核心能力是临床 NER 与符合 HIPAA 的 PII 脱敏全部推理在设备端完成。现实中很少有团队愿意为了引入 OpenMed 而重写整条数据管线——更常见的诉求是让 OpenMed 的临床 PII 召回能力融入我已有的 Presidio / spaCy / LangChain 体系。本指南围绕openmed.interop这一懒加载适配器注册表展开讲解如何在不引入额外依赖负担的前提下用 Presidio 适配器完成双向标签转换与合并去重、用 spaCy 组件把 PII span 投影到Doc、以及用 LangChain runnable 在 LLM 之前做端侧脱敏。读完本文你将掌握三套可复制的桥接代码、各适配器的配置参数语义以及偏移量、对齐模式、合并策略等关键注意事项。何时需要桥接如果你属于以下任一场景就可以考虑接入openmed.interop已经在运行Microsoft Presidio希望叠加 OpenMed 的临床 PII 召回能力或者把 OpenMed 检出的 span 回灌给 Presidio 的 anonymizer 做匿名化已经有一套spaCy流水线希望 OpenMed 的 PII 检测结果直接落在Doc上与现有组件共享同一套 token 对齐体系正在构建LangChain链希望在文本到达云端 LLM 之前先完成 PHI 脱敏——这是部署在云端模型之前的端侧护栏需要让 OpenMed 的脱敏能力从既有框架中直接可达而不必围绕openmed.deidentify重写业务代码。懒加载适配器注册表openmed.interopopenmed.interop是 OpenMed 与主流 PII/NLP 生态互操作的核心入口。它的设计原则是显式导入、按需加载所有适配器都隐藏在显式 import 之后因此导入openmed或openmed.interop永远不会把 Presidio、spaCy、LangChain 等可选第三方依赖拖进进程——每个桥接都是一个可选 extra只有在你真正需要该桥接时才安装。注册表核心 APIimport openmed.interop as interop interop.available_adapters() # (cda, hl7v2, langchain, presidio, spacy) spec interop.adapter_spec(presidio) # AdapterSpec(namepresidio, moduleopenmed.interop.presidio, # extrapresidio, descriptionPresidio RecognizerResult adapter) mod interop.get_adapter(presidio) # imports openmed.interop.presidio # Attribute access also works lazily: openmed.interop.presidio # same module, imported on first touch三个 API 的加载语义在源码中体现得十分明确见 openmed/interop/init.pyavailable_adapters()与adapter_spec()永远不会导入适配器模块它们只读取注册表字典_ADAPTERS并返回元数据因此即使对应 extra 未安装也可以安全调用适合做能力探测get_adapter(name)通过importlib.import_module(spec.module)真正触发导入init.py并在导入成功后调用模块的ensure_registered()若存在完成注册模块级__getattr__让openmed.interop.presidio这样的属性访问同样走get_adapter路径实现首次触摸才导入init.py。每个适配器在注册表中对应一个AdapterSpec数据类init.py包含name、module、extra对应 pip 安装名、description等元数据字段。目前注册表内置了 30 余个适配器覆盖 cda、hl7v2二者随核心分发无需 extra、presidio、spacy、langchain、llamaindex、haystack、spark、snowflake、duckdb、polars 等生态其中cda与hl7v2的extra字段为空字符串或core即随主包安装即可使用。只安装你需要的 extrapip install openmed[presidio] # Presidio RecognizerResult adapter pip install openmed[spacy] # spaCy openmed_deid component pip install openmed[langchain] # LangChain redaction runnable # cda 和 hl7v2 适配器随核心分发无需 extra见各自技能文档各 extra 的版本约束定义在仓库根目录的 pyproject.toml 中presidioextra 对应presidio-analyzer2.2.354,3pyproject.tomlspacyextra 对应spacy3.8.9及click8.0pyproject.tomllangchainextra 对应langchain-core0.2,2pyproject.toml。注意 LangChain 桥接只依赖langchain-core并不要求完整的 langchain 全家桶。Presidio 桥接双向转换与语义合并模块openmed.interop.presidio负责在 Presidio 的RecognizerResult与 OpenMed 规范化的PIIEntity之间转换并通过 OpenMed 的语义单元合并器把两个检测器的结果合并为一份去重后的 span 集合。三个已验证的可调用对象from openmed.interop.presidio import ( to_canonical, # RecognizerResult(s) - [PIIEntity] from_canonical, # [PIIEntity] - [RecognizerResult] (需要 presidio extra) merge_with_openmed, # 合并 OpenMed 与 Presidio span解决重叠 PresidioAdapterConfig, ) import openmed text Dr. Smith called patient at 617-555-0123 on 2024-03-02. # Presidio 产出 RecognizerResultOpenMed 产出 PIIEntity。 openmed_spans openmed.extract_pii(text).entities presidio_results analyzer.analyze(texttext, languageen) # 你的 Presidio analyzer merged merge_with_openmed( openmed_spans, presidio_results, texttext, configPresidioAdapterConfig(preserve_presidio_labelsTrue), ) # - 去重后的 [PIIEntity]重叠按 score、长度、OpenMed 来源优先级解析各函数的行为在源码 openmed/interop/presidio.py 中均有对应实现to_canonical(result, *, textNone, configNone)接受单个或一组RecognizerResult也兼容字典形态的类 RecognizerResult 对象逐条转换为PIIEntitypresidio.py。转换时会把 Presidio 的entity_type、start、end、score映射进 PIIEntity并在metadata中保留presidio_entity_type、recognizer_name、recognition_metadata等溯源信息from_canonical(entities, *, result_clsNone, configNone)把规范化 PIIEntity 转回RecognizerResultpresidio.py。它允许传入result_cls以便在无 presidio 依赖的环境下做轻量测试不传时则惰性导入presidio_analyzer.RecognizerResult缺失依赖时抛出带 extra 提示的ImportErrormerge_with_openmed(openmed_entities, presidio_results, *, text, configNone, use_semantic_patternsTrue)是推荐的合并入口presidio.py它先把 OpenMed 实体复制并打上sourceopenmed标记把 Presidio 结果转成规范化实体然后一并送入openmed.core.pii_entity_merger.merge_entities_with_semantic_units最后用_resolve_overlaps做一轮重叠裁决。为什么是合并而不是简单并集如果只是把两个检测器的 span 简单取并集Presidio 命中的PHONE与 OpenMed 命中的部分重叠 span 会产生双重脱敏或重复输出。merge_with_openmed通过语义单元合并器让重叠或相邻的检测收敛为一个正确的 span并内置了完整的标签映射Presidio → OpenMedPHONE_NUMBER → PHONE、US_SSN → SSN、EMAIL_ADDRESS → EMAIL、US_DRIVER_LICENSE → ID_NUM、US_PASSPORT → ID_NUM、MEDICAL_LICENSE → ID_NUM、CRYPTO → BITCOIN_ADDRESS、US_BANK_NUMBER → ACCOUNT_NUMBER等见 presidio.py 的_PRESIDIO_TO_CANONICALOpenMed → PresidioFIRST_NAME/LAST_NAME/MIDDLE_NAME → PERSON、STREET_ADDRESS/ZIPCODE → LOCATION、DATE_OF_BIRTH/TIME → DATE_TIME、ID_NUM → MEDICAL_LICENSE等见 presidio.py 的_CANONICAL_TO_PRESIDIO。重叠裁决的优先级在_best_overlap中实现presidio.py依次比较置信度分数、span 长度、是否来自 OpenMed 来源最后才是起始位置——即高分数、更长、OpenMed 检出的实体在冲突中胜出。把 OpenMed span 送入 Presidio anonymizer若想把 OpenMed 检出的实体推给 Presidio 的匿名化器只需先转回RecognizerResultresults from_canonical(openmed_spans) # [RecognizerResult] anonymized anonymizer.anonymize(texttext, analyzer_resultsresults)PresidioAdapterConfigpresidio.py提供三个运行时选项配置项默认值说明sourcepresidio写入metadata的适配器来源标记preserve_presidio_labelsTrue转回 RecognizerResult 时优先沿用 Presidio 原始标签存于metadata.presidio_entity_type否则用 OpenMed 到 Presidio 的映射表allow_semantic_only_matchesFalse是否允许仅由语义模式无模型证据命中的匹配进入合并结果spaCy 桥接openmed_deid 管道组件模块openmed.interop.spacy_component通过Language.factory注册了一个名为openmed_deid的 spaCy 管道工厂spacy_component.py。把它加入管道后OpenMed 检出的 PII span 会以 spaCySpan的形式落到Doc上。import spacy import openmed.interop.spacy_component # 注册 Language.factory nlp spacy.blank(en) nlp.add_pipe(openmed_deid, config{ confidence_threshold: 0.5, lang: en, target: openmed_pii, # doc.spans 的键名 merge_ents: False, # 设为 True 时同时写入 doc.ents alignment_mode: expand, # 字符偏移到 token 的对齐方式strict|contract|expand }) doc nlp(Patient John Doe, MRN 12345, seen today.) for span in doc.spans[openmed_pii]: print(span.label_, span.text) # 原始字符偏移 span 也可以通过 doc._.openmed_pii 获取组件配置参数OpenMedDeidConfigspacy_component.py在__post_init__中会校验alignment_mode必须是strict、contract、expand三者之一且target必须是非空字符串。完整参数如下参数默认值说明model_nameNone指定 OpenMed 使用的模型不指定时用默认模型confidence_threshold0.5置信度阈值低于阈值的检测会被丢弃langen文本语言传给底层提取器policyNone脱敏策略名仅当提取器签名接受policy关键字时才透传extract_kwargs会做签名探测targetopenmed_piidoc.spans上写入投影结果的键名merge_entsFalse为True时把投影 span 合并进doc.entsalignment_modeexpand字符偏移到 token 的对齐策略实现细节与 doc 扩展组件执行时调用openmed.extract_pii(doc.text, ...)获取原始检测spacy_component.py先以依赖轻量的OpenMedPiiSpanlabel、start、end、score存入doc._.openmed_pii扩展属性再用doc.char_span(start, end, label..., alignment_mode...)把字符偏移投影为 token 对齐的Span写入doc.spans[target]spacy_component.pymerge_entsTrue时投影结果与已有doc.ents一起经 spaCy 的filter_spans解决重叠后写入doc.entsspacy_component.py如果不想走add_pipe也可以直接实例化OpenMedDeidComponent/OpenMedDeidConfig构造组件并可通过extractor参数注入自定义的提取函数便于测试或替换检测后端。LangChain 桥接链上的端侧脱敏 runnable模块openmed.interop.langchain提供一个Runnable形态的脱敏器可以放在LLM 步骤之前让 PHI 在离开设备之前就被处理掉。from openmed.interop.langchain import ( create_redaction_runnable, LangChainRedactionConfig, ) redactor create_redaction_runnable( configLangChainRedactionConfig(methodmask, policyhipaa_safe_harbor), input_keytext, # 对字典载荷中的该键做脱敏可选 output_keytext, ) chain redactor | prompt | llm # redact - prompt - model chain.invoke({text: John Doe, MRN 12345, has type 2 diabetes.})可处理的数据形态OpenMedRedactionTransform的_redact_value按类型分派langchain.py支持字符串直接脱敏LangChainDocument复制对象并仅替换page_contentmetadata与列表顺序保持不变消息类对象含content属性复制并替换content多模态内容块中只改写text/content键Prompt 类对象含messages序列逐条脱敏消息Mapping载荷未指定input_key时对除metadata、additional_kwargs、response_metadata之外的文本值做脱敏指定input_key时只改该键output_key可另设写出键缺失时报KeyErrorlist/tuple递归处理每个元素容器类型保持不变。三种创建方式函数说明create_redaction_transform(...)返回依赖轻量的OpenMedRedactionTransform对象不需要安装langchain-core适合测试或非 LangChain 的本地编排层create_redaction_runnable(...)在 transform 之上调用.as_runnable(nameopenmed_redaction)包装为RunnableLambda需要langchain-corecreate_redaction_node(...)语义上等价于 runnable 工厂专为链节点命名场景提供OpenMedRedactionTransform本身实现了invoke、batch、transform等方法langchain.py即使不包装成 runnable 也可以作为普通 callable 或流式阶段使用。配置转发面完整的 deidentify 参数LangChainRedactionConfiglangchain.py会转发 OpenMeddeidentify的完整参数面。它在__post_init__中校验policy非空、consistent为布尔、seed为整数或 None并通过to_deidentify_kwargs()生成底层调用参数dataclass(frozenTrue) class LangChainRedactionConfig: method: str mask # mask | replace | format_preserve | ... model_name: str | None None confidence_threshold: float 0.7 keep_year: bool False keep_mapping: bool False use_smart_merging: bool True lang: str en normalize_accents: bool | None None use_safety_sweep: bool True consistent: bool True # 确定性替代同一实体同一占位符 seed: int | None None locale: str | None None policy: str | None hipaa_safe_harbor calibration_thresholds_path: str | Path | None None extra_kwargs: Mapping[str, Any] field(default_factorydict)注意extra_kwargs不能覆盖任何具名配置字段源码会检测键冲突并抛ValueErrorto_deidentify_kwargs也会过滤掉值为None的键保证只把有意义的参数传给底层。确定性替代与错误安全LangChainRedactionStatelangchain.py承载请求级的确定性替代控制consistentTrue保证同一实体在同一请求内替换为一致占位符seed提供可复现的随机源该状态只保存控制项与聚合计数器从不保存源文本、映射或载荷避免把 PHI 带进日志或回调LangChainRedactionErrorlangchain.py在脱敏失败时抛出异常信息刻意不包含载荷值——因为底层 deidentifier 可能是应用注入的其异常文本不应假定对链日志或回调 trace 安全此外该模块还提供get_langchain_tools()把 OpenMed 注册表中的工具渲染为 LangChainStructuredTool对象langchain.py以及create_retrieval_chain/OpenMedRetrievalChain用于本地脱敏 网关受限外部模型 授权再标识的检索组合。与 OpenMed 之间的数据交接桥接的本质是让规范化对象在生态之间流动。规范对象是openmed.core.pii.PIIEntityopenmed/core/pii.py其核心字段包括text实体文本片段label/entity_typePII 类别NAME、EMAIL、PHONE、SSN 等start/end字符偏移confidence模型置信度0-1redacted_text/original_text脱敏替换文本与原始文本hash_value/reversible_id一致性哈希与可逆假名化句柄。数据流向分两个方向进入 OpenMedPresidio 的RecognizerResult以及隐式的 spaCy 文本通过适配器转换为PIIEntity此后即可走 OpenMed 常规的脱敏、审计与策略链路例如 deidentifying-clinical-text、auditing-deidentification-runs 等技能流出 OpenMedfrom_canonical把实体送回 Presidio 的 anonymizerspaCy 组件把 span 交给下游 spaCy 组件LangChain runnable 把脱敏文本喂给任意链。边缘情况与注意事项探测免费导入收费。available_adapters()/adapter_spec()可以放心探测而不装 extra但一旦触摸模块get_adapter或属性访问而依赖缺失会抛出清晰的ImportError并指明需要安装的 extra。仓库测试 test_core_does_not_import_adapters.py 与 test_presidio_adapter.py 专门验证了这种懒加载与错误提示行为。偏移量必须指向同一段文本。merge_with_openmed与 spaCy 对齐都假设所有 span 索引的是同一字符串。请在最前面一次性完成脱敏或归一化切勿混用归一化前后两种文本的偏移量。alignment_modeexpand是 spaCy 侧的默认值它会把字符 span 向外吸附到 token 边界如果要求精确的字符级对齐并接受无法对齐的 span 被丢弃请改用strict。LangChain 脱敏是护栏而非保证。在把它放到云端 LLM 之前请用openmed.eval的泄漏门禁见 evaluating-with-leakage-gates评估脱敏质量确认达到可接受的召回水平。跨桥接依然保持本地优先。无论走哪条桥OpenMed 推理都留在设备端只有你自己的下游 LLM/云端步骤如果有会离开机器——这正是先脱敏再出网的原因。延伸阅读适配器注册表实现openmed/interop/init.pyPresidio 适配器源码openmed/interop/presidio.pyspaCy 组件源码openmed/interop/spacy_component.pyLangChain 适配器源码openmed/interop/langchain.py桥接测试用例tests/unit/interop/test_presidio_adapter.py、tests/unit/interop/test_spacy_component.py、tests/unit/interop/test_langchain.py、tests/unit/interop/test_langchain_redaction.py规范化 PII 实体定义openmed/core/pii.py相关技能deidentifying-clinical-text、auditing-deidentification-runs、evaluating-with-leakage-gates【免费下载链接】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),仅供参考