第二季第 1 篇:合同解析插件——把 PDF 变成结构化条款 JSON
📌 本文是《项目实战:从零搭建智能合同审查平台》(第二季)的第 1 篇🎯 读完本文你将:① 理解为什么不能"整篇丢给 LLM" ② 掌握正则 + 规则的条款切分引擎 ③ 拿到可直接运行的 Python 解析器 ④ 对 5 份示例合同全部跑通⏱️ 预计阅读时间:15 分钟 | 动手实践:20 分钟💻 前置要求:完成第二季第 0 篇(环境搭建 + 示例数据)🔗 原理对照:万悟平台的「插件开发」能力讲的是"把外部 API 注册为 Agent 可调用的工具"。本篇做的是同一件事——把"合同解析"封装为一个确定性工具,输入 PDF 文本,输出结构化 JSON,供下游工作流调用。没读过万悟源码不影响本篇阅读。
一、本篇要解决什么问题?
1.1 场景
法务小张收到一份 47 页的施工合同 PDF。老板说:"半小时内告诉我里面有没有坑。"
小张打开 PDF,Ctrl+F 搜"违约"——出来 23 处。搜"赔偿"——17 处。搜"解除"——11 处。他需要逐条看、逐条判断、逐条记录。
问题不是"找不到",而是"没有结构"。
一份合同 PDF 在计算机眼里就是一坨连续文本。没有"这是第几条"、"这条属于什么类型"、"这条有几个子项"的概念。你没法对"一坨文本"做逐条审查、逐条对比、逐条评分。
1.2 本篇目标
输入:合同纯文本(PDF 提取后)输出:结构化 JSON├── meta(contract_id、contract_type、title、parties、sign_date)└── clauses[](每个条款)├── number: ”第七条”├── title: ”违约责任”├── clause_type: ”breach”└── content: 原文
1.3 为什么不用 LLM?

合同条款的编号格式是高度规律的:"第X条"、"X.Y"、"(一)"。用正则切分,准确率 > 99%,速度快 200 倍,零成本。
LLM 的价值在后面的"审查判断"环节(第 2 篇),不在"结构解析"环节。用对工具做对事,这是工程思维。解析是确定性工序,归入「规则先行、模型收口」的 80% 规则侧——正是实战中「解析不走 LLM」的普遍做法,让模型只处理真正需要语义判断的审查环节。
二、核心概念
2.1 条款切分的三层结构

中国合同的条款编号有严格的层级:
第一层:大条款 ”第一条 合同期限” ”第七条 违约责任”第二层:子条款 ”1.1” ”3.2” ”10.3”第三层:子项 ”(一)” ”(二)” ”(三)”
我们的解析器按第一层("第X条")切分;子条款(X.Y)与子项((一))作为原文保留在 content 中,供下游按需再解析。
2.2 条款类型分类
切完之后,每个条款需要一个类型标签。这不是用 LLM 判断的,而是用关键词匹配:
”违约责任” → 包含”违约” → 类型 = breach”付款方式” → 包含”付款” → 类型 = payment”合同期限” → 包含”期限” → 类型 = term
为什么需要类型标签?因为后续审查时,不同类型的条款用不同的规则集:
breach类 → 检查违约金比例是否合理 payment类 → 检查预付款比例是否超标 term类 → 检查期限是否合法
2.3 整体流程
┌─────────────────────────────────────────────────┐│ 合同文本(PDF/Word 提取后的纯文本) │└──────────────────────┬──────────────────────────┘▼┌──────────────────────────────────────────────────┐│ Step 1: detect_contract_type() ││ 关键词统计 → 合同类型(labor/procurement/...) │└──────────────────────┬───────────────────────────┘▼┌──────────────────────────────────────────────────┐│ Step 2: CLAUSE_PATTERN 切分(正则 ”第X条”) ││ 每段提取 标题 + 内容 │└──────────────────────┬───────────────────────────┘▼┌──────────────────────────────────────────────────┐│ Step 3: _classify_clause() ││ 关键词匹配 → 类型标签(breach/payment/term...) │└──────────────────────┬───────────────────────────┘▼┌──────────────────────────────────────────────────┐│ 输出:ParsedContract(结构化 JSON) ││ 供下游工作流 / 审查引擎 / RAG 检索消费 │└──────────────────────────────────────────────────┘
三、核心代码拆解(完整可运行实现见《附录-合同审查平台完整代码库精讲》(本季最后附录))
# models/parser.py —— 真实库(全量 63 passed,parser 模块为其中一部分)”””合同解析引擎:文本 / PDF / Word → 结构化条款(零 LLM 依赖)。”””import refrom dataclasses import dataclass, fieldfrom typing import List, Optional# 第X条 切分正则(真实库 CLAUSE_PATTERN)CLAUSE_PATTERN = re.compile(r”(第[一二三四五六七八九十百]+条)\s*(.+?)(?=第[一二三四五六七八九十百]+条|$)”,re.DOTALL,)TYPE_KEYWORDS = {# 合同类型关键词(detect_contract_type 用)”labor”: [”劳动合同”, ”用人单位”, ”劳动者”, ”竞业限制”, ”工资”],”procurement”: [”采购”, ”供货”, ”验收”, ”预付款”, ”质保”],”construction”: [”施工”, ”工程”, ”竣工”, ”工期”, ”监理”],”nda”: [”保密协议”, ”商业秘密”, ”保密期限”],”lease”: [”租赁”, ”租金”, ”承租”, ”出租”],}@dataclassclass ContractMeta:contract_id: str = ””contract_type: str = ”unknown”title: str = ””parties: List[str] = field(default_factory=list)sign_date: str = ””@dataclassclass Clause:# 真实库字段,仅 4 个number: str; title: str; clause_type: str; content: str@dataclassclass ParsedContract:meta: ContractMeta = field(default_factory=ContractMeta)clauses: List[Clause] = field(default_factory=list)raw_text: str = ””def detect_contract_type(text: str) -> str: ...def _classify_clause(title: str, content: str) -> str: ...def parse_contract(text: str, contract_id: str = ””) -> ParsedContract: ...def extract_text(filename: str, raw_bytes: bytes) -> str: ...# .txt/.pdf/.docx# 完整源码(含注释)见《附录-合同审查平台完整代码库精讲》(本季最后附录)
四、运行结果
$ pytest tests/test_parser.py -q......... [100%]9 passed in 0.8s
5 份示例合同均被确定性解析(以 pytest tests/test_parser.py 的 9 项断言为准,如 labor 合同切出约 10~12 条条款);解析为纯确定性工序,毫秒级、零 LLM 成本。整库回归 pytest 统一63 passed(本篇聚焦解析模块的 9 项断言)。
五、踩坑记录
[一二三四五六七八九十] 没覆盖"十"的组合(十一、十二) | [一二三四五六七八九十百]+,用 + 匹配多字组合 | |||
[::]\s*(.+?)[\n\r],允许冒号后有空格 |
六、与万悟平台能力对照
七、小结 & 下一篇预告
⏱️ 30 秒速览
这篇你只需要记住 3 件事:
合同解析不用 LLM——正则 + 规则引擎:50ms/份、零成本、100% 确定性 按"第X条"切分为条款(子条款/子项保留在 content 中),类型分类器 9 种 输出结构化 JSON,是后续审查/对比/审批全部环节的数据基础
想深挖?
完整可运行库(parser + 审查/批量/多租户等全部模块):见《附录-合同审查平台完整代码库精讲》(本季最后附录);切分规则:§2 核心概念
本篇要点
✅ 合同解析不用 LLM——正则 + 规则引擎,50ms/份,零成本,100% 确定性 ✅ 按"第X条"切分为条款(子条款/子项保留在 content 中) ✅ 类型分类器:9 种条款类型,关键词匹配,为后续审查规则路由做准备 ✅ 5 份示例合同均被正确切分(以 pytest 断言为准) ✅ 输出 JSON 是后续所有环节(审查/对比/审批/监控)的数据基础
下一篇预告
第二季第 2 篇:审查模型路由拿到切分好的结构化条款后,怎么审?劳动合同的"竞业限制"和施工合同的"工期违约金"用的是完全不同的法律规则。本篇实现:5 类合同 → 按类型路由到对应规则集 + 可选的 LLM 第二意见(可关闭)。
🔗 原理对照:万悟平台的「模型接入」能力
附录说明
本篇正文已讲清设计思路与关键片段;完整可运行源码库(解析 + 审查 / 批量 / 多租户 / 基线 / 可观测等全部模块,已测 63 passed)在《附录-合同审查平台完整代码库精讲》(本季最后附录)中逐一对应;源码可回复公众号关键词「第二季项目代码」,自动回复会给到付费文章《第二季·智能合同审查平台完整源码》(¥29)→ 付后解锁,文内附微云下载直链 + 二维码。
📦 获取方式:回复公众号关键词「第二季项目代码」→ 自动回复给付费文章《第二季·智能合同审查平台完整源码》(¥29)→ 付后解锁,文内附微云下载直链 + 二维码。
⚠️ 教学代码声明:本季配套源码
contract-review-platform为示例教学代码,仅用于学习合同审查系统的设计思路与工程实现,不构成任何法律意见,也不保证适用于真实业务。作者不承担因使用、修改或部署本代码产生的任何问题解答义务或法律/商业后果;你可以在本示例代码基础上自行修改以满足你的需求。用于真实合同审查前,请务必由执业法律人士审核。
📱 关注公众号,追更不迷路
本系列文章首发于微信公众号「农夫三拳有点癫」,每周更新源码拆解与架构实战。
在微信扫描下方二维码即可关注:
夜雨聆风