金融业务里,模型每一次批、拒、调额的背后,都对应着真金白银和一份合规责任。监管一旦追问「这个结论凭什么得出」,答不上来就不再是技术问题,而是业务风险:举证不能、责任不清,最后往往由机构买单。
很多人以为把对话存进数据库、给请求加个 trace_id 就算留痕了。可真要求复现,才发现模型版本、提示词模板、检索语料、温度参数全都不在快照里,同一输入跑出十个结果;日志能改能删,出了事故互相推诿,追责与不可抵赖更是无从谈起。
本文分享一套金融级 AI 决策治理的落地做法:证据链日志 + 版本快照复现 + 责任矩阵签批 + 哈希链签名存证,可直接落地。
一、四性拆解:可追溯和可复现的边界
先把四个词的边界划清楚,后面的设计才不会跑偏:
- 可追溯:回答「当时用了什么输入、什么版本、得到什么输出」,重在记录齐全。
- 可复现:同一输入重新算出同一结果,重在锁死随机性与全部版本。
- 可追责:每个环节有明确责任人与授权边界,重在签批留痕。
- 不可抵赖:日志事后改不了也否认不了,重在密码学存证。
| 目标 | 关键手段 | 典型失败后果 |
|---|---|---|
| 可追溯 | 全链路证据日志、统一 trace_id | 监管问询无法举证 |
| 可复现 | 固定 seed、版本快照、依赖锁定 | 结论被质疑为黑箱 |
| 可追责 | 责任矩阵、人机双签 | 事故责任无法划分 |
| 不可抵赖 | 哈希链、数字签名、时间锚定 | 日志被认定可伪造 |
核心结论: 四性是一条递进的链------先记全,才谈得上复现;能复现,才分得清责任;有了签批与签名,才赖不掉。
二、可追溯:为一次决策拼出完整证据链
证据链的本质,是给每次调用留一份可拼装的档案:
- 输入侧:原始请求、脱敏标识、检索命中的文档片段及其索引版本号。
- 推理侧:模型名与权重指纹、温度与 seed、提示词模板版本、依赖锁定文件。
- 输出侧:结果、置信度、命中的风控规则编号、人工复核记录与时间戳。
下面这份记录是建议落库的最小结构,可直接当作表结构设计的起点:
json
{
"trace_id": "dec_20250612_9f31c2",
"biz": {"scene": "loan_limit_precheck", "user_tag": "u_8***21"},
"input": {"request": "申请提额至 5 万", "docs": ["kb_v12:p-881", "kb_v12:p-903"]},
"model": {"name": "fin-llm-7b", "weight_sha": "b1e4c7a9", "prompt_tpl": "limit_v7", "temp": 0, "seed": 42},
"output": {"result": "approve_5w", "confidence": 0.86, "rules_hit": ["R-112"]},
"review": {"approver": "zhang***", "signed_at": "2025-06-12T10:31:07+08:00"},
"chain": {"prev_hash": "8a51f0d3", "self_hash": "c0d27e11"}
}
一条链上任何一环缺失,追溯就断在那个缺口处。
三、可复现:把随机性锁进版本快照
复现不是「再问一次」,而是重建当时的全部运行条件:
- 锁随机性 :
temperature=0、显式seed、固定采样与解码库版本。 - 锁版本集合:模型权重 hash、提示词模板 hash、检索索引快照 id、依赖 lock 文件。
这段 Python 展示「生成快照 → 按快照复现」的最小闭环,适用于 Python 3.10+:
python
import json, hashlib, random
def snapshot(model, prompt, docs, temp=0.0, seed=42):
"""把一次决策的全部运行条件固化成可寻址快照"""
random.seed(seed)
return {
"seed": seed, "temp": temp,
"weight_sha": model.weight_sha, # 模型权重指纹
"prompt_sha": hashlib.sha256(prompt.encode()).hexdigest(),
"index_id": docs.index_id, # 检索索引快照
"dep_lock": hashlib.sha256(open("poetry.lock", "rb").read()).hexdigest(),
}
def reproduce(snap, prompt, model):
"""按快照重建条件重跑,返回可与原记录逐字 diff 的结果"""
random.seed(snap["seed"])
assert model.weight_sha == snap["weight_sha"], "模型版本漂移,拒绝复现"
return model.generate(prompt, temperature=snap["temp"], seed=snap["seed"])
复现的本质:输入 + 参数集合 + 版本集合,三者全部可寻址。
四、可追责:用责任矩阵划定人机边界
责任要落到具体的人和清晰的机器边界:
- 人机分界:低风险自动执行、中风险人机双签、高风险人工终审,阈值写进配置而非口头约定。
- 留痕签批:谁批的、依据哪条规则、在哪个时间窗口,全部写回证据链的 review 字段。
| 风险等级 | 决策方式 | 责任主体 | 必须留痕 |
|---|---|---|---|
| 低(话术建议) | 模型自动执行 | 模型 owner | 输入输出与版本快照 |
| 中(额度预审) | 模型给出 + 复核员双签 | 复核员 + 模型 owner | 双签记录与差异说明 |
| 高(拒贷终审) | 人工终审,模型仅作参考 | 审批人 | 终审依据与规则编号 |
没有签批记录的自动化,出事故时等于无人负责。
五、不可抵赖:哈希链与签名把日志变证据
日志能改就能赖账,把它串成链再签名即可:
- 哈希链:每条记录都带上一条的 hash,改动任一环节会导致后续校验全部失败。
- 数字签名:机构私钥对链尾签名,外部只需公钥即可独立验真,无需信任我方数据库。
这段 Python 用标准库做哈希链、用 Ed25519 做签名,可直接改造成写日志的中间件:
python
import hashlib, json, time
from nacl.signing import SigningKey
chain, sk = [], SigningKey.generate() # 生产环境改用机构托管私钥
def append(record: dict) -> dict:
prev = chain[-1]["self_hash"] if chain else "0" * 64
payload = {"ts": time.time_ns(), "prev": prev, "rec": record}
digest = hashlib.sha256(json.dumps(payload, sort_keys=True).encode()).hexdigest()
entry = {**payload, "self_hash": digest}
entry["sig"] = sk.sign(digest.encode()).signature.hex()
chain.append(entry)
return entry
def verify(chain) -> bool:
prev = "0" * 64
for e in chain:
body = json.dumps({k: e[k] for k in ("ts", "prev", "rec")}, sort_keys=True)
if e["prev"] != prev or hashlib.sha256(body.encode()).hexdigest() != e["self_hash"]:
return False
prev = e["self_hash"]
return True
链式哈希保证「改不了」,数字签名保证「赖不掉」。
六、审计交付:审计包必备的六类材料
监管来查时交付的不是一堆日志,而是一份自证的审计包:
- 范围声明:时间区间、业务线、涉及模型与决策条数。
- 链尾签名:区间末条记录的 hash、链尾签名与当前验签公钥。
- 抽样证据:每类风险等级各抽若干条完整决策记录。
- 复现脚本:一条命令重跑抽样决策,输出与原记录逐字 diff。
- 验真步骤:第三方用公钥验签、用锚点比对 Merkle 根的操作说明。
- 变更台账:期间内提示词、模型、规则的变更单与审批记录。
审计包的价值不在材料多,而在第三方能独立复算并得出同样结论。
七、链式外锚:把链尾推给可信时间源
单机日志仍可能被整体重写,必须把链尾推出去:
- 批量锚定:按日把当日链尾两两哈希压成 Merkle 根,写入内部存证服务或可信时间源。
- 定期核验:每日任务重算本地根与锚点比对,不一致立刻告警并冻结写入口。
这段 bash 适合挂到 cron,每日压缩链尾并推送存证:
bash
#!/usr/bin/env bash
# 把当日链尾两两哈希压缩成 Merkle 根,交内部存证服务盖时间戳
mapfile -t level < <(cat logs/tails/*.txt | sort)
while (( ${#level[@]} > 1 )); do
next=()
for ((i = 0; i < ${#level[@]}; i += 2)); do
right=${level[i+1]:-${level[i]}}
next+=("$(printf '%s%s' "${level[i]}" "$right" | sha256sum | cut -d' ' -f1)")
done
level=("${next[@]}")
done
file="anchor/$(date +%F).json"
echo "{\"date\":\"$(date +%F)\",\"root\":\"${level[0]}\"}" > "$file"
curl -sS -X POST https://notary.internal/anchor --data-binary @"$file"
本地链自证完整性,外部锚点证明「当时就在」。
八、排错对照:四类高频问题的根因处置
落地时最容易踩的坑集中在四类,建议直接贴到值班手册里:
| 现象 | 根因 | 处置 |
|---|---|---|
| 复现结果与当时不一致 | 未固定 seed 或模型静默升级 | 快照记权重 hash,发布前强制比对 |
| 监管要日志却取不出 | 明文与索引过期被清理 | 热层 90 天 + 冷层 WORM 归档分离 |
| 篡改发生却没被发现 | 只存 hash、不成链、不锚定 | 改为链式结构并每日锚点核验 |
| 出事故后互相推诿 | 责任矩阵没落到字段 | 审批人与规则编号写进证据链 |
问题:版本漂移、日志可改、证据缺失、责任悬空;治理:快照锁版、链签存证、全量留痕、签批落库。
九、成本控制:分层留存与分级采样
全量明文留存会把存储成本打爆,按风险分级最划算:
- 热层:近 90 天保留明文与检索索引,支撑秒级追溯与随时复现。
- 冷层:更早数据转入对象存储的 WORM 桶,只留链与签名,按年抽样解封核验。
- 采样策略:低风险抽 5% 留全量快照,中高风险 100% 留存,复现能力按风险分层付费。
分级不是少存,而是把「查得到」和「查得快」分开买单。
十、落地路线:从试点到生产的四步走
先在一条业务线跑通闭环,再横向复制:
取证采集 → 快照复现 → 签批追责 → 链签存证
- 第一阶段(2~4 周):单条风控链路上线证据日志与版本快照,验证同输入可逐字复现。
- 第二阶段(1~2 月):接入签批流与哈希链,打通存证服务,跑一次内部模拟审计并补齐缺口。
之后按业务线复制,把四性做成统一 SDK,而不是每条链路各写一遍。
结语
金融级 AI 的门槛,从来不在于模型多聪明,而在于出了问题能不能把话说清楚:能翻出当时的输入与版本、能重算出同样的结果、能指认谁签的批、能证明记录没被动过------这四件事凑齐,模型才敢被放到业务主链路上。
它们不是四套孤立系统,而是一条贯通的证据流:日志负责记全,快照负责复现,签批负责定责,链签负责封存。先在一条链路上跑通闭环,再沉淀成 SDK 横向复制,成本和风险都可控。
可追溯决定能不能查,可复现决定查了算不算数,可追责决定谁来担,不可抵赖决定这套说法站不站得住。