返回 AIPA 笔记
AIPA Day 74

审计轨迹 I — 全链路 OTel 与"案件 ID 贯穿"四段 span

审计轨迹 I — 全链路 OTel 与"案件 ID 贯穿"四段 span

2026-08-27
audit-trailopentelemetrygenai-spanssemconv-drift

日期: 2026-08-27 阶段: Phase 3 - AML 调查 Copilot 标签: #audit-trail #opentelemetry #genai-spans #semconv-drift

核心问题

Day 71-73 把 SAR 生成与评测做扎实了。但有一个合规维度还没碰:当监管者几个月、甚至几年后回来问"这份 SAR 是怎么生成的",你能不能完整、可复现地把整条链路摆出来?

这不是锦上添花。EU AI Act 把可解释性变成 AML AI 的强制义务(Unit21, 2026):"从初始告警、经调查、到最终 SAR/no-SAR 判定的每一个 AI 辅助决策,都需要一条监管者可在数月或数年后审查的文档化轨迹"。一个只产出风险分、却说不清"看了什么数据、识别了什么模式、为何得出结论"的模型,不合规。所以今天的任务是:把证据汇集→类型学比对→SAR 草稿→人工复核四段,用 OpenTelemetry 串成一棵可审计的 trace 树,案件 ID 贯穿始终。

三件事讲透:

  1. 四段 span 设计 + 案件 ID 贯穿:证据→比对→草稿→复核四阶段各为一个 span,全部挂在同一案件根 span 下,case.id 作为关联键贯穿。
  2. 独立属性映射层防 semconv 漂移:OTel GenAI semconv 仍是实验态(Development),属性名会变;复用 P1 的 attributeMap 独立映射层,把自有 trace 字段和 semconv 解耦。
  3. 内容捕获 opt-in:prompt/SAR 正文默认不进 span(合规 + 隐私),仅元数据上报;正文捕获需显式开启。

关键内容

A. 四段 span 设计与"案件 ID 贯穿":把调查链路做成 trace 树

OTel GenAI agent span 约定(opentelemetry.io semconv,2026,Development 状态)给了现成的层级骨架:顶层 invoke_agent span,子 span 为每次 LLM 调用的 chat 和每次工具调用的 execute_toolgen_ai.conversation.id 作会话关联键。把 AML 调查映射上去——会话就是一个案件conversation.id := case.id,四个调查阶段是 invoke_agent 下的四个子 span:

trace (root: invoke_agent  AML-Investigation, gen_ai.conversation.id = "C037")
│  attrs: gen_ai.operation.name="invoke_agent", gen_ai.agent.name="aml-copilot"
│
├─ span①  evidence.gather            [证据汇集]
│    attrs: case.id="C037", aml.tx.count=14, aml.evidence.count=3
│    └─ (子) execute_tool  retrieveTransactions   ← 复用 P2 toolRegistry
│
├─ span②  typology.assess            [类型学比对]
│    attrs: case.id="C037", aml.typology.top="structuring",
│           aml.typology.score=0.85, aml.rule.hits=["STRUCT-01"]
│
├─ span③  sar.draft                  [SAR 草稿生成]
│    ├─ (子) chat  draftSarLlm        gen_ai.request.model="claude-opus-4-8"
│    │        attrs: gen_ai.usage.input_tokens=..., output_tokens=...
│    └─ (子) internal  verifyAnchors  aml.sar.violations=0, aml.sar.grounded=true
│            ↑ Day 72 闭世界验证结果进 span
│
└─ span④  hitl.review                [人工复核]
     attrs: case.id="C037", aml.review.action="approve",
            aml.review.actor="investigator", aml.sar.generatedBy="llm"

设计要害:case.id 是贯穿四段的关联键。OTel 的 trace_id 已经物理地把四个 span 绑在一棵树上,但 case.id(= conversation.id)是业务语义关联键——监管者按案件 ID 就能拉出完整 trace,不需要懂 trace_id。每个 span 的 case.id 属性冗余写入,是为了单 span 也能自证归属(日志被部分截断时仍可追溯)。

四段 span 各自要捕获的"决策可解释性"载荷(对应 EU AI Act"看了什么数据、识别什么模式、为何得出结论"):

span阶段捕获的决策载荷(满足可解释性义务)关键属性
① evidence.gather证据汇集看了哪些交易、筛出哪些证据aml.tx.count, aml.evidence.count
② typology.assess类型学比对命中哪些规则、各类型学得分、阈值aml.typology.top/score, aml.rule.hits
③ sar.draft草稿生成用了什么模型、token、引用是否接地gen_ai.request.model, aml.sar.grounded/violations
④ hitl.review人工复核谁、做了什么决定(approve/return/edit)aml.review.actor/action

这棵树就是"监管者可在数年后审查的文档化轨迹"的物化——span①.evidence.count=3 说清看了什么、span②.rule.hits 说清识别了什么模式、span③.grounded=true 说清 SAR 事实可溯源、span④.action=approve 说清人类终审了。四段缺一,可解释性义务就有缺口。

反直觉洞察①(trace 是给"未来的监管者"写的,不是给"现在的工程师"调试用的):APM 出身的工程师把 trace 当性能调试工具——看延迟、找瓶颈。AML 审计 trace 的设计目标完全不同:它的读者是数年后的监管者/审计师,要回答的是"这个 SAR 判定合规吗",不是"哪一步慢"。这导致两条相反的设计取向:① 业务语义属性(case.id/rule.hits/grounded)比性能属性(latency)重要得多;② trace 必须持久、不可篡改、长期可查(监管留存期常达 5 年),而非 APM 默认的几天 TTL。把 AML 审计 trace 当 APM 配(短 TTL、只记延迟)= 合规裸奔。

B. 独立属性映射层:为什么不直接写 semconv 属性名

OTel GenAI semconv 截至 2026-03 仍是 Development(实验)状态——agent/framework span 标 experimental,属性名可能随 spec 版本变(P1 attributeMap.ts 头注已核实此事)。如果四段 span 里硬编码 gen_ai.request.model 这种字符串,spec 一升级(比如改成 gen_ai.model.request_id),全仓库要全局替换,且历史 trace 和新 trace 属性名不一致,审计查询直接断裂。

解法是 P1 已建的独立属性映射层src/aml/observability/attributeMap.ts):自有 trace 字段 → semconv 属性"形状"的转换函数,属性名常量集中在一处(GEN_AI_ATTR)。四段 span 的落地复用并扩展它:

# 自有领域字段(稳定,我们自己定义,不随 OTel 漂移)
AmlSpanFields:
  caseId, stage, txCount, evidenceCount,
  topTypology, typologyScore, ruleHits[],
  requestModel, inputTokens, outputTokens,
  sarGrounded, sarViolations, reviewActor, reviewAction

# 映射层(隔离漂移):领域字段 → semconv 属性名
function toAuditAttributes(f: AmlSpanFields) → SemconvAttributes:
    attrs ← {}
    # 复用 P1 GEN_AI_ATTR 常量(漂移时只改这一处)
    if f.requestModel: attrs[GEN_AI_ATTR.REQUEST_MODEL] = f.requestModel
    if f.inputTokens:  attrs[GEN_AI_ATTR.USAGE_INPUT_TOKENS] = f.inputTokens
    # AML 私有属性(OTel 无对应,用 aml.* 命名空间,自管稳定性)
    attrs["aml.case.id"]         = f.caseId
    attrs["aml.stage"]           = f.stage
    attrs["aml.typology.top"]    = f.topTypology
    attrs["aml.sar.grounded"]    = f.sarGrounded
    attrs["aml.review.action"]   = f.reviewAction
    return attrs

关键设计:两类属性分治。OTel 已标准化的(model/tokens/finish_reason)走 GEN_AI_ATTR 常量、跟 spec 演进;OTel 没有的领域语义(case.id/typology/grounded/review)放 aml.* 私有命名空间,由我们自管稳定性、永不被 spec 升级影响。映射层是这两类的唯一汇合点。

反直觉洞察②(在实验态 spec 上直接建审计系统,等于在流沙上盖楼):直觉是"OTel 是行业标准,照着写就对了"。但 GenAI semconv 仍是 Development——直接硬编码其属性名,spec 一动,你数年的历史审计 trace 就和新 trace 对不上属性名,而审计恰恰要求跨年可查。映射层不是过度设计,是对"标准尚未稳定"这一事实的防御。更反直觉的是:最该稳定的属性(case.id/grounded 这些合规关键字段)恰恰是 OTel 没标准化的——所以把它们放进我们自管的 aml.* 命名空间,反而比依赖 OTel 标准更稳。对合规系统,"自己掌控的私有属性"比"实验态的行业标准"更可靠。

属性分治对照:

属性类别命名空间稳定性来源漂移风险升级处置
模型/token/finish_reasongen_ai.*OTel spec(Development)高(实验态)只改 GEN_AI_ATTR 常量一处
案件/类型学/接地/复核aml.*(私有)我们自管无(自定义)我们说了算
trace/span 结构OTel SDK 核心OTel 稳定 API跟 SDK 主版本

C. 内容捕获 opt-in:正文默认不进 span

OTel GenAI semconv 默认 prompt/completion 内容不采集(opt-in)(P1 attributeMap 头注已据此设计——不映射任何消息正文)。AML 场景这条要更严,有双重理由:

  1. 隐私/合规:SAR 正文含客户 PII(姓名、账号、SSN——B 节 Abrigo 列举的字段)。把正文塞进 telemetry 后端(Datadog/Langfuse)= PII 出域,触发 GDPR/数据驻留问题。
  2. 机密性:SAR 的存在本身受"禁止泄露(tipping-off)"约束——SAR 内容不该散落在通用可观测后端里。

所以四段 span 的内容策略:

默认(opt-out 内容):
  span 只记元数据 —— case.id, 模型, token 数, 得分, 接地结果, 复核动作
  ❌ 不记 prompt 正文、SAR 叙述正文、客户 PII

显式开启(opt-in,仅受控审计环境):
  set AML_AUDIT_CAPTURE_CONTENT=true
  → 正文写入独立的、加密的、受访问控制的审计存储(非通用 telemetry 后端)
  → 且记录"谁在何时开启了内容捕获"(捕获本身也是审计事件)

注意一个微妙点:审计要"可复现",但可复现不等于"把正文塞进 trace"。可复现性靠的是——span 里记了 case.id + model + 确定性的 verifyAnchors 结果,给定同一案件输入,模板基线(Day 71,确定性)可逐字节重放;LLM 部分虽有采样方差,但 grounded=true + 锚点已证明"事实层可溯源到证据"。审计需要的是"决策可解释 + 事实可溯源",不是"正文逐字留存在 telemetry 里"——后者是隐私负债,前者才是合规资产。

反直觉洞察③("全量留正文"不是更合规,是更不合规):直觉是"审计嘛,存得越全越安全"。错。AML 的 SAR 受 tipping-off 约束、含 PII,把正文全量塞进通用 telemetry 后端,制造的是数据泄露面而非合规证据。合规要的是"能证明决策正当 + 事实可溯源",这靠元数据 + 确定性可重放就够;正文要存也得进独立加密、强访问控制的审计库,且"开启内容捕获"这个动作本身要被审计。少存正文、多存可验证的元数据与接地结果,才是合规最优。

设计要点/决策表

要点决策理由
trace 结构四段 span 挂同一 invoke_agent 根证据→比对→草稿→复核全链路可审计
关联键case.id = conversation.id,四段冗余写监管按案件 ID 拉全链,单 span 自证归属
trace 读者面向未来监管者,非性能调试业务语义属性 > 性能属性;长期持久不可篡改
留存长 TTL(对齐监管留存期,常 5 年)审计要求跨年可查,非 APM 短 TTL
属性分治gen_ai.* 走 OTel 常量、aml.* 私有自管隔离实验态 spec 漂移,合规字段自管更稳
映射层复用 P1 attributeMap,唯一汇合点spec 升级只改一处,历史/新 trace 不断裂
内容捕获默认 opt-out,正文不进 spanPII + tipping-off;opt-in 进独立加密审计库
可复现靠元数据+确定性重放,非留正文决策可解释/事实可溯源即合规,正文是负债

对本项目的落地

  • src/aml/observability/attributeMap.ts 扩展:在现有 GEN_AI_ATTR/toSemconvAttributes 基础上,新增 AmlSpanFields 接口与 toAuditAttributes(fields)(B 节)——把四段 span 的领域字段映射为 gen_ai.*(复用现有常量)+ aml.*(私有命名空间)属性袋。aml.* 常量集中定义为 AML_ATTR,与 GEN_AI_ATTR 并列,便于自管稳定性。
  • 新建 src/aml/observability/auditTrace.ts:定义四段 span 的 span-like 形状(evidence.gather/typology.assess/sar.draft/hitl.review),导出 buildAuditTree(case, assessment, draft, review) → 返回四段 span 的属性树(纯数据结构,不真正上报——与 P1 attributeMap 同纪律:无后端 collector、无真实 LLM 元数据,仅构造 semconv"形状")。case.idconversation.id 贯穿。
  • 内容捕获开关auditTrace.tsAML_AUDIT_CAPTURE_CONTENT(默认 false);为 false 时 buildAuditTree 绝不写入任何 prompt/SAR 正文/PII 字段,仅元数据。为 true 时也仅在返回结构里标记 contentCaptureEnabled,并附"谁/何时开启"占位——真实加密审计库为后续构建。
  • 复用前序产出接 span:span② 读 Day 71/73 的 assessment(typology.top/score/rule.hits);span③ 读 Day 72 的 verifyAnchors 结果(grounded/violations)与 generatedBy;span④ 读 AuditEventsrc/aml/types.ts 已有 actor/action)。审计 trace 不重造数据,只把四天的产出串成可审计树。
  • 诚实标注:本日落 AmlSpanFields/toAuditAttributes/buildAuditTree/AML_ATTR 常量与 opt-out 内容策略;不真正导出 span(无 collector、无生产 LLM);长 TTL 持久化、不可篡改存储、加密审计库、内容捕获审计为 P3 上线后运营动作,不谎称已实现。模型 id 用显式 claude-opus-4-8,不写裸 "Claude"。

参考资料

  1. OpenTelemetry — Semantic Conventions for GenAI agent and framework spansinvoke_agent/create_agent/invoke_workflow span 命名规则;gen_ai.conversation.id 关联键;gen_ai.agent.name/id;全部标 Development(实验);span kind CLIENT/INTERNAL (2026-03)
  2. OpenTelemetry — Inside the LLM Call: GenAI Observability / agent span 树:顶层 invoke_agent + 子 chat(每次 LLM 调用)+ execute_tool(每次工具调用);Datadog/Honeycomb/New Relic 已支持,LangChain/CrewAI/AG2 原生 emit (2026)
  3. Unit21 — EU AI Act 2026 FAQs: What Fraud and AML Teams Need to Know:EU AI Act 令 AML AI 可解释性强制;从告警→调查→SAR/no-SAR 每个 AI 辅助决策需数年可审查的文档化轨迹;说不清"看了什么/识别什么模式/为何结论"即不合规;关键决策须人类终审 (2026)
  4. Oscilar — AI in AML: How FIs Can Use AI Without Breaking Compliance:审计轨迹须完整且持久;模型治理须记录输入变量选择、阈值justification (2026)
  5. 本仓库 src/aml/observability/attributeMap.ts(P1 独立映射层、内容 opt-out 纪律、semconv 漂移核实)、src/aml/types.ts(AuditEvent actor/action)、src/agent/mcp/toolRegistry(P2 工具,execute_tool 子 span 来源)、docs/aipa/day72-sar-llm-draft.md(verifyAnchors grounded)、docs/aipa/day71-sar-rule-baseline.md(确定性重放) (2026-06)

SOTA 检查 (2026-06-11)

  • OTel GenAI semconv 是 2026-06 LLM/agent 可观测的事实标准,但仍实验态:agent/framework span 截至 2026-03 标 Development;client span 趋稳。Datadog/Honeycomb/New Relic + LangChain/CrewAI/AG2 已采纳。本项目复用其骨架(invoke_agent + 四段子 span),但用独立映射层防漂移——正因 spec 未稳,映射层是 live 的必要防御(反直觉洞察②)。
  • 是否仍是 SOTA:✅ 是。AML 实务 2026 明确把"可审计 trace + 可解释决策"作部署前提:ThetaRay Ray(agentic 调查套件)、FIS+Anthropic Financial Crimes Agent(2026-05-04,证据汇集→SAR 叙述)、Fiserv agentOS AML Triage(2026-05-14)均强调可追溯与人工终审。本项目四段 span + case.id 贯穿与此方向一致。
  • 合规法规状态(须带状态标注):EU AI Act —— Article 50 透明义务 2026-08-02 生效(AI 生成内容标注/机器可读标记,SAR 草稿 span 应记 AI 生成标记;人工复核+编辑责任人有豁免空间,本项目强制 HITL 终审落在豁免取向);Annex III 高风险义务经 Digital Omnibus 临时协议(2026-05-07)推迟至 2027-12-02——给金融机构更多落地时间,但可解释/可审计的设计取向不变。SR 11-7(2011 美联储/OCC 模型风险管理三道防线,经典监管)+ NIST AI RMF GenAI Profile(2024-07)+ ISO/IEC 42001:2023 为治理框架配套。
  • 更新替代品监测:SHAP/attention heatmap/graph motif 等可解释性技术正被集成进 SAR drafting(Oscilar 2026)——可作 span② typology.assess 的"为何命中"增强载荷;本项目 v1 用规则 description+得分已满足"可解释"下限,SHAP 级解释留待后续,回填本笔记。
  • 过时认知警示:"审计就该全量留正文"是过时直觉——AML 的 tipping-off + PII 约束下,全量正文进通用 telemetry 后端是泄露负债;2026 合规最优是"元数据 + 确定性可重放 + 接地结果",正文进独立加密审计库(反直觉洞察③)。
  • 待跟踪:OTel GenAI semconv 从 Development 转 Stable 的时点(届时 aml.*gen_ai.* 分治边界可能调整);EU AI Act Article 50 实施细则对"SAR 草稿机器可读标记"的具体格式要求;监测 FIS/Fiserv 2026 H2 GA 的 SAR Agent 是否公布其审计 trace schema 以对标。