第1886篇:一次成功的AI重构——把规则引擎替换成LLM的前后对比
第1886篇:一次成功的AI重构——把规则引擎替换成LLM的前后对比
我干这行十几年,见过很多次"把老系统推倒重来"的提议。大部分以失败告终。
这次例外。
那个规则引擎有多痛苦
我们做的是一个合同合规审查系统。业务方(法务团队)会定期更新一批合规规则,比如"合同中不能出现超过180天的账期""某类条款必须包含仲裁条款"之类的。系统要能自动扫描合同文本,标记出不合规的地方。
这个系统是大概五年前做的,用的是Drools规则引擎。当时的设计逻辑是:法务把规则整理成结构化的条件表达式,开发把它写成Drools的DRL文件,系统就能自动执行。
听起来很清晰,实际上很痛苦。
痛苦点一:规则的表达能力有限。
很多合规规则是语义性的,不是精确匹配的。比如"合同中不能暗示提供排他性服务"——这句话怎么写成DRL?"排他性服务"怎么用正则表达式匹配?法务同学想表达的是一种法律语义,但Drools只懂if-then逻辑。
我们最后的"解决方案"是让法务列举尽可能多的关键词,然后做关键词匹配。这导致漏报率很高,而且每出现一种新的表达方式都要人工更新规则。
痛苦点二:规则的维护成本极高。
DRL文件最终有大概1800条规则,每个规则平均5-10行,加起来超过两万行。这些规则由3个不同的人在5年里陆续添加,命名规范混乱,有大量的规则冗余和规则冲突。每次新增规则都要担心是否会跟现有规则产生冲突。
痛苦点三:法务改规则必须经过开发。
法务同学有新的规则需求,需要写成需求文档 → 开发理解后转成DRL → 测试验证 → 上线。这个过程平均需要两周。法务非常不满意,经常说"你们的系统跟不上业务节奏"。
为什么考虑用LLM替换
有一天,我用GPT-4随手测了一下:把一段有问题的合同文本发给它,问"这段合同条款是否符合公司合规要求"(附上了规则说明),结果让我惊讶——GPT-4不仅能准确识别出违规点,还能解释"为什么这样不合规"。
这让我意识到:合规审查本质上是一个需要法律语义理解的任务,而不是一个规则匹配任务。LLM在语义理解上的能力,天然适合这个场景。
但把整个系统换掉不是小事。我们需要先回答几个问题:
- LLM的准确率能达到什么水平?(现有Drools系统的准确率是多少?)
- 延迟能接受吗?(审查一份合同需要多长时间?)
- 成本怎么算?
- 出了问题怎么归因?(LLM是黑盒,Drools的决策可追溯)
先做评估:不是拍脑袋的决策
在做任何改动之前,我们花了两周时间做定量评估。
建立评估数据集
从历史审查记录里抽取了500份合同,每份都有法务同学的人工标注(哪些地方不合规,为什么不合规)。这500份作为评估集。
评估现有Drools系统的基线
用Drools对这500份合同重新跑一遍,对比人工标注,计算准确率、召回率、F1值。
结果:
- 精确率(precision):78%(系统报告的问题,78%确实是真问题)
- 召回率(recall):61%(真实问题里,系统只找到了61%)
- F1:68.5%
召回率61%意味着有将近40%的真实合规问题,系统完全没检测出来。这比我们预期的要差。
评估LLM方案的效果
为了快速验证,我们写了一个简单的Prompt,把规则说明和合同文本一起发给GPT-4,让它做审查,输出结构化的审查报告。
SYSTEM_PROMPT = """
你是一名专业的法务合规审查助手。你的任务是审查合同条款是否符合公司的合规要求。
公司合规规则如下:
{rules}
审查合同时,请:
1. 仔细阅读合同文本
2. 对照每一条合规规则进行检查
3. 标注出所有违规或疑似违规的条款
4. 解释违规原因
输出格式(JSON):
{{
"violations": [
{{
"rule_id": "规则编号",
"clause_text": "违规的条款原文",
"reason": "违规原因说明",
"severity": "HIGH/MEDIUM/LOW"
}}
],
"summary": "整体合规性摘要"
}}
"""对同样的500份合同跑了一遍:
- 精确率:85%(比Drools高7个百分点)
- 召回率:79%(比Drools高18个百分点!)
- F1:82%
召回率的提升是最显著的。这说明LLM能理解规则的语义意图,找到Drools用关键词匹配找不到的隐性违规。
重构方案:不是简单的替换,是系统性的设计
有了评估数据支撑,我们开始设计新系统。
核心架构
合同分块处理
一份合同可能有几十页,不能直接塞进一个Prompt。我们按章节分块,每块独立审查。
@Service
public class ContractReviewService {
@Autowired
private ContractParser contractParser;
@Autowired
private ComplianceRuleRepository ruleRepo;
@Autowired
private LlmClient llmClient;
public ContractReviewReport review(byte[] contractFile, String contractType) {
// 1. 解析合同,按条款分块
List<ContractClause> clauses = contractParser.parse(contractFile);
// 2. 加载适用的合规规则(按合同类型过滤)
List<ComplianceRule> applicableRules = ruleRepo.findByContractType(contractType);
String rulesText = formatRules(applicableRules);
// 3. 对每个条款块进行审查(并行执行)
List<CompletableFuture<ClauseReviewResult>> futures = clauses.stream()
.map(clause -> CompletableFuture.supplyAsync(
() -> reviewClause(clause, rulesText),
reviewExecutor
))
.collect(Collectors.toList());
List<ClauseReviewResult> results = futures.stream()
.map(CompletableFuture::join)
.collect(Collectors.toList());
// 4. 聚合结果,去重(同一问题可能在多个条款中被识别)
return aggregateResults(results, clauses);
}
private ClauseReviewResult reviewClause(ContractClause clause, String rulesText) {
String prompt = buildReviewPrompt(clause.getText(), rulesText, clause.getTitle());
String rawResponse = llmClient.complete(prompt);
try {
return parseReviewResult(rawResponse, clause.getId());
} catch (JsonParseException e) {
log.warn("Failed to parse LLM review result for clause {}, retrying", clause.getId());
// 重试一次,加上更强的格式约束
String retryPrompt = prompt + "\n\n[重要:请严格按照上述JSON格式输出,不要包含任何其他文字]";
String retryResponse = llmClient.complete(retryPrompt);
return parseReviewResult(retryResponse, clause.getId());
}
}
private String buildReviewPrompt(String clauseText, String rulesText, String clauseTitle) {
return String.format("""
你是专业的法务合规审查助手。请对以下合同条款进行合规审查。
适用的合规规则:
%s
待审查的条款(%s):
%s
请输出JSON格式的审查结果,仅输出JSON,不包含其他内容:
{
"violations": [
{
"rule_id": "触发的规则编号",
"excerpt": "触发规则的具体文字",
"reason": "为什么违反该规则",
"severity": "HIGH/MEDIUM/LOW",
"suggestion": "修改建议"
}
],
"is_compliant": true或false
}
""",
rulesText, clauseTitle, clauseText
);
}
}规则管理系统:让法务自己维护规则
这是整个重构中我最满意的部分。我们做了一个简单的规则管理界面,法务同学可以直接用自然语言写规则,不需要任何代码知识。
@Entity
@Table(name = "compliance_rule")
public class ComplianceRule {
@Id
private String ruleId;
private String ruleName;
@Column(columnDefinition = "TEXT")
private String ruleDescription; // 自然语言描述,法务直接写
@Column(columnDefinition = "TEXT")
private String examples; // 违规示例,帮助LLM理解规则意图
@Column(columnDefinition = "TEXT")
private String counterExamples; // 不违规的示例,减少误判
private String applicableContractTypes; // 适用合同类型,逗号分隔
private String severity; // HIGH/MEDIUM/LOW
private Boolean enabled;
private LocalDateTime lastModifiedAt;
private String lastModifiedBy;
// 生成给LLM看的规则描述
public String toPromptText() {
StringBuilder sb = new StringBuilder();
sb.append(String.format("[规则%s] %s\n", ruleId, ruleName));
sb.append("规则说明:").append(ruleDescription).append("\n");
if (examples != null && !examples.isEmpty()) {
sb.append("违规示例:").append(examples).append("\n");
}
if (counterExamples != null && !counterExamples.isEmpty()) {
sb.append("不违规的情况:").append(counterExamples).append("\n");
}
return sb.toString();
}
}法务同学现在写规则的方式是这样的(截取一条真实规则):
规则名称:禁止排他性条款
规则说明:合同中不得包含限制甲方与其他供应商合作的排他性条款,无论该条款是明确约定还是通过隐晦的语言暗示。
违规示例:
- "甲方在合作期间不得采购乙方竞品"
- "甲方应优先选用乙方提供的服务,除非乙方无法满足需求"
- "本合同为甲方的唯一×××采购渠道"
不违规的情况:
- "乙方应保证在合作期间不向甲方的直接竞争对手提供服务"(这是限制乙方,不是甲方)
- "甲方承诺每年不低于×××万元的采购额"(这是最低采购额承诺,不是排他性限制)
适用类型:采购合同,服务合同
严重程度:HIGH这种自然语言规则,直接给LLM理解,比DRL代码更精准。
上线后的对比数据
重构版本上线三个月后,我们做了一次系统性的对比评估(用新的评估样本):
| 指标 | Drools版本 | LLM版本 | 变化 |
|---|---|---|---|
| 精确率 | 78% | 86% | +8% |
| 召回率 | 61% | 81% | +20% |
| F1值 | 68.5% | 83.4% | +14.9% |
| 平均审查时间 | 12秒(一份标准合同) | 45秒 | -270%(变慢了) |
| 规则更新周期 | 平均2周 | 当天 | 大幅改善 |
| 法务团队满意度 | 调研评分3.2/5 | 调研评分4.6/5 | +44% |
延迟是唯一的负项。45秒对于"提交后等结果"的异步场景是可以接受的,但不适合需要实时反馈的场景。
我们为此做了一个优化:对于重要条款(金额条款、违约条款等),优先审查并先行返回,让用户在等待完整报告的同时能看到最关键部分的结果。
没想到的收益:审查报告的质量
Drools版本的审查报告大概是这样的:
规则R045违反:第7.2条包含关键词"独家"
规则R102违反:第9.1条缺少"争议解决"相关表述LLM版本的报告是这样的:
【重要风险】第7.2条 - 排他性条款风险(高风险)
条款原文:"本协议有效期内,甲方不得向其他技术服务商寻求相似服务。"
风险分析:该条款构成对甲方的排他性限制,违反公司合规规则R045。排他性条款
会限制公司的采购灵活性,在供应商服务质量下降或价格不合理时无法及时切换,
存在较大商业风险。
修改建议:删除此条款,或改为对乙方的保密义务和竞争限制,而非对甲方采购
行为的限制。法务同学看到这个报告的第一反应是:"这比我们自己写的审查意见还详细。"
一个警告:LLM不是银弹
重构成功,不代表LLM适合所有规则引擎的场景。
有几种情况不适合用LLM替换规则引擎:
精确数值计算的规则:比如"利率不超过LPR的1.5倍",LLM做数值计算不可靠,Drools更准确。
高频低复杂度的规则:如果你的规则很简单(比如字段格式校验),LLM的成本和延迟都不划算。
审计合规要求极严的场景:LLM的决策过程不如Drools透明可追溯。在某些强监管场景下,监管机构要求能精确说明"为什么这里不合规",LLM的解释虽然流畅但可能缺乏法律上的精确性。
我们选择LLM是因为这个场景的核心难点是语义理解,而不是精确计算。如果你的规则引擎主要做的是数值比较和精确匹配,LLM不是好的替代方案。
