第 16 章:法律与合规——Legal and compliance
第 16 章:法律与合规——Legal and compliance
合规判断有个特别之处:它必须能被解释、被追溯、被审计。监管者问"你为什么判这条不算违规"时,一句"模型说的"是交不了差的。本章讲 Jev 怎么把法规条款匹配、合同条款抽取、政策判定这三类判断,做成一条留下概率与理由的可审计流水线。
合规判断的硬约束:可审计
一般的内容分类错了,改一版模型重刷一遍就行。合规判断错了,后果是罚款、诉讼或者审计不通过。这类判断有三条硬约束,它们直接决定了技术方案长什么样。
第一条,要概率,不要结论。"这条交易有 62% 的概率触发反洗钱政策"和"这条交易违规"是两种完全不同的输出。前者能支撑分级处置——高风险转法务、中风险进人工队列、低风险自动放行;后者一旦错了就是硬伤,而且你没法对判定质量做统计。Jev 的 Noul 返回 P(true),Choice 返回每个选项的概率,Score 返回有序分布,正好匹配这个需求。
第二条,要理由,不要黑箱。审计时被追问的往往是"这一条依据哪条政策判的""为什么这份合同被标了高风险"。这要求判断链路可回溯:state 是什么、问了哪些类型化问题、命中了哪个选项、概率是多少。
第三条,要确定性,不要漂移。同一份合同今天判"低风险"、明天判"高风险",这本身就是一次合规事故。锁定决策层版本(写 jev-1.13 而不是 jev-latest),并对概率做区间断言,才能保证行为可复现。
官方把这一类判断归为"监管、合同或政策一致性决策,且必须可审计"。这三条约束加在一起,把合规判断推向了类型化决策层,而不是生成模型。
法规与内部政策的条款匹配
合规的第一类判断是条款匹配:给定一段事实(一笔交易、一条行为、一份说明),判断它是否触发某条法规或内部政策,触发哪一条,风险多高。
最容易犯的错,是把它做成一个"大分类器"——把几十条法规塞进一个 Choice,让模型选一条。但法规之间大量重叠,"这笔交易既可能触发 A 也可能触发 B",一个单选逼不出这种信息,而且单点结论无法解释。
更稳的做法是逐条判定:每条政策写成一个 Noul 问题,独立返回概率;再用一个 Score 给整体定级,用一个 Choice 归到主要违规类型。逐条 Noul 还带来一个工程上的好处——政策可以增删,加一条新规就是加一个问题,不需要重新训练任何东西。
有人会问,条款匹配能不能靠关键词或向量检索解决。关键词召得全但精确度低,一段正常描述里出现的"拆分""单笔"这类词会把无关交易全捞进来;向量检索能抓到语义,却给不出"是否触发"这个封闭判定,更没有可入账的概率。Jev 的价值恰恰在于把两件事拆开——检索负责把相关条款捞回来,逐条 Noul 负责给出判定。
from typesafe import TypesafeClient
client = TypesafeClient()
facts = """
交易:某供应商在一个季度内向同一收款账户分 7 笔付款,
单笔金额 9.2 万至 9.8 万,累计 66.4 万元。
付款审批由采购专员一人完成,未见复核记录。
"""
decision = client.systemone(
state=facts,
questions={
# 逐条政策,各判一次,返回 P(true)
"structuring": {
"type": "noul",
"question": "该交易是否存在刻意拆分金额以规避大额审批阈值的迹象?"
},
"single_approver": {
"type": "noul",
"question": "该交易是否违反了'大额付款需双人复核'的内部政策?"
},
# 风险定级:有序评分
"risk": {
"type": "score",
"question": "该交易的合规风险等级?",
"labels": ["可忽略", "低", "中", "高", "严重"]
},
# 主要类型:封闭多选
"category": {
"type": "choice",
"question": "若需上报,应归入哪一类?",
"choices": ["反洗钱", "采购舞弊", "无合规问题"]
}
},
instructions="你是合规分析助手。只依据给出的交易事实判断,不要引入未提及的背景。"
)
# 控制流仍然在代码里
if decision["structuring"].probability >= 0.7 or decision["risk"].score >= 4:
escalate_to_compliance(decision)注意 instructions 里那句"只依据给出的交易事实判断"。合规场景的幻觉代价特别高——模型补出一个不存在的背景,可能直接把一笔正常交易判成违规。这类"证据边界"的约束,必须写进指令。
合同条款抽取与风险标注
第二类判断是合同审查:从一份长合同里抽出关键条款,给每条打风险标签。它落在两个形态上——Structured Data Extraction 把非结构化条款转成字段,Verification 核对某条是否满足要求。
抽取字段的难点不在"抽",而在缺失值处理。合同千差万别,很多字段本来就是缺失的。强迫模型必须给一个值,它就会编。正确做法是给每个枚举字段留一个"未提及"选项,只把高置信的字段自动写入,其余留在人工队列。
contract_terms = client.systemone(
state=contract_text,
questions={
"termination_notice": {
"type": "choice",
"question": "合同的终止通知期是多长?",
"choices": ["30 天", "60 天", "90 天", "其他", "未提及"]
},
"auto_renew": {
"type": "noul",
"question": "合同是否包含自动续约条款?"
},
"unilateral_change": {
"type": "noul",
"question": "是否存在允许单方变更价格或服务范围的条款?"
},
"unfair_penalty": {
"type": "noul",
"question": "违约金是否显著高于行业惯例(例如超过合同金额的 30%)?"
}
},
instructions="你是合同审阅助手,逐条比对合同原文,找不到依据就判否或未提及,不要推测。"
)
risk_flags = [
k for k, v in contract_terms.items()
if k != "termination_notice" and v.probability >= 0.6
]risk_flags 是一串命中的风险点,每个都带概率。法务看到的不只是"这份合同有风险",而是"自动续约、单方变更、高额违约金这三条各有多大概率成立"——这才是能接手判断的输入。
Policy-as-Code:把政策写成 YAML
前面两节把政策逻辑写进了代码。再进一步,是把政策本身变成数据——Policy-as-Code。目标是让 DevOps 和安全团队不改代码、只改一份 YAML,就能增删治理规则,而 Jev 负责按规则做语义判定。
这个方向上目前最完整的探索是 Jev Policy Engine(BhavinM/jev-policy-engine),一个通用的 Policy-as-Code SDK:把治理规则写成 YAML,由 Jev 执行,并带 audit mode(只记录不拦截)和 fail-closed(判定不确定时默认拒绝)两套控制。它已经不止服务法律,也覆盖了 AI 治理这类更宽泛的合规需求。
一份政策的骨架大致长这样:
# policy.yaml
version: 1
rules:
- id: no-pii-in-prompt
description: 禁止向下游模型发送身份证号等个人敏感信息
type: noul
question: "这段文本是否包含身份证号、银行卡号或未脱敏的手机号?"
on: prompt_text
action: block
fail_closed: true
- id: no-internal-keys
description: 禁止外发内部密钥格式
type: noul
question: "这段文本是否包含疑似内部 API key 或访问令牌?"
on: outbound_text
threshold: 0.8
action: block
fail_closed: true引擎读这份 YAML,为每条规则拼出对应的 Jev 调用,再把概率和阈值一比,决定放行还是拦截:
import yaml
def evaluate(text: str, policy_path: str) -> dict:
rules = yaml.safe_load(open(policy_path))["rules"]
questions = {
r["id"]: {"type": r.get("type", "noul"), "question": r["question"]}
for r in rules
}
d = client.systemone(
state=text,
questions=questions,
instructions="你是访问控制判定器,只依据给定文本判断,命中即判 true。"
)
hits = []
for r in rules:
p = d[r["id"]].probability
thr = r.get("threshold", 0.5)
if p >= thr:
hits.append({"rule": r["id"], "p": round(p, 3), "action": r["action"]})
elif r.get("fail_closed") and thr - p < 0.1:
# 落在阈值附近的灰区,fail-closed 保守拒绝
hits.append({"rule": r["id"], "p": round(p, 3), "action": "block"})
return {"hits": hits}fail-closed 是这里最关键的设计。 合规拦截的默认姿态应该是"拿不准就拦",不是"拿不准就放"。放行的漏判成本由系统承担,拦截的误判成本由用户承担——前者往往大得多。这套判断逻辑写成代码,拦不拦百分之百由阈值和 threshold 字段决定,模型只回答"这段文本是不是包含敏感信息"。
合规问答与生成内容的护栏
合规里还有一类高频需求:让助手基于法规库回答"我们这样做合不合规"。回答本身的写作是生成任务,交给 chat 模型;但回答里的每一句结论都要有条款依据,这正是护栏该干的活。做法是让生成放开写,用 Jev 对成品做后置核验(Verification 形态),把"这段回答是否站得住"拆成几条独立的布尔判定。
answer_review = client.systemone(
state=f"问题:{question}\n法规片段:{retrieved_clauses}\n草稿回答:{draft_answer}",
questions={
"grounded": {"type": "noul",
"question": "回答中的每条结论是否都能在给出的法规片段里找到依据?"},
"overreach": {"type": "noul",
"question": "回答是否给出了超出法规片段范围、带有法律效力的具体建议?"},
"has_citation": {"type": "noul",
"question": "回答是否为关键结论标注了可核验的条款出处?"},
"support": {"type": "score",
"question": "法规片段对这条回答的支撑强度?",
"labels": ["无关", "弱", "中", "强"]}
},
instructions="你是合规文案核验员。逐句核对回答与法规片段,找不到依据即判否。"
)
if (not answer_review["grounded"].value
or answer_review["overreach"].value
or answer_review["support"].score < 2):
withhold_answer("回答缺乏条款依据或越权给出建议")这套核验比在提示词里写"请不要编造条款"可靠得多。提示词只能降低编造的概率,代码里的 if 能百分之百挡住。生成模型负责可读性,决策层负责"这句话有没有出处"——分工清清楚楚。另外,检索层喂进来的法规片段同样是不可信的输入,overreach 这一条判的正是"模型有没有超出片段自己发挥"。
可解释性与审计:概率、理由与校准
合规判断最终要过审计这一关,而审计要的不是"系统判了 A",是"系统以什么置信度、依据什么判了 A"。这就要求把每次判定的完整上下文留下来——state、questions、返回的概率、命中的规则、锁定的模型版本。这是决策层相对生成模型的天然优势:输出本身就是结构化、可序列化、可入库的。
落库时至少应记下这几项:模型版本、state 摘要、questions 全文、每个问题的概率、命中的规则或阈值、时间戳。有了它们,任何一次历史判定事后都能重放和归因;没有它们,"系统判了 A"就是一句无法追问的断言。
校准(calibration)是另一个审计维度。"模型说有 0.9 概率"这句话本身要被验证——它真的在 90% 的情形下成立吗?一个探索性的做法是 LegalForecast-MTD(johnhughes3/LegalForecastBench):它让 Jev 从法官的书面记录里预测联邦 motion-to-dismiss 的裁决结果,再用 claim-defendant micro-Brier 分数评估它给出的校准概率。用 Brier 这类指标而不是纯准确率,正因为这里关心的不是"猜对没有",而是"你说的概率准不准"。
需要说清楚的是,这两个项目 star 数都不高,代表的是方向上的探索性实现,不是能直接照搬的生产方案。但它们把合规判断最核心的两个诉求——确定性的 Policy-as-Code,和经得起 Brier 检验的校准概率——都摆到了台面上。
把三类判断串成一条管线,大概是这样:
flowchart TD
IN["事实 / 合同 / 请求"] --> EXT["Structured Extraction
抽取字段与条款"]
IN --> POL["Policy-as-Code
逐条 Noul 判定"]
EXT --> RISK["Score
风险定级"]
POL --> RISK
RISK --> GATE{"阈值 + fail-closed"}
GATE -->|高置信命中| BLOCK["拦截 / 上报法务"]
GATE -->|灰区| HUMAN["人工复核队列"]
GATE -->|低风险| PASS["放行"]
BLOCK --> AUDIT["审计日志
state + 概率 + 规则 + 版本"]
HUMAN --> AUDIT
PASS --> AUDIT代码始终握着 GATE 这个分叉:命中就拦、灰区转人工、放行——每一条都是明确的 if,模型只给出了概率和理由。
小结
- 合规判断的硬约束是可审计:要概率、要理由、要可复现,这三点决定了它适合决策层而不是生成模型。
- 条款匹配用逐条 Noul,而不是一个大分类器——政策可增删、结果可解释,重叠触发也能如实反映;检索负责捞回条款,判定交给 Jev。
- 合同审查落在
Structured Data Extraction与Verification上,关键是给枚举字段留"未提及"、只自动写入高置信项。 - Policy-as-Code 把规则写成 YAML(如
BhavinM/jev-policy-engine的 audit mode 与 fail-closed),模型只做语义判定,拦不拦由代码和阈值决定。 - 合规问答的护栏用
Verification:把"回答是否有条款依据""是否越权发挥"拆成独立 Noul,让生成的成品也过一遍判定。 - 合规的默认姿态是 fail-closed:不确定就拦;校准要经得起检验,
johnhughes3/LegalForecastBench用 micro-Brier 评估校准概率是一个值得注意的探索方向。
下一章换个战场,看 Jev 在电商市场与广告这两个判断量更大、生意味更浓的场景里怎么落地。