LocalLegal Suite · 本地合同审阅套件
面向企业法务 / 采购 / HR 的本地合同审阅 Agent Skills。8 个可互相编排的 Skill 全流程运行在 Intel Core Ultra AI PC 上,敏感文本不出机;只在需要通用法律知识时 通过
legal.rag发起去标识化云端补充。
项目简介
企业每天都在签合同。真正的痛点不是模板套用,而是审阅:法务人手不够,业务 急着签,一份 30 页的采购合同压下来,三天见不到反馈。而云端 AI 服务面前横着一堵 墙——合同本身就是商业机密,甲方名字都不允许上云。
LocalLegal Suite 把八个专家动作拆成八个 Skill,装进 Core Ultra 的本地推理里,让 Agent(QwenWork / WorkBuddy / TraeWork / 豆包办公)一句话完成 读进来 → 抽条款 → 扫风险 → 比模板 → 引法条 → 给替换条款 → 出报告。
不止于"标红"。redline.suggest 为每一条风险产出可以直接粘贴进反稿的替换条款
并附法条依据——法务要的不是"违约金无上限有风险",是那句能写进合同的话。
没有模型也能跑。 确定性规则层(33 条风险规则 + 20 条修订模板 + 正则结构切分) 独立工作,风险矩阵、模板偏离、修订建议一条都不少。这也是 125 个测试能在没有 AI PC 的机器上全绿的原因。
核心功能
| Skill | 职责 | 运行位置 | LLM |
| --- | --- | --- | --- |
| doc.ingest | PDF / DOCX / TXT / 扫描件 → 规范化文本 | 本地 CPU(OCR 走 GPU/NPU) | – |
| clause.extract | 抽当事方 / 金额 / 期限 / 条款分类 | 本地 iGPU | Qwen INT4 + 正则兜底 |
| risk.scan | 规则库 + LLM 双通道,出 0-100 风险分 | 本地 iGPU | 规则为主,LLM 补充 |
| template.diff | 与企业标准模板逐条比对,判更严 / 更松 | 本地 iGPU | Qwen INT4 |
| pii.redact | 身份证 / 手机 / 银行卡 / 机构名 / 姓名脱敏 | 本地 CPU | 纯正则 |
| legal.rag | 法条检索,本地 corpus ↔ 可选云端 | 本地 CPU / 云 | – |
| redline.suggest | 每条风险产出可粘贴的替换条款 | 本地 iGPU | 模板优先,LLM 兜底 |
| report.gen | 编排以上全部,出 Markdown / HTML / DOCX | 本地 CPU | – |
调用关系
八个 Skill 通过共享 Registry 相互调用,report.gen 是唯一入口:
用户 / Agent ──"帮我审这份采购合同,跟标准模板对比一下"
│
▼
┌─────────────┐
│ report.gen │ 编排层:一句指令 → 七次 skill 调用
└──┬──────────┘
│
├──▶ doc.ingest ─────────── PDF/DOCX/OCR → 文本
│ ▲
├──▶ clause.extract ────────┘(缺文本时自己回调 doc.ingest)
│ ▲
├──▶ risk.scan ─────────────┤(缺条款时回调 clause.extract)
├──▶ template.diff ─────────┘
│
├──▶ redline.suggest ──▶ legal.rag ──▶ pii.redact
│ 给出替换条款 法条依据 出站前强制脱敏
│ │ (最后一道闸口)
├──▶ legal.rag ──────────────┘
│
└──▶ pii.redact ──── 报告内的 PII 清单(只报计数,不回填原值)
不是硬编码流水线:risk.scan 只给文本也能跑(自己回调 clause.extract),
clause.extract 只给路径也能跑(自己回调 doc.ingest)。Agent 侧可以从任意
一个 Skill 切入。
五类合同实测
python stress.py 在 examples/ 五份样本上的真实输出(stub 模式,纯规则层):
| 样本 | 风险分 | 高危条数 | 结论 |
| --- | --- | --- | --- |
| sample_procurement | 92 | 5 | reject |
| sample_nda | 69 | 2 | negotiate |
| sample_labor | 76 | 3 | reject |
| sample_lease | 92 | 5 | reject |
| sample_dpa | 81 | 4 | reject |
风险分采用软饱和 100×(1-e^(-total/60)),永不达到 100——审阅工具不该宣称自己
什么都发现了。横向比较看分数,纵向比较看高危条数。
安装与启动
1. 安装依赖
python -m pip install -r requirements.txt
# 硬件加速可选(Core Ultra 上强烈推荐)
python -m pip install openvino openvino-genai openvino-tokenizers optimum-intel nncf
也可以一步到位,脚本会装依赖、跑测试并打印设备摘要:
python install.py # 依赖 + 测试(不含模型)
python install.py --with-openvino # 追加 OpenVINO 栈
python install.py --with-model # 追加模型导出与量化
python install.py --all # 全部
python install.py --all --model qwen3-1.7b # 指定档位
2. 导出模型(一次性,可跳过)
先看有哪些档位,按机器算力挑:
python -m models.quantize --list
| 档位 | 量化 | 约占用 | 活跃参数 | 适用 |
| --- | --- | --- | --- | --- |
| qwen3-30b-a3b | INT4 | ~16GB | 3B | MoE,内存 ≥32GB 时首选:速度接近 3B,质量接近大模型 |
| qwen3-8b | INT4 | ~4.8GB | 8B | MoE 装不下时的主力档 |
| qwen2.5-7b | INT4 | ~4.2GB | 7B | 无 thinking 模式,输出更省 token |
| qwen3-4b | INT4 | ~2.5GB | 4B | 默认档,算力紧张时的推荐选择 |
| qwen2.5-3b | INT4 | ~1.9GB | 3B | 低配机器兜底 |
| qwen3-1.7b | INT8 | ~1.8GB | 1.7B | 最小可用档(再压会明显掉点) |
python -m models.quantize --profile qwen3-4b
换档位只改一个环境变量,不用改代码:
export LOCALLEGAL_MODEL=qwen3-30b-a3b
python locallegal.py models # 看当前档位和导出状态
用未收录的模型也不需要改代码:
LOCALLEGAL_MODEL=my-model \
LOCALLEGAL_MODEL_ID=org/my-model \
LOCALLEGAL_LLM_DIR=~/models/my-model-int4-ov \
python locallegal.py review --contract contract.pdf
档位表里的占用是按参数量估算的规划值,导出后
models.quantize会打印实际大小, 引用数字时以实际值为准。
3. 验证运行状态
python locallegal.py devices
{
"runtime": {"requested": "AUTO", "resolved": "GPU", "available": ["CPU", "GPU", "NPU"]},
"llm": {"profile": "qwen3-4b", "bits": 4, "device": "GPU", "stub": false}
}
两半都要看:runtime.resolved 证明算在 iGPU/NPU 上,llm.stub 证明真有模型在答
而不是规则兜底。stub 模式下 CLI 会额外打印一行提醒和导出命令。
使用示例
一键审阅(最常用)
python locallegal.py review \
--contract examples/sample_procurement.txt \
--template examples/template_procurement.txt \
--out out/report.md
风险分: 92/100
报告已写入: out/report.md
输出目录不存在时会自动创建。--format 支持 markdown / html / docx。
其他 CLI 入口
python locallegal.py skills # 列出 8 个 Skill 及其参数 schema
python locallegal.py models # 档位表 + 哪些已导出
python locallegal.py devices # 运行设备与当前模型
python bench.py --repeat 3 # 各阶段延迟与 LLM 吞吐实测
在 Python 里单独调用某个 Skill
from skills.registry import load_all, call
load_all()
text = call("doc.ingest", path="contract.pdf")["text"]
clauses = call("clause.extract", text=text)["clauses"]
risks = call("risk.scan", clauses=clauses)
redlines = call("redline.suggest", risks=risks["risks"], cite=False)["redlines"]
for r in redlines:
print(r["clause"], "→", r["after"])
挂载到 Agent 平台
每个 Skill 一份 manifests/*.yaml,兼容 QwenWork / WorkBuddy / TraeWork / 豆包办公。
描述遵循 OpenAI function-calling schema,Agent 侧可以直接把 function 字段喂给
tool-use 通道。集合 manifest 见 suite.yaml,平台接入细节见
docs/agent_platforms/。
目录结构
locallegal-suite/
├── SKILL.md # 本文件:项目说明与使用指南
├── locallegal.py # CLI 入口(review / skills / models / devices)
├── install.py # 一键安装 + 冒烟测试
├── bench.py # 延迟与吞吐实测
├── gen_cases.py # 生成压测语料 → cases/
├── stress.py # 全链路压测 + 假阴性检测
├── suite.yaml # 套件级 manifest
├── manifests/ # 每个 Skill 的 skill.yaml(8 份)
├── skills/ # 8 个 Skill 实现
│ ├── registry.py # 相互调用的关键点
│ ├── doc_ingest.py
│ ├── clause_extract.py
│ ├── risk_scan.py
│ ├── template_diff.py
│ ├── pii_redact.py # 出站脱敏闸口
│ ├── legal_rag.py
│ ├── redline.py # 可粘贴的替换条款
│ └── report_gen.py
├── models/
│ ├── ov_runtime.py # OpenVINO 设备选择 / 降级
│ ├── profiles.py # 模型档位表(换模型只改这里或环境变量)
│ ├── llm.py # 本地推理 + JSON 容错 + stub 回退
│ └── quantize.py # NNCF 权重量化脚本
├── rules/ # 全部可由法务直接编辑,无需改代码
│ ├── risk_rules.yaml # 33 条风险规则(商事 / 劳动 / 租赁 / 数据)
│ ├── redline_templates.yaml # 20 条修订模板
│ └── legal_corpus.json # 离线 mini-corpus(31 条)
├── examples/ # 采购 / NDA / 劳动 / 租赁 / DPA 五类样本 + 标准模板
├── cases/ # 13 份压测语料(由 gen_cases.py 生成)
├── tests/ # 125 个 pytest 用例
├── docs/ # ModelScope 文章、混合智能说明、平台接入
├── demo/ # 路演 PPT 大纲、生成脚本、录屏脚本
└── out/ # 报告输出目录(运行时自动创建)
混合智能与隐私
-
默认全本地:所有涉及原始合同文本的 Skill(
doc.ingest、clause.extract、risk.scan、template.diff、pii.redact、redline.suggest、report.gen) 在 Core Ultra 上完成,无网络出口。 -
可选云端:
legal.rag支持通过LOCALLEGAL_RAG_ENDPOINT指向企业内网 / 云端 法条服务。出站前强制经过pii.redact——甲方名、姓名、手机号、身份证、 地址一律脱敏,云端只能看到"违约金 无上限"这样的抽象法律短语。 -
弱网降级:endpoint 连不上时
legal.rag回落内置 mini-corpus,报告照常产出 (tests/test_hybrid_degradation.py用一个必然连接失败的地址实测这条路径)。 -
可审计:设
LOCALLEGAL_RAG_AUDIT=1后,每一次检索决策(含未出站的) 都写入本地 JSONL,合规团队可核查究竟什么离开过这台机器:LOCALLEGAL_RAG_AUDIT=1 python locallegal.py review --contract contract.pdf cat ~/.cache/locallegal/rag_audit.jsonl # {"ts":"...","egress":false,"query":"违约 无上限","backend":"local-offline","results":3}也可以直接传路径:
LOCALLEGAL_RAG_AUDIT=/var/log/locallegal/audit.jsonl。
运行设备
| 阶段 | 默认设备 | 备注 | | --- | --- | --- | | PDF / DOCX 文本层抽取 | CPU | 纯 I/O,OpenVINO 不介入 | | 扫描件 OCR | GPU / NPU | PaddleOCR + OpenVINO 后端 | | 条款抽取 · 风险 · 模板 diff · 修订 | GPU(iGPU 优先) | 当前档位的本地模型,OpenVINO GenAI | | 法条 RAG | CPU 或云端 | 敏感度分级后决定 |
测试
python -m pytest tests/ -q
# 125 passed
| 测试文件 | 证明什么 |
| --- | --- |
| test_registry.py | Skill 注册与互相调用 |
| test_doc_ingest.py | 多格式摄入、编码回退 |
| test_clause_and_risk.py | 条款分类与规则命中 |
| test_multi_domain.py | 五域各自的毒条款都被抓到 |
| test_pii_redact.py | PII 脱敏与 keep 名单 |
| test_template_diff.py | 模板偏离的确定性兜底(无模型也能出结果) |
| test_hybrid_degradation.py | 弱网降级、出站强制脱敏、审计留痕 |
| test_e2e_report.py | 端到端报告结构 |
| test_json_robustness.py | 小模型 JSON 输出容错 |
| test_small_model_efficiency.py | 小模型下的 token 效率 |
| test_regressions.py | 十个真实缺陷的回归锁(见下) |
压测:
python gen_cases.py && python stress.py
test_regressions.py 里每条断言都对应一个先用真实输入复现、再修掉的缺陷:
| 缺陷 | 现象 |
| --- | --- |
| 身份证号紧贴中文标签不脱敏 | 身份证320102199003071234 原样出站(\b 在汉字后失效) |
| 末条无尾随换行被静默漏抽 | 报告里少一个条款且不报错 |
| 风险分硬封顶 | 五份样本三份 100/100,失去区分度 |
| stub 模式标注成"本地LLM" | 没有模型却说机器起草了条款 |
| 编号格式强耦合"第X条" | 用 1. 编号的毒合同得 0 分、结论"可签署"(改成"第一条"即 87 分 reject) |
| 无法分类的条款被丢弃 | "最终解释权"这类没有 clause_type 的条款整条消失 |
| 真实霸王条款大面积漏检 | 概不退款 / 人身损害免责 / 单方变更全都没规则 |
| 姓名角色标签不全 | 乙方(员工):李晓明 漏脱敏 |
| 中文数字缺"零" | 第一百零一条 整条丢失 |
| 单行文本切不开 | PDF 文本层挤成一行时所有条款并成一块 |
测试默认在 stub-LLM 模式下跑(无模型也可 CI);真实模型到位后同一套测试仍成立 (断言的是结构契约,不是措辞)。
已知限制
以下五条是设计取舍或需要较大改动才能解决,使用前请知悉:
- 不支持外文合同。规则库、分类器、法条语料全是中文。目前会标"结构未识别, 需人工审阅"兜住,但没有真正支持。
_classify关键词计数优先于类型优先级。"第三条 租金与押金"因"租金"出现 多次被归为租金,押金专属规则挂不上——风险仍会抓到,只是归类不够准。- 法条检索是字面关键词重叠,会错配("付款"风险关联到《个人信息保护法》 第 51 条)。引用报告里"参考法律依据"前自己核一遍。
- 全角数字漏检。
100%这类全角百分比匹配不到预付比例规则。 - 重复条款去重后不提示。100 条重复条款与 1 条同分(去重本身是对的,但文档 异常没有告警)。
报告若出现"⚠️ 结构未识别,需人工审阅",表示没有审到,不等于审过了没问题, 请勿依据该结论签署。
许可
Apache-2.0
微信扫一扫