news 2026/9/19 5:10:25

OpenMed 本地优先的 LlamaIndex 摄入脱敏变换:在拆分、嵌入与存储前保护节点文本与敏感元数据

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
OpenMed 本地优先的 LlamaIndex 摄入脱敏变换:在拆分、嵌入与存储前保护节点文本与敏感元数据

OpenMed 本地优先的 LlamaIndex 摄入脱敏变换:在拆分、嵌入与存储前保护节点文本与敏感元数据

【免费下载链接】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 为 LlamaIndex 提供了一套可选的本地优先(local-first)摄入变换(ingestion transform),可在节点被拆分(splitting)、嵌入(embedding)或持久化之前,先对节点文本和敏感元数据进行脱敏,同时严格保持 LlamaIndex 的"节点列表"(list-of-nodes)契约不变。阅读本文后,你将掌握如何用openmed[llamaindex]IngestionPipeline中插入脱敏变换、如何通过LlamaIndexRedactionConfig精细调校本地脱敏引擎、如何读取仅含计数的审计元数据,以及该适配器在源码层面的设计边界。

安装与依赖边界

LlamaIndex 是 OpenMed 的可选依赖,需要显式安装额外依赖项:

pip install "openmed[llamaindex]"

在 pyproject.toml 中,该 extra 声明为llama-index-core>=0.10,<1。关键设计在于:导入openmedopenmed.interop并不会导入 LlamaIndex。这一保证由两处机制共同落实:

  • 适配器注册表(openmed/interop/init.py)中,llamaindex被登记为惰性加载的 builtin 适配器,import openmed.interop只注册元数据,不触发第三方导入;
  • 工厂函数在运行期才通过_load_optional_class动态导入llama_index.core.schema.TransformComponent等类(openmed/interop/llamaindex.py)。

单元测试test_registry_loads_llamaindex_redaction_adapter_lazily(tests/unit/interop/test_llamaindex_redaction.py)会在加载适配器后断言sys.modules中不存在任何llama_index前缀模块,验证了这一惰性边界。若未安装 extra 就调用工厂,会通过openmed.core.capabilities.raise_missing_backend抛出带openmed[llamaindex]提示的MissingOptionalDependencyError(见test_redaction_factories_raise_clear_error_without_extra,tests/unit/interop/test_llamaindex_redaction.py)。

在拆分之前完成脱敏:摄入变换的正确用法

核心原则是把脱敏变换放在 splitter 之前,这样后续所有 chunk 都由已受保护的文本派生而来,而不是在拆分后逐块脱敏(后者容易产生跨 chunk 的上下文泄漏与不一致)。

from llama_index.core.ingestion import IngestionPipeline from llama_index.core.node_parser import SentenceSplitter from openmed.interop.llamaindex import ( LlamaIndexRedactionConfig, create_redaction_transform, ) redaction_transform = create_redaction_transform( config=LlamaIndexRedactionConfig( numeric_metadata_allowlist=("page_number",), ) ) pipeline = IngestionPipeline( transformations=[ redaction_transform, SentenceSplitter(chunk_size=512, chunk_overlap=32), embed_model, ], disable_cache=True, ) redacted_nodes = pipeline.run(documents=documents, store_doc_text=False)

该变换在源码中的行为(OpenMedRedactionTransform.__call__,openmed/interop/llamaindex.py)依次完成三件事:

  1. 复制节点:通过_clone(优先调用 Pydantic 的model_copy(deep=True),否则回退copy.deepcopy)生成节点副本,原始节点绝不被修改;
  2. 保留既有 chunk 元数据:脱敏仅作用于节点文本与元数据值本身,chunk 的结构信息(如start_char_idxend_char_idx)保持不变(见test_ingestion_transform_redacts_copies_before_storage,tests/unit/interop/test_llamaindex_redaction.py);
  3. 以确定性 UUID 假名替换节点与关系标识符:节点id_relationships中的关联节点node_id会通过 UUIDv5 命名空间https://openmed.ai/llamaindex-redaction生成稳定假名(openmed/interop/llamaindex.py 与_pseudonymized_node_id,openmed/interop/llamaindex.py),实现存储安全(storage-safe)的链接一致性:同一来源标识符反复出现,得到的假名也相同

测试test_ingestion_transform_keeps_source_ids_stable_across_calls验证了"同一 source id 两次变换得到相同 UUID 假名"(tests/unit/interop/test_llamaindex_redaction.py),且test_real_ingestion_sanitizes_related_node_metadata证明序列化后的存储元数据中不会残留任何原始姓名、文档 ID 或病历号(tests/unit/interop/test_llamaindex_redaction.py)。

数字元数据的白名单机制

numeric_metadata_allowlist用于放行经过审查、不具备识别性的数值元数据(如页码page_number)。源码逻辑见_redact_metadata_value(openmed/interop/llamaindex.py):

  • 白名单内的数值字段原样保留;
  • 其他数值字段会被替换为确定性假名,格式为openmed-<sha256 摘要>,其中摘要基于"类型名 + 原值"计算(_pseudonymized_identifier,openmed/interop/llamaindex.py)。

例如病历号record_number=1234567会被替换成openmed-...形式的字符串,而page_number=2保持不变(见测试断言,tests/unit/interop/test_llamaindex_redaction.py)。

元数据脱敏的完整规则

redact_metadata=True(默认开启)时,节点元数据会递归处理:

  • 字符串值:走完整脱敏流程(键与值都处理),并自动写入excluded_llm_metadata_keys/excluded_embed_metadata_keys,使脱敏后的元数据不会回流进 LLM/Embedding 的get_content()输出(_exclude_metadata_from_content,openmed/interop/llamaindex.py;测试见 tests/unit/interop/test_llamaindex_redaction.py);
  • 映射 / 列表 / 元组:递归处理,且带环检测——循环引用会抛出ValueError("LlamaIndex metadata must not contain cycles")(openmed/interop/llamaindex.py);
  • 布尔与 null:原样保留;数字:按上述白名单规则处理;
  • 其他类型(如pathlib.Path):抛出TypeError,提示元数据仅支持字符串、数字、布尔、null、映射、列表或元组(测试test_postprocessor_rejects_unsupported_or_cyclic_metadata,tests/unit/interop/test_llamaindex_redaction.py)。

与缓存、并行及序列化的兼容性

变换通过to_dict()/__reduce__暴露稳定的缓存标识与可 pickle 重建能力(openmed/interop/llamaindex.py):

  • 默认缓存标识为default:<sha256(配置 repr)>,同一配置产生同一标识,不同配置(如maskremove)产生不同标识,避免 IngestionCache 串用(_redaction_cache_identity,openmed/interop/llamaindex.py);传入自定义deidentifier时则退化为custom:<随机 hex>,保证自定义脱敏器下不误用缓存;
  • 提供__reduce__使变换可跨进程 pickle,支持并行摄入(num_workers>0)与分布式场景(见test_real_llamaindex_transform_is_picklable_when_extra_is_installedtest_real_llamaindex_parallel_ingestion_when_extra_is_installed,tests/unit/interop/test_llamaindex_redaction.py)。

仅计数的审计元数据(Counts-only Audit Metadata)

变换仍然只返回节点列表,敏感信息不会混入返回数据。需要审计时,从变换实例上读取安全摘要:

audit = redaction_transform.audit_metadata

audit_metadata(以及等价的get_audit_metadata()与结构化对象last_audit)返回一个仅含计数的字典,字段定义见LlamaIndexRedactionAudit.to_dict()(openmed/interop/llamaindex.py):

字段含义
nodes_processed处理的节点总数
nodes_changed发生至少一处变化的节点数
text_values_redacted被脱敏的文本值数量
metadata_values_redacted被脱敏的元数据值数量
entity_counts按规范类别(canonical label)统计的实体计数映射
source_ids_pseudonymized被假名化的来源标识符数量
numeric_metadata_pseudonymized被假名化的数字元数据数量

该摘要不含任何来源标识符、偏移量、输入文本、替换值或任意脱敏器元数据;它不会被写入节点元数据,也不会被发送给嵌入模型。审计对象在__post_init__中被冻结为不可变、经过校验的状态(openmed/interop/llamaindex.py):所有计数通过_safe_nonnegative_count强制为非负整数,实体计数通过_normalize_audit_counts折叠为规范的CANONICAL_LABELS集合内的类别,任何未知/恶意标签都会落入"OTHER"而不会泄露原始值(_safe_audit_label,openmed/interop/llamaindex.py)。

两个测试从攻击者视角验证了审计的安全性:

  • test_audit_collapses_untrusted_entity_labels_without_exposing_them:敏感标签SYNTHETIC_SENSITIVE_VALUE被折叠为{"OTHER": 2},且不出现在repr中(tests/unit/interop/test_llamaindex_redaction.py);
  • test_audit_freeze_and_sanitizes_caller_owned_counts:负数与布尔计数值被清零/丢弃,调用方随后修改源字典也不会影响已冻结的审计(tests/unit/interop/test_llamaindex_redaction.py)。

可选检测器实体元数据的边界保护

若脱敏结果带有可选的实体元数据(如result.pii_entities),审计只从中提取计数,并施加双重边界:

  • 每个被脱敏值最多读取10,000条实体观测(常量_MAX_AUDIT_ENTITIES_PER_VALUE);
  • 实体类别数量最多为规范标签数 + 1(常量_MAX_AUDIT_ENTITY_CATEGORIES,防哈希洪泛式膨胀)。

读取过程中任何异常(如ExplodingEntityMetadata迭代器抛错)都会被吞掉,畸形元数据不会导致脱敏失败observe_value,openmed/interop/llamaindex.py;测试见 tests/unit/interop/test_llamaindex_redaction.py)。

用 LlamaIndexRedactionConfig 调校本地脱敏引擎

LlamaIndexRedactionConfig(openmed/interop/llamaindex.py)把 OpenMed 底层openmed.core.pii.deidentify的可调参数以 frozen dataclass 的形式暴露出来,核心字段如下:

字段默认值说明
method"mask"脱敏方法,如maskremovereplacehashformat_preserveaadhaar_maskshift_dates等(对应 openmed/core/pii.py 的deidentify支持集合)
model_nameNone指定 PII 检测模型(默认英文模型由底层deidentify决定)
confidence_threshold0.7脱敏所需的最低置信度,默认 0.7 是为安全起见抬高的阈值
keep_yearFalse日期脱敏时是否保留年份
keep_mappingFalse是否保留脱敏映射(用于可逆/映射审计场景)
use_smart_mergingTrue智能合并(例如把拆散的日期片段01/15/1970合并为完整日期)
lang"en"检测语言
redact_metadataTrue是否递归脱敏节点元数据
numeric_metadata_allowlist()数字元数据白名单(见上文)
normalize_accentsNone是否归一化重音字符(默认交给引擎决定)
use_safety_sweepTrue是否启用最终安全扫描兜底
consistentFalse是否启用一致性脱敏(同一实体跨文本稳定输出)
seedNone随机种子
localeNone区域设置
policyNone策略配置
calibration_thresholds_pathNone校准阈值文件路径
extra_kwargs{}透传的额外关键字参数

to_deidentify_kwargs()(openmed/interop/llamaindex.py)负责把这些字段映射为deidentify(...)的关键字参数,并做一道安全护栏extra_kwargs不能覆盖任何已命名的安全配置字段,一旦碰撞立即抛出ValueError(测试test_extra_kwargs_cannot_override_named_safety_configuration,tests/unit/interop/test_llamaindex_redaction.py)。例如试图用extra_kwargs={"use_safety_sweep": False}关闭安全扫描会被拒绝——这保证了无论调用方如何组合参数,安全扫描这类防护默认值不会被静默绕过。

自定义脱敏器注入

除了配置驱动,create_redaction_transform(config=..., deidentifier=...)create_redaction_postprocessor(config=..., deidentifier=...)都接受一个deidentifier可调用对象。其约定为:返回字符串,或返回带deidentified_text属性的对象(_deidentified_text,openmed/interop/llamaindex.py)。测试中的fake_deidentify展示了这一最小契约——接收文本与 kwargs,返回含deidentified_text的结果对象(tests/unit/interop/test_llamaindex_redaction.py)。

摄入期 vs 检索期:两条互补的防护路径

本文讲解的create_redaction_transform属于**摄入期(ingestion-time)防护:节点在写入索引前就被脱敏,保证存储与嵌入的内容本身不含 PII。而 OpenMed 还提供了检索期(retrieval-time)**节点防护——通过create_redaction_postprocessor生成BaseNodePostprocessor子类OpenMedRedactionPostprocessor(openmed/interop/llamaindex.py),在查询返回后、进入 LLM 前对NodeWithScore节点进行脱敏,同步方法postprocess_nodes与异步方法apostprocess_nodes(内部经asyncio.to_thread包装)均已实现,且不改变节点的 score(见test_postprocessor_redacts_fixture_nodes_without_an_llmtest_postprocessor_supports_async_retrieval,tests/unit/interop/test_llamaindex_redaction.py)。检索期 postprocessor 的storage_safe=False,因此不做节点 ID 假名化,只做内容脱敏——保持索引链接不变。

完整的检索期接入指南参见 LlamaIndex redaction postprocessor guide。

配套的 LlamaIndex 工具集成

同一适配器模块还通过get_llamaindex_tools()把 OpenMed 的注册表工具渲染为 LlamaIndexFunctionTool对象(openmed/interop/llamaindex.py),并支持在openmed.interop顶层以openmed.interop.get_llamaindex_tools()访问(openmed/interop/init.py)。这意味着脱敏变换、检索期后处理器与函数调用工具三者可以组合成一条完整的"脱敏摄入 → 安全检索 → 工具调用"链路,全部在本地完成,无需任何云服务。

总结

OpenMed 的 LlamaIndex 集成把本地优先的 PII/PHI 脱敏能力无缝嵌入 LlamaIndex 的数据摄取生命周期:通过把create_redaction_transform置于 splitter 之前,确保后续所有 chunk 均派生自受保护文本;通过确定性 UUID 假名化节点与关系标识符,实现存储安全的跨文档链接;通过仅计数的审计元数据,为管线遥测提供不含任何敏感内容的安全摘要。从源码可以看出,该适配器在每一层都做了防御性设计——惰性导入、配置冲突拒绝、审计标签白名单折叠、实体元数据上限、循环引用检测与可 pickle/可缓存——使其既能满足 HIPAA 场景下的严格隐私要求,又能作为 LlamaIndex 生态中稳定、可复用的标准变换组件。

【免费下载链接】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),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/19 5:17:14

3D角色资源制作规范全解析:从网格拓扑到导出验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/19 5:06:43

汇川InoProShop定时器指令TON/TOF/TP工程实战解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/19 5:21:12

PyTorch MNIST下载404与DataLoader读取实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/19 5:24:14

基于全卷积网络的船舶检测与船牌识别方案

简介&#xff1a;这份PDF文档为《基于全卷积神经网络的船舶检测和船牌识别系统》原文&#xff0c;面向深度学习、计算机视觉研究者及港口智能监控相关从业者。文档针对船舶轮廓复杂、船牌位置不固定、船牌文本多样等挑战&#xff0c;系统阐述SDR-FCN方案&#xff1a;利用SDNet完…

作者头像 李华
网站建设 2026/9/19 5:20:35

同一把 TaoToken Key,从 Google Home MCP 到 Antigravity

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华