审计轨迹 I — 全链路 OTel 与"案件 ID 贯穿"四段 span
审计轨迹 I — 全链路 OTel 与"案件 ID 贯穿"四段 span
日期: 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 贯穿始终。
三件事讲透:
- 四段 span 设计 + 案件 ID 贯穿:证据→比对→草稿→复核四阶段各为一个 span,全部挂在同一案件根 span 下,
case.id作为关联键贯穿。 - 独立属性映射层防 semconv 漂移:OTel GenAI semconv 仍是实验态(Development),属性名会变;复用 P1 的
attributeMap独立映射层,把自有 trace 字段和 semconv 解耦。 - 内容捕获 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_tool;gen_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_reason | gen_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 场景这条要更严,有双重理由:
- 隐私/合规:SAR 正文含客户 PII(姓名、账号、SSN——B 节 Abrigo 列举的字段)。把正文塞进 telemetry 后端(Datadog/Langfuse)= PII 出域,触发 GDPR/数据驻留问题。
- 机密性: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,正文不进 span | PII + 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.id作conversation.id贯穿。 - 内容捕获开关:
auditTrace.ts读AML_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④ 读AuditEvent(src/aml/types.ts已有 actor/action)。审计 trace 不重造数据,只把四天的产出串成可审计树。 - 诚实标注:本日落
AmlSpanFields/toAuditAttributes/buildAuditTree/AML_ATTR常量与 opt-out 内容策略;不真正导出 span(无 collector、无生产 LLM);长 TTL 持久化、不可篡改存储、加密审计库、内容捕获审计为 P3 上线后运营动作,不谎称已实现。模型 id 用显式claude-opus-4-8,不写裸 "Claude"。
参考资料
- OpenTelemetry — Semantic Conventions for GenAI agent and framework spans:
invoke_agent/create_agent/invoke_workflowspan 命名规则;gen_ai.conversation.id关联键;gen_ai.agent.name/id;全部标 Development(实验);span kind CLIENT/INTERNAL (2026-03) - OpenTelemetry — Inside the LLM Call: GenAI Observability / agent span 树:顶层 invoke_agent + 子 chat(每次 LLM 调用)+ execute_tool(每次工具调用);Datadog/Honeycomb/New Relic 已支持,LangChain/CrewAI/AG2 原生 emit (2026)
- Unit21 — EU AI Act 2026 FAQs: What Fraud and AML Teams Need to Know:EU AI Act 令 AML AI 可解释性强制;从告警→调查→SAR/no-SAR 每个 AI 辅助决策需数年可审查的文档化轨迹;说不清"看了什么/识别什么模式/为何结论"即不合规;关键决策须人类终审 (2026)
- Oscilar — AI in AML: How FIs Can Use AI Without Breaking Compliance:审计轨迹须完整且持久;模型治理须记录输入变量选择、阈值justification (2026)
- 本仓库
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 以对标。