企业合同智能审查系统——从立项到上线的完整复盘
企业合同智能审查系统——从立项到上线的完整复盘
适读人群:做企业AI落地的工程师/产品经理 | 阅读时长:约18分钟 | 核心价值:合同审查AI系统的完整技术方案+踩坑记录,有架构有数据
去年年初,我接了一个项目:帮一家有200多家连锁门店的零售集团,做合同智能审查系统。
这家公司每年要审大量合同——供应商协议、租赁合同、加盟合同、员工劳动合同……法务部只有5个人,审合同排队严重,一个供应商合同有时候等两周才能回复。老板拿到一篇关于AI合同审查的报道,直接找到我们,说想做。
我当时评估完需求,觉得这事可以做,但比大多数人想象的要复杂得多。
这篇把整个项目的技术方案、踩的坑、上线后的数据全部写出来。没有包装,是真实经历。
立项阶段:需求比你想象的模糊
第一次需求会议,法务总监说了一句话:"就是AI帮我们看合同,看有没有问题。"
这句话没有任何可以直接转化为技术方案的信息。我花了整整三次深访会议,才把需求拆解清楚:
他们真正需要的是什么:
- 风险条款识别:合同里是否有对己方不利的条款(违约责任不对等、单方面终止权、免责条款过宽)
- 必要条款完整性检查:重要合同类型是否包含所有必要条款(质保期、交付标准、争议解决条款)
- 合规性检查:是否违反劳动法、民法典等强制性规定
- 对比标准模板:与公司内部的标准合同模板相比,偏差在哪里
他们不需要的:
- AI直接"批准"或"拒绝"合同(法务明确表示,最终判断必须是人)
- 自动修改合同内容
- 法律建议(只能识别问题,不能给具体法律意见,这涉及执照问题)
这个边界非常重要。很多AI合同项目死在这里:把"辅助工具"做成了"替代法务",然后出了法律问题没有人担责。
技术方案选型
模型选择:
数据合规是第一约束。合同里有供应商名称、金额、保密条款,不能走外网API。选择:本地部署vLLM + Qwen2.5-14B-Instruct。
服务器用了一台配A10 24GB的戴尔服务器,客户已有,我们只负责软件部署。
为什么选14B而不是更小或更大:
在合同条款理解这个任务上,我测试了7B、14B、32B三个规模。7B在识别复杂法律条款的逻辑关系时,漏检率太高(约25%)。14B漏检率降到8%左右,在可接受范围内。32B精度继续提升,但A10 24GB跑不了FP16的32B,量化后速度显著下降,性价比不如14B。
系统架构:
[用户上传合同] (Word/PDF)
|
v
[文档解析层]
- PDF: pdfplumber提取文本
- Word: python-docx提取
- 输出: 结构化文本+章节信息
|
v
[文本预处理]
- 章节分割(根据合同结构)
- 长文本分块(超过4000字的切块处理)
- 重叠窗口保持上下文连贯
|
v
[多维度审查引擎]
- 风险条款扫描
- 必要条款完整性检查
- 合规性检查
(每个维度独立调用LLM,Prompt不同)
|
v
[结果聚合]
- 去重和合并相关联的问题
- 按风险等级分类(高/中/低)
- 生成审查报告
|
v
[前端展示]
- 原文高亮显示问题条款
- 侧边栏显示问题说明和建议
- 导出审查报告(Word格式)核心Prompt设计
这个项目里最花时间的不是工程代码,是Prompt设计和测试。
风险条款识别Prompt(经过30多次迭代的版本):
RISK_ANALYSIS_PROMPT = """你是一名专业的合同审查律师助手,具有丰富的商事合同审查经验。
请仔细阅读以下合同条款,识别其中对【{party_role}】({party_name})存在法律风险的内容。
合同条款:
{contract_text}
审查要求:
1. 只识别明确存在的风险,不要推测或假设
2. 每个风险点必须指明具体的条款原文
3. 风险等级分为:高(可能造成重大损失或违法)、中(可能造成损失但可协商)、低(建议修改但影响有限)
4. 不得给出具体法律建议,只描述风险事实
请按以下JSON格式输出,不要输出其他内容:
{{
"risks": [
{{
"risk_level": "高/中/低",
"clause_text": "原文条款内容",
"risk_description": "该条款存在的风险描述",
"risk_category": "违约责任/权利不对等/模糊表述/合规风险/其他"
}}
],
"overall_risk_level": "高/中/低",
"summary": "整体风险概括(50字以内)"
}}"""这个Prompt有几个设计要点值得说:
角色设定要具体:不是"你是法律助手",是"你是专业合同审查律师助手,具有丰富的商事合同审查经验"。具体角色会让模型更好地理解任务边界。
party_role参数化:审查同一份合同,"对甲方的风险"和"对乙方的风险"是完全不同的。让调用方传入要保护哪方的利益。
强制JSON输出 + 明确格式示例:不给示例,模型输出的JSON格式经常会有细微差异导致解析失败。
不要输出其他内容:这句话很关键,否则模型会在JSON前后加解释性文字,解析会出错。
必要条款完整性检查Prompt:
COMPLETENESS_CHECK_PROMPT = """请检查以下{contract_type}合同是否包含所有必要条款。
合同内容:
{contract_text}
{contract_type}合同必须包含的条款:
{required_clauses}
请检查每个必要条款是否在合同中明确约定,按JSON格式输出:
{{
"missing_clauses": ["缺失的条款名称"],
"present_clauses": ["存在的条款名称"],
"unclear_clauses": [
{{
"clause_name": "条款名称",
"issue": "该条款存在但表述不清晰的描述"
}}
]
}}"""
# 不同合同类型的必要条款配置
REQUIRED_CLAUSES = {
"供应商协议": [
"货物/服务描述及规格",
"交付时间和方式",
"价格及付款条件",
"质量标准及验收",
"质保期及售后责任",
"违约责任",
"保密条款",
"争议解决方式",
"合同变更和终止"
],
"租赁合同": [
"租赁物描述",
"租赁期限",
"租金及支付方式",
"押金条款",
"维修责任划分",
"转租限制",
"提前退租条款",
"违约责任",
"争议解决方式"
],
"劳动合同": [
"工作内容和工作地点",
"工作时间和休息休假",
"劳动报酬",
"社会保险",
"劳动保护",
"合同期限",
"试用期约定",
"保密和竞业限制"
]
}把必要条款配置化是一个很重要的决策。一开始我把必要条款硬编码在Prompt里,后来法务提了很多修改意见,每改一次要改代码。改成配置化之后,法务可以自己在管理界面维护这个列表,完全不需要工程师介入。
长文档处理:最麻烦的工程问题
合同有长有短。最短的劳动合同三页,最长的一份工程总包合同有140页,约8万字。
8万字超出了任何实用context长度,必须做切分。
切分有个根本性的问题:如果一个违约责任条款横跨章节边界,你切开了,模型可能漏检。
解决方案:基于合同章节结构做切分,而不是简单按字数切。
import re
from typing import List, Tuple
def split_contract_by_structure(text: str, max_chunk_size: int = 3000) -> List[dict]:
"""
基于合同章节结构智能切分,保持章节完整性
"""
chunks = []
# 识别章节标题的正则(覆盖常见格式)
chapter_patterns = [
r'^第[一二三四五六七八九十百]+条', # 第一条、第二条
r'^第\s*\d+\s*条', # 第1条
r'^\d+\s*[.、]\s*[^\d]', # 1. 或 1、
r'^[一二三四五六七八九十]+[、.]\s*', # 一、二、
]
combined_pattern = '|'.join(f'({p})' for p in chapter_patterns)
# 按行分割,找到章节边界
lines = text.split('\n')
current_chunk = {'title': '前言', 'content': '', 'start_line': 0}
chunks_raw = []
for i, line in enumerate(lines):
if re.match(combined_pattern, line.strip()) and current_chunk['content']:
# 保存当前chunk
chunks_raw.append(current_chunk)
current_chunk = {'title': line.strip(), 'content': line + '\n', 'start_line': i}
else:
current_chunk['content'] += line + '\n'
if current_chunk['content']:
chunks_raw.append(current_chunk)
# 合并太小的chunk,分割太大的chunk
result = []
buffer = ''
buffer_title = ''
for chunk in chunks_raw:
if len(buffer) + len(chunk['content']) <= max_chunk_size:
if not buffer_title:
buffer_title = chunk['title']
buffer += chunk['content']
else:
if buffer:
result.append({'title': buffer_title, 'content': buffer})
# 如果单个章节太长,按段落再切分
if len(chunk['content']) > max_chunk_size:
sub_chunks = split_by_paragraph(chunk['content'], max_chunk_size, overlap=200)
for j, sub in enumerate(sub_chunks):
result.append({
'title': f"{chunk['title']}(第{j+1}段)",
'content': sub
})
buffer = ''
buffer_title = ''
else:
buffer = chunk['content']
buffer_title = chunk['title']
if buffer:
result.append({'title': buffer_title, 'content': buffer})
return result
def split_by_paragraph(text: str, max_size: int, overlap: int = 200) -> List[str]:
"""段落级别切分,带重叠窗口"""
paragraphs = [p for p in text.split('\n\n') if p.strip()]
chunks = []
current = ''
for para in paragraphs:
if len(current) + len(para) <= max_size:
current += para + '\n\n'
else:
if current:
chunks.append(current)
# 重叠:前一个chunk的最后overlap个字符
overlap_text = current[-overlap:] if len(current) > overlap else current
current = overlap_text + para + '\n\n'
if current:
chunks.append(current)
return chunks这个切分逻辑上线前做了大量测试,覆盖了不同格式的合同(有些合同格式非常不规则)。现在处理一份40页的合同,平均切成8-12个chunk,每个chunk独立审查后聚合结果。
上线前的最大挑战:法务不信任AI
技术上线前,我们做了一轮内测,让法务团队试用两周,对比AI审查结果和他们自己的审查结果。
数据很难看:
- AI识别出来的风险,法务认为有效的:62%
- 法务认为存在风险但AI没识别出来:约15%的合同有漏检
- AI误报(识别为风险但法务认为正常):约35%的"风险"是误报
这些数字让客户的法务总监不太高兴。
我的判断是:35%误报率在合同审查场景下是可以接受的,因为误报的代价是"多看一眼",而漏检的代价是"被坑"。宁可多报不能少报。但15%的漏检率需要降低。
我们做了几个优化:
优化1:Prompt分层审查
原来是一个Prompt做所有审查,改为:先做一遍粗粒度扫描识别高风险区域,再对高风险区域做精细化审查。这把漏检率从15%降到了7%。
优化2:加入Few-shot示例
把法务之前标注的"真实高风险案例"作为few-shot示例放入Prompt,让模型学习他们的判断标准,而不是模型自己的泛化理解。
优化3:法务可调整置信度阈值
给法务一个界面,让他们可以调整"报出多少才算风险"——对于保守的法务,可以调低阈值,宁可多报;对于想减少工作量的法务,可以调高阈值,只看高确信的风险点。
两周优化后,漏检率降到5%,法务的接受度明显提升了。
上线后的真实数据
系统上线后运行了四个月,几个关键数据:
| 指标 | 数据 |
|---|---|
| 处理合同数 | 2,847份 |
| 平均审查时间(AI) | 45秒/份(原来人工约2小时/份) |
| 法务确认有效的风险点 | 8,341个 |
| AI识别出但人工之前未发现的风险 | 312个(约3.7%) |
| 系统可用率 | 99.1% |
| 法务工作效率提升 | 平均每人每天可处理合同数从2.3份提升到11.7份 |
有三个关键发现值得分享:
发现1:AI在"必要条款完整性检查"上的表现比风险识别更稳定。这是因为必要条款检查是结构化的对照任务,模型不需要做法律判断,只需要判断"这个合同里有没有提到XX",准确率高达94%。
发现2:AI发现的312个"新风险"里,有效的约200个。这些是法务之前没注意到的,对客户来说是真实价值。
发现3:最大的阻力不是技术,是工作流改变。法务原来的工作方式是"打开Word,从头读到尾,批注"。新系统要他们"先看AI报告,再去原文验证"。有两个资深法务一开始很排斥,觉得被AI"管着"了。花了一个月才慢慢适应。
这个项目教会我的事
做企业AI项目,边界比功能重要。合同审查AI的价值,恰恰在于它不试图替代法务做决策,而是帮法务快速找到需要重点关注的地方。越清楚"AI不做什么",项目反而越容易落地。
业务流程改造是AI落地最难的部分。技术上线是开始,不是终点。让用户改变工作方式需要时间、耐心和持续的支持。
Prompt的质量需要业务专家参与。这个项目里,最好的Prompt是我和法务总监在白板前反复讨论后写出来的,不是我自己拍脑袋写的。AI项目里的领域专家时间是最关键的资源。
