第2073篇:法律场景AI应用——合同审查和法规查询的工程实践
2026/4/30大约 9 分钟
第2073篇:法律场景AI应用——合同审查和法规查询的工程实践
适读人群:法律科技方向或需要处理合规文档的工程师 | 阅读时长:约18分钟 | 核心价值:掌握法律场景下AI应用的特殊要求,包括免责声明、条款提取、合规检查等
律所和企业法务部门的需求很有规律:大量合同需要审查,法规需要追踪,但熟练律师的时间成本很高。
AI在法律领域能做的是"pre-screening"——先做初步筛查,把高风险条款、异常内容标记出来,让律师聚焦在最需要人工判断的地方。
这篇文章讲如何用AI处理法律文档,以及这个场景的特殊工程要求。
法律AI的能力边界
合同关键信息提取
/**
* 合同关键信息提取服务
* 从非结构化合同文本中提取结构化信息
*/
@Service
@RequiredArgsConstructor
@Slf4j
public class ContractExtractionService {
private final ChatLanguageModel llm;
private final ObjectMapper objectMapper;
/**
* 提取合同的核心要素
*/
public ContractElements extractElements(String contractText) {
String prompt = String.format("""
请从以下合同文本中提取关键要素。
提取规则:
- 如果信息不存在,填null
- 金额统一转换为数字(人民币元)
- 日期格式统一为yyyy-MM-dd
- 主体名称提取完整的法定名称
合同文本:
%s
请输出JSON格式(只输出JSON):
{
"contractType": "合同类型(买卖/服务/租赁/劳动/其他)",
"contractNumber": "合同编号",
"signDate": "签订日期",
"effectiveDate": "生效日期",
"expiryDate": "到期日期",
"partyA": {
"name": "甲方全称",
"legalRepresentative": "法定代表人",
"address": "注册地址",
"contactPerson": "联系人",
"taxId": "统一社会信用代码"
},
"partyB": {
"name": "乙方全称",
"legalRepresentative": "法定代表人",
"address": "注册地址"
},
"contractAmount": 合同金额数字或null,
"currency": "货币单位",
"paymentTerms": "付款方式描述",
"deliveryTerms": "交付/履行方式",
"warrantyPeriod": "质保期",
"terminationConditions": ["终止条件1", "终止条件2"],
"governingLaw": "适用法律",
"disputeResolution": "争议解决方式"
}
""", truncate(contractText, 6000));
try {
String response = llm.generate(prompt);
String json = extractJson(response);
return objectMapper.readValue(json, ContractElements.class);
} catch (Exception e) {
log.error("合同要素提取失败: {}", e.getMessage());
return new ContractElements();
}
}
/**
* 提取合同中的义务条款
* 分别列出甲方和乙方的主要义务
*/
public ObligationsResult extractObligations(String contractText) {
String prompt = String.format("""
从以下合同中提取双方的主要权利和义务条款。
只提取明确的、具体的义务,不要推断。
对于每项义务,标注条款编号(如:第3条第2款)。
合同文本:
%s
输出JSON:
{
"partyAObligations": [
{"clause": "条款编号", "description": "义务描述", "isKeyObligation": true/false}
],
"partyBObligations": [...],
"penalties": [
{"trigger": "违约触发条件", "penalty": "违约后果", "clause": "条款编号"}
]
}
""", truncate(contractText, 6000));
try {
String response = llm.generate(prompt);
return objectMapper.readValue(extractJson(response), ObligationsResult.class);
} catch (Exception e) {
return new ObligationsResult();
}
}
private String truncate(String text, int maxLength) {
if (text.length() <= maxLength) return text;
// 保留前后各一半
int half = maxLength / 2;
return text.substring(0, half) + "\n...[中间内容省略]...\n" +
text.substring(text.length() - half);
}
private String extractJson(String text) {
int start = text.indexOf('{');
int end = text.lastIndexOf('}');
return start >= 0 && end > start ? text.substring(start, end + 1) : "{}";
}
@Data
public static class ContractElements {
private String contractType;
private String contractNumber;
private String signDate;
private String effectiveDate;
private String expiryDate;
private Party partyA;
private Party partyB;
private Double contractAmount;
private String currency;
private String paymentTerms;
private String deliveryTerms;
private String warrantyPeriod;
private List<String> terminationConditions;
private String governingLaw;
private String disputeResolution;
@Data
public static class Party {
private String name;
private String legalRepresentative;
private String address;
private String contactPerson;
private String taxId;
}
}
@Data
public static class ObligationsResult {
private List<Obligation> partyAObligations = new ArrayList<>();
private List<Obligation> partyBObligations = new ArrayList<>();
private List<PenaltyClause> penalties = new ArrayList<>();
@Data
public static class Obligation {
private String clause;
private String description;
private boolean isKeyObligation;
}
@Data
public static class PenaltyClause {
private String trigger;
private String penalty;
private String clause;
}
}
}风险条款检测
/**
* 合同风险条款检测
* 识别常见的不公平条款和风险点
*/
@Service
@RequiredArgsConstructor
@Slf4j
public class ContractRiskAnalyzer {
private final ChatLanguageModel llm;
private final ObjectMapper objectMapper;
// 风险条款模式库(可以从数据库加载和更新)
private static final List<RiskPattern> RISK_PATTERNS = List.of(
new RiskPattern(
"单方修改权",
Pattern.compile("甲方有权随时修改|单方面变更|甲方可以调整.*无需通知"),
RiskLevel.HIGH
),
new RiskPattern(
"格式霸王条款",
Pattern.compile("本合同所有条款最终解释权归甲方"),
RiskLevel.MEDIUM
),
new RiskPattern(
"无限连带责任",
Pattern.compile("无限连带责任|无上限赔偿|全部损失.*包括间接"),
RiskLevel.HIGH
),
new RiskPattern(
"自动续约陷阱",
Pattern.compile("自动续约.*不通知|默认续期.*不退款"),
RiskLevel.MEDIUM
)
);
/**
* 规则引擎检测(快速、确定性)
*/
public List<RiskFinding> detectByRules(String contractText) {
List<RiskFinding> findings = new ArrayList<>();
for (RiskPattern pattern : RISK_PATTERNS) {
Matcher matcher = pattern.pattern().matcher(contractText);
while (matcher.find()) {
// 找到包含匹配的段落(前后100字)
int start = Math.max(0, matcher.start() - 100);
int end = Math.min(contractText.length(), matcher.end() + 100);
String context = contractText.substring(start, end);
findings.add(new RiskFinding(
pattern.riskType(),
pattern.level(),
context,
matcher.start(),
"规则引擎",
getRecommendation(pattern.riskType())
));
}
}
return findings;
}
/**
* LLM深度分析(慢但更全面)
* 用于检测规则引擎无法覆盖的复杂风险
*/
public List<RiskFinding> analyzeByLlm(String contractText) {
String prompt = String.format("""
作为一名经验丰富的合同律师,请分析以下合同中的潜在风险条款。
重点关注:
1. 权利义务明显不对等的条款
2. 模糊或歧义的表述(可能引发争议)
3. 违约责任设置不合理的条款
4. 可能侵害弱势一方权益的条款
5. 法律合规性问题
注意:只标记具有实质性风险的条款,不要过度标记。
合同文本片段:
%s
输出JSON(只输出JSON):
{
"risks": [
{
"riskType": "风险类型",
"riskLevel": "HIGH/MEDIUM/LOW",
"clause": "原文引用(精确引用,不超过100字)",
"reason": "风险原因解释",
"recommendation": "修改建议"
}
]
}
重要提示:以上分析仅供参考,不构成正式法律意见,具体法律问题请咨询执业律师。
""", truncate(contractText, 5000));
try {
String response = llm.generate(prompt);
JsonNode root = objectMapper.readTree(extractJson(response));
List<RiskFinding> findings = new ArrayList<>();
for (JsonNode riskNode : root.get("risks")) {
findings.add(new RiskFinding(
riskNode.get("riskType").asText(),
RiskLevel.valueOf(riskNode.get("riskLevel").asText()),
riskNode.get("clause").asText(),
-1, // LLM不提供位置
"LLM分析",
riskNode.get("recommendation").asText()
));
}
return findings;
} catch (Exception e) {
log.error("LLM风险分析失败: {}", e.getMessage());
return List.of();
}
}
/**
* 综合分析:规则引擎 + LLM
*/
public ContractRiskReport analyzeContract(String contractText) {
// 并发执行两种分析
CompletableFuture<List<RiskFinding>> ruleFuture =
CompletableFuture.supplyAsync(() -> detectByRules(contractText));
CompletableFuture<List<RiskFinding>> llmFuture =
CompletableFuture.supplyAsync(() -> analyzeByLlm(contractText));
List<RiskFinding> ruleFindings = ruleFuture.join();
List<RiskFinding> llmFindings = llmFuture.join();
// 合并去重
List<RiskFinding> allFindings = new ArrayList<>();
allFindings.addAll(ruleFindings);
allFindings.addAll(llmFindings);
long highRiskCount = allFindings.stream()
.filter(f -> f.level() == RiskLevel.HIGH).count();
String recommendation = highRiskCount > 0 ?
"发现" + highRiskCount + "处高风险条款,建议咨询律师后签署" :
allFindings.isEmpty() ? "未发现明显风险条款" : "存在少量中低风险条款,请审慎评估";
return new ContractRiskReport(allFindings, recommendation,
"以上分析仅供参考,不构成法律意见,签署重要合同前请咨询执业律师。");
}
private String getRecommendation(String riskType) {
Map<String, String> recommendations = Map.of(
"单方修改权", "建议增加:任何修改须经双方协商一致并书面确认",
"格式霸王条款", "建议删除该条款或修改为双方共同解释",
"无限连带责任", "建议明确责任上限,通常为合同金额的一定比例"
);
return recommendations.getOrDefault(riskType, "建议咨询法律专业人士");
}
private String truncate(String text, int maxLength) {
return text.length() <= maxLength ? text : text.substring(0, maxLength) + "...";
}
private String extractJson(String text) {
int start = text.indexOf('{');
int end = text.lastIndexOf('}');
return start >= 0 && end > start ? text.substring(start, end + 1) : "{}";
}
public record RiskPattern(String riskType, Pattern pattern, RiskLevel level) {}
public record RiskFinding(
String riskType,
RiskLevel level,
String clauseText,
int position,
String detectionMethod,
String recommendation
) {}
public record ContractRiskReport(
List<RiskFinding> findings,
String recommendation,
String disclaimer
) {}
public enum RiskLevel { HIGH, MEDIUM, LOW }
}法规查询RAG
/**
* 法规知识库构建和查询
* 把法律法规构建成RAG知识库,支持精准检索
*/
@Service
@RequiredArgsConstructor
@Slf4j
public class LegalRagService {
private final EmbeddingModel embeddingModel;
private final EmbeddingStore<TextSegment> legalVectorStore;
private final ChatLanguageModel llm;
/**
* 法律问答(带来源引用)
*/
public LegalQueryResult answer(String question) {
// 1. 向量检索相关法条
float[] queryEmbedding = embeddingModel.embed(question);
EmbeddingSearchRequest request = EmbeddingSearchRequest.builder()
.queryEmbedding(Embedding.from(queryEmbedding))
.maxResults(5)
.minScore(0.7)
.build();
List<EmbeddingMatch<TextSegment>> matches =
legalVectorStore.search(request).matches();
if (matches.isEmpty()) {
return new LegalQueryResult(
"未找到相关法律条文,请咨询执业律师。",
List.of(),
"系统无法找到相关法律依据,以上回答仅供参考。"
);
}
// 2. 构建上下文(带条文来源)
StringBuilder context = new StringBuilder();
List<LegalSource> sources = new ArrayList<>();
for (EmbeddingMatch<TextSegment> match : matches) {
TextSegment segment = match.embedded();
String law = segment.metadata("law_name");
String article = segment.metadata("article_number");
String content = segment.text();
context.append(String.format("【%s 第%s条】\n%s\n\n", law, article, content));
sources.add(new LegalSource(law, article, content));
}
// 3. 基于法条回答
String prompt = String.format("""
请基于以下法律条文回答问题。
要求:
1. 只引用给定的法律条文,不要添加条文外的内容
2. 引用时注明来源,格式:(依据:《XXX》第X条)
3. 如果给定条文不足以完整回答,说明"本回答基于现有条文,建议咨询律师获取完整意见"
相关法律条文:
%s
问题:%s
回答末尾必须附加:本回答仅供参考,不构成法律意见。
""", context.toString(), question);
String answer = llm.generate(prompt);
return new LegalQueryResult(
answer,
sources,
"本回答基于AI检索的法律条文,不构成正式法律意见,具体问题请咨询执业律师。"
);
}
public record LegalQueryResult(
String answer,
List<LegalSource> sources,
String disclaimer
) {}
public record LegalSource(
String lawName,
String articleNumber,
String content
) {}
}法律文件生成的注意事项
/**
* 法律文件AI辅助生成
* 注意:生成的模板仍需律师审核
*/
@Service
@RequiredArgsConstructor
public class LegalDocumentGenerator {
private final ChatLanguageModel llm;
/**
* 生成合同草稿(仅供参考,必须人工审核)
*/
public GeneratedDocument generateContractDraft(
ContractGenerationRequest request) {
String prompt = String.format("""
请根据以下信息生成一份%s合同草稿。
注意:
1. 使用标准的中文合同格式
2. 对于不确定的内容,用[待填写:说明]标注
3. 在关键条款后添加注释,说明注意事项
合同类型:%s
甲方:%s
乙方:%s
合同主要内容:%s
特殊要求:%s
生成后在文档末尾添加:"本合同草稿由AI辅助生成,仅供参考,
正式签署前请务必咨询执业律师进行专业审核。"
""",
request.contractType(),
request.contractType(),
request.partyA(),
request.partyB(),
request.mainContent(),
request.specialRequirements() != null ? request.specialRequirements() : "无"
);
String draft = llm.generate(prompt);
return new GeneratedDocument(
draft,
"本文件由AI辅助生成,仅供参考,不具有法律效力。" +
"正式合同需由执业律师审核后方可使用。",
LocalDateTime.now()
);
}
public record ContractGenerationRequest(
String contractType,
String partyA,
String partyB,
String mainContent,
String specialRequirements
) {}
public record GeneratedDocument(
String content,
String disclaimer,
LocalDateTime generatedAt
) {}
}法律场景的AI应用,最重要的原则是:AI做辅助,律师做决策。
任何面向C端用户的法律AI产品,必须在显著位置显示免责声明,不能让用户误认为AI输出等同于法律意见。这不只是合规要求,也是产品负责任的体现。
