Jev Recipe / 合规基础设施

用 Jev 做决策审计追踪:每个答案可记录、可重放、可解释

把自动化决策变成合规级证据:Jev 的每次 evaluate 调用都返回带校准置信度的类型化答案,审计日志因此能同时记录契约、答案与闸门——小到可以永久保存,确定到可以重放,清晰到可以回应 GDPR 第 22 条。

构建决策审计追踪的 6 个步骤

01记录契约,而不只是答案。无法解释的决策就是负债,所以每条记录都要在答案旁边带上 schema 版本、questions 对象的哈希和模型版本。questions 对象就是机器可读的决策逻辑——含每个选项的 criteria——这正是审计员或数据主体问「为什么」时想要的东西。每个 schema 版本只存一份、其余引用,一百万条决策的存储成本和一条一样。
02存完整类型化答案,含置信度。答案说明模型选了什么,校准置信度说明这个选择值多少信任。因为 Jev 的置信度经 RLCD 校准,日志里的一条 0.85 就是一份「85% 准确率合同」——几年后监管者或用户问「这类决策多少次是对的」时,这个数字仍然有意义。Choice 题还要存选项分布:落选选项就是建设期没人想到要去要的解释。
03双轴闸门:置信度 × 可逆性。退款批准上的 0.97 和额度提升上的 0.97 不是同一种决策——前者点一下就回滚,后者改变一个人的财务足迹。只有置信度过闸「且」可逆性低才自动执行;中间带进复核队列;高可逆性即使高置信度也强制人工签核。双轴规则只有四行代码,却是「闸门」和「橡皮图章」的区别。
04给重要自动化决策写第 22 条级记录。GDPR 第 22 条赋予个人不受完全自动化决策支配的权利(当该决策产生法律效力或类似重大影响时)——以及获得解释的权利。兑现它的记录长这样:主体引用、决策及其置信度、逻辑块(schema 版本、questions 哈希、当时生效的闸门阈值)、人工介入路径、保留期限。某个决策算不算「重大」是你们团队的法律判断——日志的存在是为了让无论哪种答案都可被证明。
05用重放代替迁移。因为 state 和 questions 都在存储里,schema 升级后可以让每个历史案例在新契约下重新决策——确定性地、每案一次前向。重放一个东西当三个工具用:迁移检查(新 schema 改了哪条车道?)、回归套件(日志里的 state 就是你带标注的数据集)、解释引擎(这个决策在当日的规则下说了什么)。
06监控置信度分布,而不只是准确率。跟踪自动执行占比、复核率、以及争议区间的流量。校准会安静地退化——第三方对决策模型的审计已经抓到过「对错得多的答案报 30%+ 置信度」的案例——而最早的症状就是你自己的分布在移动。复核率上升不是 bug,是传感器在工作。置信度门控降级链用在实时流量上的那套漂移纪律,这里用在历史上。
schema / 决策·可逆性·人工确认契约
{
  "decision": {
    "type": "choice",
    "instructions": "按公开政策裁决本案",
    "criteria": {
      "approve": "满足全部政策条件——无需自由裁量",
      "refer": "满足大部分条件但命中已声明的边缘情形——附原因转队列",
      "deny": "触碰硬性条件——拒绝并指明该条件"
    }
  },
  "reversibility": {
    "type": "score",
    "instructions": "如果这个决策错了,逆转的代价多大?1(轻松可逆,如退款)到 4(对个人造成不可逆伤害,如上报征信的账户关闭)"
  },
  "needs_human_review": {
    "type": "noul",
    "instructions": "综合本案与当前置信度,决策生效前是否需要人工确认?"
  }
}
把一个真实案例作为 state 发送,检查 decision、reversibility 与 needs_human_review 的校准置信度。

六次决策,一本审计账

下面每次决策都已经跑过 evaluate 端点。拖动两道闸门——自动执行置信度阈值与人工接管阈值——看车道实时重算。高置信度本身不够:额度提升调用置信度 0.93 仍进复核队列,因为可逆性是第二条轴。

自动执行 · 2复核队列 · 2人工决策 · 2
决策答案与置信度可逆性车道

退款自动批准 · 工单 #4812

dec_8f21 · audit-contract@1.3.0 · jev-1.13 · 96ms

批准0.97可逆 1/4自动执行

内容发布门禁 · 草稿 #2214

dec_8f22 · audit-contract@1.3.0 · jev-1.13 · 88ms

拦截0.88可逆 2/4自动执行

额度提升申请 · 客户 #7741

dec_8f23 · audit-contract@1.3.0 · jev-1.13 · 102ms

转人工0.93可逆 3/4Art. 22 记录复核队列

优惠投放 · 客户 #2298

dec_8f24 · audit-contract@1.3.0 · jev-1.13 · 91ms

标准0.71可逆 1/4复核队列

工单升级 · 工单 #5190

dec_8f25 · audit-contract@1.3.0 · jev-1.13 · 84ms

false0.55可逆 2/4人工决策

账户冻结 · 客户 #3306

dec_8f26 · audit-contract@1.3.0 · jev-1.13 · 99ms

冻结0.78可逆 4/4Art. 22 记录人工决策

仅演示数据——六条模拟决策。车道按与文案相同的双轴规则实时重算。

决策审计证据:Jev 类型化日志 vs 生成式聊天日志 vs 无决策日志

METRIC
Jev
聊天日志 / 规则审计
日志里有什么
类型化答案 + 校准置信度 + schema 版本 + questions 哈希——定长记录,便宜到可以永久保存
一段自由文本(或解析了 JSON 但没有置信度);产生它的提示词版本通常已不可考
解释一个决策
criteria 块就是解释——审计员读选项定义加上选项分布即可
对生成文本的事后合理化;同一问题问两次,解释就会变
置信度作为证据
RLCD 校准——日志里的一条 0.85 等价于 85% 准确率合同,仅凭日志就能证明闸门行为
自我上报且未校准——独立审计已抓到决策模型对错得多的答案报 30%+ 置信度;规则引擎则完全没有置信度
升级后重放
日志 state + 版本化 questions 可确定性重判——每个历史案例都是一个可重跑的回归用例
提示词漂移、模型弃用——原始决策不可复现,改动只能盲发
漂移检测
置信度分布、复核率、争议带流量都是可测的时间序列——校准退化有早期症状
文本输出没有可比信号,漂移靠投诉或事故发现
人工接管证据
升级车道、生效阈值、复核人动作都住在同一条记录里
审批散落在工单和聊天记录里,与触发它的决策互不关联

生产代码:TypeScript 与 Python 双端审计管道

python / 基线:解释不了的聊天日志
import logging
import os

import requests

logger = logging.getLogger("decisions")

def decide_with_chat_model(customer_id: str, case: dict) -> str:
    resp = requests.post(
        "https://api.openai.com/v1/chat/completions",
        headers={"Authorization": f"Bearer {os.environ['OPENAI_API_KEY']}"},
        json={
            "model": "gpt-4o-mini",
            "messages": [{
                "role": "user",
                "content": f"按政策 v3 裁决本案,回复决策与一段理由。\nCase: {case}",
            }],
        },
        timeout=30,
    )
    decision_text = resp.json()["choices"][0]["message"]["content"]

    # 审计日志拿到的是一段话。无法设阈值、无法重放、无法解释——
    # 只能重读。这次调用用的是哪个版本的提示词?「大概率批准」
    # 指的是 0.8 的置信度吗?日志说不出来,因为根本没记。
    logger.info("decision for %s: %s", customer_id, decision_text)
    return decision_text
python / jev:一次 evaluate 调用,一条审计记录
import hashlib
import json

import requests

JEV_ENDPOINT = "https://api.typesafe.ai/v1/jev/evaluate"
AUTO_EXECUTE_CONFIDENCE = 0.85  # 过闸 且 可逆性 <= 2 -> 自动执行
HUMAN_CONFIDENCE = 0.60         # 低于此,或可逆性 4 -> 人工

QUESTIONS = {
    "decision": {
        "type": "choice",
        "instructions": "按公开政策裁决本案",
        "criteria": {
            "approve": "满足全部政策条件——无需自由裁量",
            "refer": "满足大部分条件但命中已声明的边缘情形",
            "deny": "触碰硬性条件——指明该条件",
        },
    },
    "reversibility": {
        "type": "score",
        "instructions": "如果错了,逆转代价多大?1 = 轻松可逆,4 = 对个人不可逆伤害",
    },
    "needs_human_review": {
        "type": "noul",
        "instructions": "决策生效前是否需要人工确认?",
    },
}

def decide_and_record(subject_ref: str, state: dict) -> dict:
    payload = {"state": state, "questions": QUESTIONS}
    resp = requests.post(JEV_ENDPOINT, json=payload, timeout=5)
    resp.raise_for_status()
    data = resp.json()

    decision = data["decision"]
    reversibility = data["reversibility"]["answer"]
    if data["needs_human_review"]["answer"] or reversibility == 4:
        lane = "human_decision"
    elif decision["confidence"] >= AUTO_EXECUTE_CONFIDENCE and reversibility <= 2:
        lane = "auto_executed"
    else:
        lane = "review_queue"

    # 记录即审计追踪:契约、答案、闸门——一行定长记录,
    # 可在任意未来 schema 版本下重放。
    return {
        "subject_ref": subject_ref,
        "decision": decision["answer"],
        "confidence": decision["confidence"],
        "reversibility": reversibility,
        "lane": lane,
        "logic": {
            "schema_version": "audit-contract@1.3.0",
            "questions_hash": hashlib.sha256(
                json.dumps(payload["questions"], sort_keys=True).encode()
            ).hexdigest()[:16],
            "model_version": "jev-1.13",
        },
        "human_intervention_path": (
            "available_on_request"
            if lane == "auto_executed"
            else "mandatory_before_effect"
        ),
    }
typescript / 基线:结构化输出,非结构化证据
import { z } from "zod";
import OpenAI from "openai";

const Decision = z.object({ decision: z.enum(["approve", "refer", "deny"]) });
const openai = new OpenAI();

export async function decideCase(caseData: unknown) {
  const resp = await openai.chat.completions.create({
    model: "gpt-4o-mini",
    response_format: { type: "json_object" },
    messages: [
      { role: "user", content: `按政策 v3 裁决: ${JSON.stringify(caseData)}` },
    ],
  });
  const { decision } = Decision.parse(
    JSON.parse(resp.choices[0].message.content!),
  );

  // 形状有保证,证据没有。没有置信度、没有 schema 版本、没有
  // 分布——六个月后的审计只能拿到「是什么」,永远拿不到
  // 「为什么」和「多少次是对的」。
  return { decision, auditRecord: { decision } };
}
typescript / jev:双轴闸门,端到端类型化
const JEV_ENDPOINT = "https://api.typesafe.ai/v1/jev/evaluate";
const AUTO_EXECUTE_CONFIDENCE = 0.85; // 过闸 且 可逆性 <= 2
const HUMAN_CONFIDENCE = 0.6; // 低于此,或可逆性 4 -> 人工

type Lane = "auto_executed" | "review_queue" | "human_decision";

const QUESTIONS = {
  decision: {
    type: "choice",
    instructions: "按公开政策裁决本案",
    criteria: {
      approve: "满足全部政策条件",
      refer: "命中边缘情形——附原因转队列",
      deny: "触碰硬性条件——指明该条件",
    },
  },
  reversibility: {
    type: "score",
    instructions: "如果错了,逆转代价多大?1 = 轻松可逆,4 = 不可逆伤害",
  },
  needs_human_review: {
    type: "noul",
    instructions: "生效前是否需要人工确认?",
  },
} as const;

export async function decideAndRecord(subjectRef: string, state: object) {
  const resp = await fetch(JEV_ENDPOINT, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ state, questions: QUESTIONS }),
  });
  if (!resp.ok) throw new Error("Jev evaluate failed");
  const data = await resp.json();

  const { answer, confidence } = data.decision as {
    answer: string;
    confidence: number;
  };
  const reversibility = data.reversibility.answer as number;
  const lane: Lane =
    data.needs_human_review.answer || reversibility === 4
      ? "human_decision"
      : confidence >= AUTO_EXECUTE_CONFIDENCE && reversibility <= 2
        ? "auto_executed"
        : "review_queue";

  return {
    subject_ref: subjectRef,
    decision: answer,
    confidence,
    reversibility,
    lane,
    logic: { schema_version: "audit-contract@1.3.0", model_version: "jev-1.13" },
    human_intervention_path:
      lane === "auto_executed" ? "available_on_request" : "mandatory_before_effect",
  };
}
// Art. 22 记录 = 这个对象。重放 = 把存储的 state 以新 schema 版本
// 重新提交,然后 diff 车道。

决策审计追踪 FAQ

什么是决策审计追踪?

一条带时间戳、可查询的记录,记下系统做出的每一次自动化决策:决策是什么、置信度多少、在哪个 schema 与模型版本下、经过哪道闸门、人工介入路径是什么。用 Jev 时这份记录几乎是从 evaluate 响应里白拿的——类型化答案、校准置信度、选项分布本来就是结构化的,所以日志是一条定长记录,而不是一段话。

GDPR 第 22 条要求这个吗?

第 22 条赋予个人围绕「仅自动化进行、且产生法律效力或类似重大影响」的决策的权利——包括不受其支配,以及在例外情形下获得关于决策逻辑的有意义信息。某个具体决策是否落在其内,是你们团队的法律判断,不是日志库能替你决定的。本 recipe 提供的是证据层:如果你们把某类决策视为重大,第 22 条记录(主体、决策、置信度、逻辑版本、人工路径、保留期限)就是一个对象;如果判断为不重大,日志正是让你能始终如一地为这个判断辩护的东西。

一条决策记录应该包含什么?

六个字段:主体引用(尽可能假名化)、决策及其置信度、逻辑块(schema 版本、questions 哈希、模型版本、当时生效的闸门阈值)、Choice 题的选项分布、闸门判给的车道、以及人工介入路径(若有人工动作则含复核人动作)。除主体和复核人动作外,其余全部来自那一次 evaluate 响应。

这和普通的应用日志有什么区别?

应用日志回答「代码做了什么」;审计追踪回答「系统决定了什么、多有把握、依据哪套规则」。差别在结构:类型化决策记录可以设阈值统计(置信度低于 0.9 的自动执行发生了多少次?)、可以重放(新 schema 会怎么判?)、可以聚合(置信度分布在漂移吗?)——这三件事对自由文本日志行都做不了。

schema 升级后,能对旧案例重新决策吗?

能——这就是重放,而且是确定性的。因为 state 和版本化 questions 都在存储里,升级契约意味着把每条日志 state 重新提交给新 schema,然后 diff 车道。团队把重放当三件事用:迁移检查(升级有没有改变任何自动执行的决策?)、回归套件(日志 state 就是历史标注数据)、以及解释引擎(当有人问「当时的规则在今天怎么说」)。

为什么校准置信度对审计很重要?

因为置信度数字只有映射现实时才是证据。Jev 置信度经 RLCD 校准,一条 0.85 闸门对应可测量的准确率合同——你可以用自己的日志算出 0.85 档的决策多少次是对的。未校准的自我上报置信度撑不起这件事:对决策模型的独立审计发现过对错得多的答案报 30%+ 置信度,那会让每条日志阈值都沦为装饰。校准还让漂移可见——分布移动时,复核率会先于你的用户告诉你。

继续扩展合规与门控运营