news 2026/9/19 4:42:41

OpenMed 隐私安全 Agent 运行摘要:`openmed.agent.RunSummary` 确定性元数据设计与边界校验实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
OpenMed 隐私安全 Agent 运行摘要:`openmed.agent.RunSummary` 确定性元数据设计与边界校验实战

OpenMed 隐私安全 Agent 运行摘要:openmed.agent.RunSummary确定性元数据设计与边界校验实战

【免费下载链接】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 平台,其 Agent 编排层在诊疗流程中会执行多次工具调用、产出临床证据与 FHIR/OMOP 资源。为了在仪表盘与审计证据包中呈现"这次运行发生了什么",又绝不让提示词、工具参数、证据文本、文件路径、凭据或异常信息离开数据边界,openmed.agent.RunSummary提供了一套只含确定性元数据的运行摘要机制。读完本文,你将掌握 RunEvent 事件契约、RunSummary.from_events()聚合、from_json()严格解析的完整细节,并能结合实际源码与测试用例,在自己的 Agent 工作流中落地隐私安全的运行审计。

本文主体内容继承自 docs/agent/run-summaries.md,并辅以 openmed/agent/run_summary.py、openmed/agent/outcomes.py 等源码实现与测试证据进行纵深展开。

一、设计目标:只让"元数据"过境,绝不复制内容

openmed.agent.RunSummary的核心承诺是:为仪表盘和证据包生成确定性(deterministic)元数据,但不复制任何敏感内容。在 run_summary.py 的模块 docstring 中明确写着:

Only bounded identifiers, closed workflow outcomes, counts, durations, and artifact digests cross this boundary. Prompts, tool payloads, evidence text, paths, credentials, and exception text are never accepted or rendered.

也就是说,跨越摘要边界的只有五类受限数据:

  • 有界的(bounded)工作流标识符;
  • 来自闭合词汇表的类型化WorkflowOutcome(结果类别 + 原因码);
  • 有界的非负工具调用计数与有限的运行时长;
  • 可选的、小写 SHA-256 产物摘要;
  • 版本化的 schema 标识符。

而提示词、工具实参、工具输出、证据文本、文件系统路径、凭据、异常文本——一律不被接收、不被渲染。这让运行摘要天然可以进入审计日志、远程仪表盘或证据包,而不触碰 HIPAA 合规所要求的 PHI 边界。

二、事件契约:RunEvent的五个字段

每个运行事件RunEvent(定义见 run_summary.py)只承载以下字段:

字段类型约束
workflow_idstr有界标识符,禁止路径或 URL 语法;由正则^A-Za-z0-9?$校验
outcomeWorkflowOutcome来自闭合结果/原因码词汇表(见第三节)
tool_call_countint非负整数,上限_MAX_TOOL_CALLS = 10_000_000
duration_secondsfloat有限值,上限_MAX_DURATION_SECONDS = 31_536_000.0(约一年)
artifact_digeststuple[str, ...]每个事件最多 128 个,格式为sha256:前缀 + 64 位小写十六进制

值得注意的校验细节:

  • 标识符防注入:测试 test_run_summary.py 明确验证了"workflow with spaces""/tmp/workflow""https://example.test""../secret"都会被拒绝并抛出invalid_identifier——这正是为了防止路径穿越、URL 语法与自由文本混入摘要。
  • 计数与时长有界tool_call_count必须是非负整数且不超过上限;duration_seconds先校验类型与范围,再转换为float,并强制math.isfinite,杜绝NaN/Infinity等非有限值。
  • 摘要去重:同一事件内的artifact_digests不允许重复(duplicate_item)。

事件构造示例

from openmed.agent import OutcomeClass, RunEvent, RunSummary, WorkflowOutcome event = RunEvent( workflow_id="clinical-review", outcome=WorkflowOutcome(OutcomeClass.SUCCESS, "completed"), tool_call_count=3, duration_seconds=2.5, artifact_digests=("sha256:" + "a" * 64,), ) summary = RunSummary.from_events([event]) json_payload = summary.to_json() assert RunSummary.from_json(json_payload) == summary markdown_report = summary.to_markdown()

三、结果词汇表:WorkflowOutcome的闭合枚举

WorkflowOutcome(见 outcomes.py)是事件中唯一表达"运行结果"的对象,其词汇表是闭合(closed)的——不允许自由文本状态字符串。完整词汇表继承自 docs/agent/outcome-reasons.md:

结果类别(OutcomeClass允许的原因码(reason_code
successcompleted
abstainedinsufficient_evidenceout_of_scopelow_confidence
review_requiredconflicting_evidencesafety_reviewhuman_gate
policy_deniedconsent_requiredpurpose_mismatchphi_policy
failedtool_errortimeoutinvalid_input

这张映射表在 outcomes.py 中以_REASON_CODES字典硬编码,并通过allowed_reason_codes()暴露查询接口。失败的关闭原则(fail closed)覆盖以下场景:

  • 未知结果类别 →unknown_class
  • 未知原因码 →unknown_reason
  • 类别与原因码不匹配(如successtimeout)→ 拒绝;
  • 多余字段 →unknown_field
  • 自由文本原因 → 一律拒绝。

异常信息只命名字段名或稳定错误码(如outcome_class: unknown_class),绝不回显被提交的值。这保证了即使校验失败,也不会把可疑的临床内容泄露进异常堆栈。

to_dict()按固定字段序(schema_versionoutcome_classreason_code)输出;to_json()输出按键排序的紧凑 JSON,保证相同输入序列化后逐字节一致(byte-for-byte identical)——这是审计与去重的基础。

四、聚合:RunSummary.from_events()的边界与确定性

RunSummary.from_events()(见 run_summary.py)把一组RunEvent聚合为一条摘要。源码中的关键逻辑:

  • 输入必须可迭代:字符串、bytes、映射对象会被拒绝(events: invalid_iterable),防止误把单个对象当序列;
  • 事件数上限_MAX_EVENTS = 10_000:超过即too_many_items
  • 工作流 ID 去重:聚合后workflow_ids排序去重的元组,且最多_MAX_WORKFLOWS = 1_024个;
  • 结果计数outcome_counts必然覆盖全部 5 个闭合结果类别,且合计不超过事件上限;
  • 工具调用累计tool_call_count逐事件累加,超限即total_out_of_range
  • 时长累计:使用math.fsum精确求和,避免浮点累加误差,总和超限即拒绝;
  • 摘要级去重:所有事件的产物摘要合并为排序去重集合,上限_MAX_SUMMARY_DIGESTS = 4_096

这种设计保证了"输入有界 + 聚合总量有界",任何异常输入都在进入摘要前被拦截。

直接构造的规范化强制

RunSummaryfrozen=True的 dataclass,但其__post_init__(run_summary.py)会强制规范化排序workflow_idsartifact_digests必须是"排序去重"后的序列,outcome_counts必须恰好覆盖 5 个类别键。也就是说,绕过from_events()直接构造时,若传入乱序、重复或键不完整的序列,会直接以not_sorted_unique/invalid_keys失败——从构造层面杜绝了非规范化的摘要进入下游。

五、信任边界解析:from_dict()from_json()

文档明确指出:在信任边界(trust boundaries)应使用RunSummary.from_dict()RunSummary.from_json()解析摘要。两者都拒绝:

  • 缺失字段(missing_field)与未知字段(unknown_field)——摘要字段是冻结集合_SUMMARY_FIELDS,只有schema_versionworkflow_idsoutcome_countstool_call_countduration_secondsartifact_digests六个;
  • 不支持的版本(unsupported_version,必须精确等于openmed.agent.run_summary.v1);
  • 非法计数(invalid_count);
  • 不安全的字符串(unsafe_string)——字符串要么匹配有界标识符正则,要么匹配sha256:摘要格式,否则拒绝。

from_json()在其之上还有四道额外防线(run_summary.py):

  1. 大小上限MAX_RUN_SUMMARY_JSON_BYTES = 1_048_576(1 MiB),对字符串先按 UTF-8 编码后测长度,超限即json_too_large
  2. 重复键拒绝:通过object_pairs_hook=_strict_json_object在解析时检测重复字段并抛duplicate_field
  3. 非标准非有限数拒绝parse_constant=_reject_json_constant拦截NaN/Infinity/-Infinity字面量(non_finite_number);
  4. 畸形 JSON 兜底:解析失败统一转为summary: invalid_json,且不保留底层源码异常——调用方永远不会看到来自解析器的内部 traceback。

此外,严格解析还会拒绝序列字段中出现映射对象invalid_sequence,字符串/bytes 也不行),并在转换为 float 之前完成时长边界检查,避免类型混淆带来的精度与安全问题。

六、确定性序列化:to_dict()/to_json()/to_markdown()

摘要的序列化输出是稳定、可复现的:

  • to_dict():输出前先经过_assert_safe_payload递归校验(run_summary.py),任何非有限浮点数(non_finite_number)、超出白名单的字符串(unsafe_string)、非法类型(forbidden_type)都会触发RunSummaryPrivacyError——这是序列化方向的最后一道隐私闸门;
  • to_json()json.dumps(..., sort_keys=True, separators=(",", ":")),按键排序的紧凑 JSON,保证相同摘要序列化结果逐字节一致
  • to_markdown():生成确定性 Markdown 报告,固定包含# Agent Run Summary## Workflows## Outcomes## Execution(工具调用数 + 时长)、## Artifacts(SHA-256 列表)五个小节。

JSON 键、结果行、工作流标识符、产物摘要的顺序全部稳定,可直接进入审计比对或证据包生成流水线。同时,摘要层从不读取产物内容或工作流内容——它只搬运元数据,不触碰数据本体。

七、配套生态:内容无关的 Agent 元数据体系

RunSummary不是孤立组件,它与openmed.agent包中的其他隐私安全组件共同构成一套完整的"内容无关元数据"体系(统一导出见 openmed/agent/init.py):

  • ArtifactReference:让一次运行指向一个产物,而无需把报告、临床内容、文件名、本地路径或远程 URL 复制进 trace。每个引用只含art_+ 32 位小写十六进制的透明 ID、闭合类别(evidence/preview/fhir/omop/evaluation)、版本化 schema ID、64 位小写 SHA-256 摘要和不超过有符号 64 位整数上限的正字节数。实现见 artifact_reference.py;批量挂载多个引用时用validate_artifact_references()拒绝重复 ID(duplicate_artifact_id)并保持顺序。重要边界:引用记录的是调用方提供的摘要与大小,并不自行验证;创建、解析、序列化引用永不打开本地文件、不拉取远程资源、不校验临床内容
  • Event Correlationrun_/act_前缀 + 32 位小写十六进制的 128 位随机关联 ID(RunId/ActionId/ActionCorrelation),运行时用secrets.token_bytes生成,禁止通过对提示词、临床文本、文件名、用户 ID 等做哈希或编码来派生。
  • Timing Metadata:调用方提供的单调纳秒边界,RunTiming/ActionTiming计算精确整数时长,永不读取墙钟,序列化后不含墙钟时间戳、路径或 PHI;父链接必须构成无环图。
  • Governance IdentifiersCapabilityId/PurposeId/PolicyId/WorkflowId/ToolId五种开发者手写名称,遵循<kind>:<reverse-domain>/<local-name>[@<version>]语法,GovernanceIdErrorcode提供invalid_identifierwrong_kindnamespace_too_long等稳定诊断。

这些组件的共性设计语言与RunSummary完全一致:闭合词汇、有界长度、规范化解析、错误信息不回显被拒绝的值。整个openmed.agent包在 openmed/agent 目录下均有独立源码模块与对应测试(tests/unit/agent 下的test_run_summary.pytest_outcomes.pytest_artifact_reference.pytest_correlation.pytest_timing.pytest_identifiers.py),可以作为"元数据安全模式"的完整参考实现。

八、测试验证与离线回归

运行摘要的所有边界行为都有离线测试覆盖。以 tests/unit/agent/test_run_summary.py 为例:

  • test_run_event_accepts_safe_metadata:验证合法元数据被接受且字段保持原值;
  • test_run_event_accepts_closed_outcome_vocabulary:参数化遍历全部 5 个OutcomeClass,验证闭合词汇表;
  • test_run_event_rejects_paths_urls_and_free_text:验证带空格、路径、URL、../的工作流 ID 全部以invalid_identifier拒绝;
  • test_run_event_requires_typed_outcome:字符串或字典形式的 outcome 一律invalid_type拒绝。

全文件共 442 行,覆盖计数越界、时长越界、重复摘要、乱序 ID、未知字段、重复 JSON 键、超大文档、非有限数等负面路径。类似地,治理标识符的语法测试可用如下命令聚焦运行:

uv run --frozen --extra dev pytest tests/unit/agent/test_identifiers.py -q

九、落地建议:何时、在何处使用RunSummary

结合源码设计与测试覆盖,以下实践路径可以直接复用:

  1. 在 Agent 工作流出口生成摘要:每次工作流结束后,用RunEvent记录工作流 ID、WorkflowOutcome、工具调用数、时长与产物摘要,再通过RunSummary.from_events()聚合。注意workflow_id使用有界标识符(字母数字 +_/./-),不要塞入路径或 URL。
  2. 在信任边界用from_json()解析:任何来自外部系统、网络请求或持久化存储的摘要,一律走from_json(),其 1 MiB 上限、重复键检测、非有限数拦截与字段白名单可防御畸形或恶意载荷;不要直接用RunSummary(...)构造去解析不可信数据。
  3. 摘要只进证据包,内容留在原地:产物本体通过 ArtifactReference 以art_透明 ID + SHA-256 引用,运行摘要只携带摘要层元数据;两侧都永不读取产物内容
  4. 错误处理只看错误码RunSummaryError/RunSummaryPrivacyError/OutcomeErrorcodefield_name是稳定诊断接口,异常消息不回显提交值,可直接用于监控告警而无需担心 PHI 泄露。

这套机制把"Agent 可观测性"与"医疗数据隐私"解耦:仪表盘获得的是确定、有界、无内容的运行事实,审计证据包获得的是可复现、可比对的元数据轨迹,而 PHI 始终停留在本地数据边界之内。

参考与深入阅读

  • 主文档:docs/agent/run-summaries.md
  • 结果词汇表:docs/agent/outcome-reasons.md
  • 产物引用:docs/agent/artifact-references.md
  • 关联标识:docs/agent/event-correlation.md、docs/agent/timing-metadata.md、docs/agent/governance-identifiers.md
  • 源码实现:openmed/agent/run_summary.py、openmed/agent/outcomes.py、openmed/agent/artifact_reference.py
  • 测试用例:tests/unit/agent/test_run_summary.py 及 tests/unit/agent 目录下其余组件测试

【免费下载链接】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 4:42:09

围绕 AGENTS.md 做上下文预算,TaoToken 的 Base URL 一处填写

/* 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 4:40:24

StarRocks DROP STORAGE VOLUME 详解:语法、权限与删除保护机制

StarRocks DROP STORAGE VOLUME 详解&#xff1a;语法、权限与删除保护机制 【免费下载链接】starrocks The worlds fastest open query engine for sub-second analytics both on and off the data lakehouse. With the flexibility to support nearly any scenario, StarRock…

作者头像 李华
网站建设 2026/9/19 4:36:34

嵌入式C中printf终端去哪了?MicroLIB标准IO重定向详解

/* 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 4:36:07

高通AR1+变色镜片:AR眼镜供应链BOM与功耗散热拆解

/* 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 4:34:07

Obsidian+Claude Code:打造从素材收集到成稿输出的内容工厂

最近我在搭自己的内容生产系统时&#xff0c;把 Obsidian 和 Claude Code 组合到了一条流水线上&#xff0c;跑通了从素材收集到成稿输出的完整流程。这一套搭配很值得记录&#xff0c;因为 Obsidian 负责本地知识库的沉淀、双向链接和插件生态&#xff0c;Claude Code 能在命令…

作者头像 李华
网站建设 2026/9/19 4:33:41

OpenStack本地部署指南:DevStack+Multipass可复现方案

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

作者头像 李华