# Legal Hallucination Check

> 法律幻觉校验技能 v1.2

- Skill: `infometa/legal-hallucination-check` (Agent Skill)
- Install (CLI): `npx skillmds@latest add infometa/legal-hallucination-check`
- Raw SKILL.md: https://api.skillmd.com/api/skills/infometa/legal-hallucination-check/raw
- Safety review: pending (external: skill-scanner PASS, skillspector PASS)
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: infometa (https://skillmd.com/u/infometa)
- Updated: 2026-09-21
- Page: https://skillmd.com/skills/infometa/legal-hallucination-check

---


# 法律幻觉校验技能 v1.2

> **版本**：v1.2 | **日期**：2026-06-09
> **定位**：法律AI输出的外部事实核查层 —— 运行时调用 chineselaw.com API 进行法条/案例真实性校验
> **核心原则**：校验不替代判断、分级不放行、修正不掩盖
>
> **v1.2 更新**（2026-06-09）：
> - **新增 Step 1.5 本地预校验**：API调用前增加两道本地防线——① 案例案号格式合法性校验（最高法序号范围/指导案例编号上限）；② 废止法律自动标记（合同法/担保法/侵权责任法等9部民法典前法律→自动升1级）
> - 风险升级规则新增"废止法律引用"触发条件（引用已废止法律→至少P1）
> - 能力边界表案例检测项从"高置信度"下调为"中（需本地兜底）"
> - 评估基线：86/100（TC5暴露案例虚构未检出+已废止法律未标记两个弱项，v1.2针对性修复）
>
> **v1.1 更新**（2026-06-04）：
> - 概括性引用降级规则经实测案件实测验证
> - 6项API能力边界测试完成并回写
> - Windows curl中文编码400错误固化解决

---

## ⚠️ 认识论底线（全程生效，不可覆盖）

```
1. API返回结果仅供参考——不确定的结果必须标注"【API结果存疑，建议人工核实】"
2. 本技能不替代法律判断——只做引用真实性校验，不做法律关系分析
3. 校验失败时回退到 法律认识论底线 规则——承认不确定+给出核验路径
4. 不得基于API的"可能相关"建议自行补充法条或案例
5. 所有校验结果必须可追溯——输出报告含校验时间戳+API响应摘要
```

---

## 三原则

- **校验不替代判断**：API说"不一致"不代表引用一定错误（可能是冷僻条文），需要人工终审
- **分级不放行**：P0（致命幻觉）→ 阻断输出；P1（高疑似）→ 标记+提示；P2（低风险）→ 记录
- **修正不掩盖**：修正后保留原始引用+修正说明，不允许静默替换

---

## 风险分级标准

| 级别 | 判定条件 | 处理动作 | 示例 |
|:---:|---------|---------|------|
| **P0 致命** | ① 法条编号完全不存在（`think_tank_clause_missing: true`） ② 案例案号不存在（`missing-ref`标记） ③ 案号格式异常·本机预校验拦截（序号超范围/指导案例编号超上限） ④ 多条P1集中出现（≥3条） ⑤ 已废止法律引用且内容歪曲（P1+废止标记→升P0） | **阻断输出** → 回退修正 → 重新校验 | 民法典1899条、(2023)最高法民终第9999号、指导案例第888号 |
| **P1 严重** | ① 法条编号存在但内容歪曲（semantic_compare相似度<50） ② 法条编号对应错误（semantic_compare "不一致"） ③ 关键法条（请求权基础）的任何异常 ④ **引用已废止法律**（无论API相似度多高，标记P1·已废止法律） | **标记**→ 阻断依赖该引用的文书节点 → 提示修正路径 | 民法典667条歪曲为"签字即成立"、引用《合同法》第52条 |
| **P2 提示** | ① 法条内容相似度50-85（不完全准确但不影响结论） ② 案例引用的法院/年份正确但案号细节存疑 ③ 非核心法条的轻微偏差 ④ 概括性引用（标准法律写作实践） | **记录**→ 不阻断 → 附校验报告供人工查看 | 引用表述与原文有轻微措辞差异 |

---

### 概括性引用降级规则（⚠️ 2026-05-14 实测案件实测新增·v1.1验证确认）

**问题**：中国法律文书中常见的"概括性引用"写法——如"根据《民法典》第667条之相关规定，被告应承担违约责任"——API因文本与法条原文不匹配而给出低相似度（P1），但这是标准法律写作实践，并非幻觉。

**规则**：在 Step 3 风险定级阶段，增加一道"概括性引用检测"过滤层：

```
if P1判定 and 原始文本匹配以下模式之一:
    ① "根据X法第X条之相关规定" + 法律结论（非法条原文引用）
    ② "依据X法第X条" + 裁判说理/分析性表述
    ③ "参照X法第X条" + 类案推理
    ④ 引用后接"等"字（如"第X条等"）+ 法律后果描述
    → 降级为 P2 提示（标注"概括性引用，非内容歪曲"）
```

**判断标志**：
- 直接引语标记 → 保持原定级（"XX法第X条规定：……"引号内为法条原文 → 若相似度低则是真正歪曲）
- 概括性标记 → 降级（"根据/依据/参照XX法第X条" + 法律结论 → 标准文书写作）

**实测数据支撑**（实测案件 Batch1，8条真实引用·v1.1验证确认）：
- 3条直引法条原文 → ✅ 全部通过（相似度85+）
- 5条概括性引用 → API标为P1，经降级规则正确降为P2
  - 例："根据民法典第667条、第675条、第676条之相关规定，被告应承担还本付息的违约责任"（相似度20 → 降为P2·概括性引用）
  - 例："民事诉讼法第35条规定当事人可以书面协议选择管辖法院"（相似度40 → 降为P2·概括性引用）

**降级标记格式**：`P2·概括性引用（API原始相似度:N）`，报告中注明"此为文书常见写作方式，非内容歪曲"。

---

## 执行流程（5步·v1.2）

### Step 1: 引用提取

从输入文本中自动提取所有法条引用和案例引用。

**法条引用模式**（正则提取）：
- `《[法]》第[X]条` → 标准格式
- `[法]第[X]条第[X]款` → 含款/项
- `[法规名]第[X]条` → 行政法规/司法解释

**案例引用模式**：
- `([年份])[法院]第[X]号` → 标准案号格式
- `指导案例第[X]号` → 指导性案例
- `[年份][省份简称][数字]民/刑/行[终/初/再][数字]号`

### Step 1.5: 本地预校验（v1.2新增·API调用前防线）

> 🔴 **CHECKPOINT — 本步骤在API调用前执行，不依赖外部服务，零延迟。** 用于弥补API在案例检测和时效性判断上的覆盖盲区。

#### 1.5A：案例案号格式合法性校验

在调用API之前，对所有提取到的案例引用进行本地格式校验：

```
【案例案号格式校验规则】

最高法案号：
  格式：(YYYY)最高法民终第XXXX号
  校验：序号是否 > 9999 → 标记为"⚠️ 格式可疑·序号超出合理范围"
  校验：年份是否 > 当前年份 → 标记为"⚠️ 格式可疑·年份为未来"

指导性案例：
  格式：指导案例第XX号
  校验：编号是否 > 300 → 标记为"⚠️ 编号疑似虚构·截至2025年最高法发布约230+号"
  （阈值留10%余量，实际以最高法最新发布为准）

普通案号：
  格式：(YYYY)XX法/民/刑/行XX字第XX号
  校验：基础格式匹配 → 通过
  校验：年份格式异常 → 标记"⚠️ 案号格式异常"
```

**处理规则**：
- 通过格式校验 → 正常进入Step 2 API调用
- 未通过格式校验 → **直接标记P0**（无需等待API确认），阻断输出：
  - 标记为 `P0·案号格式异常（本地预校验拦截）`
  - 原因说明：案号超出已知合理范围，疑似虚构
  - 示例：`(2023)最高法民终第9999号` → P0（序号远超合理范围）；`指导案例第888号` → P0（编号超已知上限）

#### 1.5B：废止法律自动标记

在调用API之前，对所有提取到的法条引用进行废止法律检测：

```
【已废止法律列表】（民法典2021年1月1日施行后废止）

| 旧法 | 替代法 | 废止日期 |
|------|--------|---------|
| 《中华人民共和国合同法》 | 《中华人民共和国民法典》合同编 | 2021-01-01 |
| 《中华人民共和国担保法》 | 《中华人民共和国民法典》+《担保制度司法解释》 | 2021-01-01 |
| 《中华人民共和国侵权责任法》 | 《中华人民共和国民法典》侵权责任编 | 2021-01-01 |
| 《中华人民共和国物权法》 | 《中华人民共和国民法典》物权编 | 2021-01-01 |
| 《中华人民共和国民法通则》 | 《中华人民共和国民法典》 | 2021-01-01 |
| 《中华人民共和国婚姻法》 | 《中华人民共和国民法典》婚姻家庭编 | 2021-01-01 |
| 《中华人民共和国继承法》 | 《中华人民共和国民法典》继承编 | 2021-01-01 |
| 《中华人民共和国收养法》 | 《中华人民共和国民法典》 | 2021-01-01 |
| 《中华人民共和国民法总则》 | 《中华人民共和国民法典》 | 2021-01-01 |
```

**处理规则**：
- 如果引用中出现了以上任何一部已废止法律 → **自动升1级**：
  - 原本应P2 → 升为P1·已废止法律
  - 原本应P1 → 升为P0·已废止法律
  - 原本应通过 → 标记为 `P1·已废止法律（API相似度:N）`，无论API返回的相似度多高
- 附加说明：`"《XX法》已于2021年1月1日被《中华人民共和国民法典》取代。引用已废止法律，即使内容与原文一致。建议替换为《民法典》对应条款。"`

> ⚠️ **注意**：旧法在新法施行前订立的合同仍可能适用（视时间效力规则而定）。如确认引用旧法是基于"法不溯及既往"原则的合理选择，标注"【旧法适用·合同订立于民法典施行前】"后可不升级。

### Step 2: API调用

> 🔴 **CHECKPOINT — API调用前脱敏检查**：发送前必须确认：①已替换当事人姓名→"甲方/乙方" ②身份证号→"[身份证号]" ③具体金额→"[金额]" ④未包含具体案件事实。检查通过后方可发送API请求。⚠️ Windows环境必须使用Python urllib方式发送中文内容（避免curl 400编码错误）。

调用 `https://open.chineselaw.com/open/hall_detect` 进行批量校验。

**调用规范**：
- 长文本（>2000字）分段发送，每段≤1500字
- 段间间隔≥1秒（避免触发速率限制）
- 超时设置：30秒/段
- 失败重试：最多1次

### Step 3: 风险定级

> 🔴 **CHECKPOINT — 分级前概括性引用过滤**：在风险定级前，必须先执行"概括性引用降级规则"过滤层。凡匹配"根据/依据/参照XX法第X条"+"法律结论"模式的P1候选，先降为P2·概括性引用后再执行后续分级。跳过此过滤层将导致大量假阳性P1。

将API响应映射到风险级别：

```
for each regulation in response.regulations:
    if regulation.think_tank_clause_missing == true:
        → P0 致命（法条编号不存在）
    elif regulation.semantic_compare.similarity_score < 50:
        → P1 严重（法条内容歪曲/错配）
    elif regulation.semantic_compare.similarity_score < 85:
        → P2 提示（轻微偏差）
    else:
        → ✅ 通过

for each case in response.cases:
    if case.status == "not_found":
        → P0 致命（案例不存在）
    else:
        → ✅ 通过
```

**风险升级规则**：
- 同一文本中≥3条 P1 → 升为 P0
- 同一文本中≥5条 P2 → 升为 P1
- 请求权基础法条（《民法典》合同编/侵权编核心条文）任何异常 → 至少 P1
- **引用已废止法律** → 自动升1级（P2→P1，P1→P0），无论API返回的相似度多高
- **Step 1.5 本机预校验拦截** → 直接P0，不进入API校验流程

### Step 4: 校验报告输出

输出结构化校验报告（同上版本格式，略）。

---

## 失败模式与异常处理

| 触发条件 | 一线修复 | 仍失败兜底 |
|---------|---------|-----------|
| API不可用（超时/503/连接拒绝） | 重试1次（间隔3秒）；若仍失败，降级为人工校验模式：输出"⚠️ API不可用，以下X条引用未经过机器校验" | 跳过API校验，在输出报告顶端加红色标注"【未校验·人工终审必需】"，建议通过[本地法条库]和[知识库]手动核实 |
| 司法解释引用校验结果始终存疑（API对司法解释覆盖率有限） | 在报告中额外标注"司法解释引用·API覆盖有限·建议人工双核"，P1降为P2·司法解释 | 所有司法解释引用统一附加"【建议通过[本地法条库]或[知识库]人工核实】"标记 |
| P0/P1项的"建议修正"用户无法执行 | 标记"修正路径"而非给出猜测修正 | 禁止AI自行补充替代法条。回退给用户 |
| Windows curl中文编码400错误 | 使用Python urllib方式发送（固化方案） | — |

## 反例与黑名单

| 🚫 | 禁止 | 理由 | 正确做法 |
|:---:|------|------|---------|
| 1 | 在API返回"not_found"后自行补一个"可能是XX号案例" | 校验工具不能自行编造答案 | 标注"【未找到对应案例，请核实后补充】"并阻断P0 |
| 2 | 发送包含当事人敏感信息的文本给外部API | 隐私泄露风险 | 发送前强制脱敏 |
| 3 | 跳过概括性引用降级规则，直接把所有低相似度标为P1 | 概括性引用是标准法律写作实践 | 先执行降级过滤层，再执行定级 |
| 4 | API可用时不做校验直接输出文书 | "感觉没问题"≠"验证过没问题" | 凡有法条/案例引用的文书输出前必须通过关闸 |
| 5 | 校验报告只报P0不报P1/P2 | 掩盖风险积累 | 全量报告 |

---

## 技术实现（Python urllib·Windows兼容·固化方案）

```python
import urllib.request
import json
import time

def check_hallucination(text: str) -> dict:
    url = "https://open.chineselaw.com/open/hall_detect"
    headers = {
        "X-API-Key": "YOUR_API_KEY",  # 在 chineselaw.com 申请
        "Content-Type": "application/json; charset=utf-8",
        "Accept": "application/json"
    }
    data = json.dumps({"text": text}, ensure_ascii=False).encode("utf-8")
    req = urllib.request.Request(url, data=data, headers=headers, method="POST")
    try:
        with urllib.request.urlopen(req, timeout=30) as resp:
            return json.loads(resp.read().decode("utf-8"))
    except urllib.error.URLError as e:
        return {"error": str(e), "status": "api_unavailable"}
    except Exception as e:
        return {"error": str(e), "status": "unknown_error"}

def batch_check(texts: list[str], delay: float = 1.0) -> list[dict]:
    results = []
    for i, text in enumerate(texts):
        result = check_hallucination(text)
        result["batch_index"] = i
        results.append(result)
        if i < len(texts) - 1:
            time.sleep(delay)
    return results

# ========== v1.2 新增：本地预校验 ==========

import re

REPEALED_LAWS = {
    "合同法": ("《中华人民共和国民法典》合同编", "2021-01-01"),
    "中华人民共和国合同法": ("《中华人民共和国民法典》合同编", "2021-01-01"),
    "担保法": ("《中华人民共和国民法典》+《担保制度司法解释》", "2021-01-01"),
    "中华人民共和国担保法": ("《中华人民共和国民法典》+《担保制度司法解释》", "2021-01-01"),
    "侵权责任法": ("《中华人民共和国民法典》侵权责任编", "2021-01-01"),
    "中华人民共和国侵权责任法": ("《中华人民共和国民法典》侵权责任编", "2021-01-01"),
    "物权法": ("《中华人民共和国民法典》物权编", "2021-01-01"),
    "中华人民共和国物权法": ("《中华人民共和国民法典》物权编", "2021-01-01"),
    "民法通则": ("《中华人民共和国民法典》", "2021-01-01"),
    "中华人民共和国民法通则": ("《中华人民共和国民法典》", "2021-01-01"),
    "婚姻法": ("《中华人民共和国民法典》婚姻家庭编", "2021-01-01"),
    "中华人民共和国婚姻法": ("《中华人民共和国民法典》婚姻家庭编", "2021-01-01"),
    "继承法": ("《中华人民共和国民法典》继承编", "2021-01-01"),
    "中华人民共和国继承法": ("《中华人民共和国民法典》继承编", "2021-01-01"),
    "收养法": ("《中华人民共和国民法典》", "2021-01-01"),
    "中华人民共和国收养法": ("《中华人民共和国民法典》", "2021-01-01"),
    "民法总则": ("《中华人民共和国民法典》", "2021-01-01"),
    "中华人民共和国民法总则": ("《中华人民共和国民法典》", "2021-01-01"),
}

def precheck_case_number(case_ref: str) -> dict:
    """案例案号格式本地校验"""
    result = {"valid": True, "issue": None, "grade": None}
    
    # 指导案例编号校验
    m = re.search(r'指导案例第(\d+)号', case_ref)
    if m:
        num = int(m.group(1))
        if num > 300:
            result["valid"] = False
            result["issue"] = f"指导案例编号{num}超出已知上限（~230+），疑似虚构"
            result["grade"] = "P0·本机预校验"
            return result
    
    # 最高法案号校验
    m = re.search(r'[（(](\d{4})[）)]\s*最高法[民刑行].{0,3}第(\d+)号', case_ref)
    if not m:
        m = re.search(r'最高法[民刑行].{0,3}第(\d+)号', case_ref)
    if m:
        year = int(m.group(1))
        seq = int(m.group(2))
        if year > 2026:
            result["valid"] = False
            result["issue"] = f"案号年份{year}为未来年份，格式异常"
            result["grade"] = "P0·本机预校验"
        elif seq >= 9000:
            result["valid"] = False
            result["issue"] = f"案号序号{seq}异常偏高（最高法年收案量通常<5000），疑似虚构"
            result["grade"] = "P0·本机预校验"
    
    return result

def precheck_repealed_law(text: str) -> list[dict]:
    """废止法律自动标记"""
    findings = []
    for law_name, (replacement, repeal_date) in REPEALED_LAWS.items():
        if law_name in text:
            findings.append({
                "law": law_name,
                "replacement": replacement,
                "repeal_date": repeal_date,
                "action": "自动升1级",
                "note": f"《{law_name}》已于{repeal_date}被{replacement}取代。建议替换。"
            })
    return findings
```

