第2374篇:法律合规RAG——针对监管文本的高精度检索系统设计
大约 7 分钟
第2374篇:法律合规RAG——针对监管文本的高精度检索系统设计
适读人群:在金融、法律、合规领域做AI应用的工程师 | 阅读时长:约20分钟 | 核心价值:掌握法律监管文本的专项处理技术,解决条款引用、交叉引用和精确匹配等核心问题
做过法律合规领域的RAG后,我对"高精度"这个词有了新的理解。
在一般的知识库问答里,回答"大概对"就够了——用户想了解某个功能怎么用,AI给出一个八九不离十的解释,用户可以自己判断。
但法律合规场景完全不同。有个同事在给银行做合规RAG时,系统把一条监管规定的"不超过"理解成了"不低于",虽然只是方向搞反了,但如果真的按这个执行,可能触发监管处罚。合规系统的错误代价不是"用户体验差",而是真实的法律风险。
法律文本的特殊性
/**
* 法律监管文本区别于普通文档的关键特征
*
* 特征1:条款引用关系复杂
* "依据《商业银行资本管理办法》第二十三条第三款..."
* 一条规定可能引用了5-10个其他条款
* 不把这些引用的条款内容也拿进来,无法完整理解
*
* 特征2:精确措辞至关重要
* "应当"、"可以"、"不得"在法律语境里有严格区别
* 语义相似的表述,法律含义可能完全相反
* 向量相似度检索容易忽略这种措辞差异
*
* 特征3:层级结构严谨
* 法律 > 行政法规 > 部门规章 > 规范性文件
* 下位法和上位法冲突时,以上位法为准
* RAG必须理解这个层级关系
*
* 特征4:时效性要求极高
* 已废止的条款绝对不能引用
* 正在修订中的条款需要特别标注
* 条款的生效日期可能不同(某条款明年起施行)
*/法律文本的结构化解析
@Service
public class LegalDocumentParser {
/**
* 解析法律文本,保留完整的层级结构
*
* 法律文本的典型结构:
* 第一章 总则
* 第一条 ...
* 第二条 ...
* 第二章 具体规定
* 第三条 ...
* (一) ...
* (二) ...
*/
public LegalDocument parse(String content, LegalDocumentMetadata metadata) {
LegalDocument doc = new LegalDocument();
doc.setMetadata(metadata);
// 解析章节结构
List<Chapter> chapters = parseChapters(content);
for (Chapter chapter : chapters) {
List<Article> articles = parseArticles(chapter.getContent());
for (Article article : articles) {
// 每个条款作为一个独立的检索单元
LegalArticle legalArticle = LegalArticle.builder()
.articleId(metadata.getDocId() + "#" + article.getNumber())
.docId(metadata.getDocId())
.docName(metadata.getDocName())
.chapterNumber(chapter.getNumber())
.chapterTitle(chapter.getTitle())
.articleNumber(article.getNumber())
.content(article.getContent())
// 提取这个条款引用的其他条款
.crossReferences(extractCrossReferences(article.getContent()))
// 提取关键法律术语
.legalKeywords(extractLegalKeywords(article.getContent()))
// 提取义务类型(应当/不得/可以)
.obligationType(classifyObligationType(article.getContent()))
.build();
doc.addArticle(legalArticle);
}
}
return doc;
}
/**
* 提取条款中的交叉引用
* 例:"依据本办法第二十三条"、"《商业银行法》第四十二条"
*/
private List<CrossReference> extractCrossReferences(String articleContent) {
List<CrossReference> refs = new ArrayList<>();
// 匹配内部引用:"本办法第X条"、"前款"、"上款"等
Pattern internalPattern = Pattern.compile(
"(本(?:办法|规定|条例|法))?第([一二三四五六七八九十百]+)条"
);
Matcher internalMatcher = internalPattern.matcher(articleContent);
while (internalMatcher.find()) {
refs.add(CrossReference.internal(internalMatcher.group()));
}
// 匹配外部引用:"《XXX》第X条"
Pattern externalPattern = Pattern.compile(
"《([^》]+)》(?:第([一二三四五六七八九十百]+)条)?"
);
Matcher externalMatcher = externalPattern.matcher(articleContent);
while (externalMatcher.find()) {
refs.add(CrossReference.external(
externalMatcher.group(1),
externalMatcher.group(2)
));
}
return refs;
}
/**
* 判断义务类型
* 这是法律文本中最重要的语义信息之一
*/
private ObligationType classifyObligationType(String content) {
if (content.contains("应当") || content.contains("必须") || content.contains("应")) {
return ObligationType.MANDATORY; // 强制性义务
}
if (content.contains("不得") || content.contains("禁止") || content.contains("不应")) {
return ObligationType.PROHIBITED; // 禁止性规定
}
if (content.contains("可以") || content.contains("可") || content.contains("有权")) {
return ObligationType.PERMISSIVE; // 授权性规定
}
return ObligationType.INFORMATIONAL; // 说明性条款
}
}双轨检索:向量 + 关键词精确匹配
法律文本不能只用向量检索,必须结合关键词精确匹配。
@Service
public class LegalRAGRetriever {
private final VectorStore vectorStore;
private final ElasticsearchClient esClient; // 关键词检索用ES
/**
* 法律文本的双轨检索
*
* 向量检索:找语义相关的条款(用于找到大致相关的法规领域)
* 关键词检索:精确匹配法律术语、条款编号(保证精确性)
*
* 两者结果取并集,再重排序
*/
public LegalRetrievalResult retrieve(String query) {
// 轨道1:向量语义检索
List<LegalArticle> vectorResults = vectorSearch(query);
// 轨道2:关键词精确检索
// 提取查询中的法律关键词(条款号、法规名、法律术语)
LegalQueryAnalysis analysis = analyzeLegalQuery(query);
List<LegalArticle> keywordResults = keywordSearch(analysis);
// 合并,去重,重排序
List<LegalArticle> merged = mergeAndRerank(vectorResults, keywordResults, query);
// 补全交叉引用
List<LegalArticle> withReferences = enrichWithCrossReferences(merged);
return LegalRetrievalResult.of(withReferences, analysis);
}
/**
* 分析法律查询的特殊性
*/
private LegalQueryAnalysis analyzeLegalQuery(String query) {
LegalQueryAnalysis analysis = new LegalQueryAnalysis();
// 提取条款号引用:"第二十三条"
Pattern articlePattern = Pattern.compile("第([一二三四五六七八九十百]+)条");
Matcher matcher = articlePattern.matcher(query);
while (matcher.find()) {
analysis.addArticleReference(matcher.group(1));
}
// 提取法规名称
Pattern docPattern = Pattern.compile("《([^》]+)》");
Matcher docMatcher = docPattern.matcher(query);
while (docMatcher.find()) {
analysis.addDocumentReference(docMatcher.group(1));
}
// 提取业务关键词
analysis.setKeywords(extractLegalKeywords(query));
return analysis;
}
/**
* 补全交叉引用的条款
*
* 如果检索到的条款引用了其他条款,把被引用的条款也查出来
* 深度限制为1(只展开一级引用,避免无限展开)
*/
private List<LegalArticle> enrichWithCrossReferences(List<LegalArticle> articles) {
Set<String> processedIds = articles.stream()
.map(LegalArticle::getArticleId)
.collect(Collectors.toSet());
List<LegalArticle> enriched = new ArrayList<>(articles);
for (LegalArticle article : articles) {
for (CrossReference ref : article.getCrossReferences()) {
if (ref.isInternal()) {
String refId = resolveInternalReference(article.getDocId(), ref);
if (refId != null && !processedIds.contains(refId)) {
LegalArticle refArticle = findArticleById(refId);
if (refArticle != null) {
refArticle.setNote("(被第" + article.getArticleNumber() + "条引用)");
enriched.add(refArticle);
processedIds.add(refId);
}
}
}
}
}
return enriched;
}
}Prompt设计:法律场景的特殊处理
@Service
public class LegalPromptBuilder {
/**
* 法律RAG的Prompt有几个关键要求:
* 1. 必须原文引用,不能改写条款内容
* 2. 必须标注来源(哪部法规第几条)
* 3. 不确定时,明确说明不确定,不能编造
* 4. 明确区分"应当"和"可以"的不同法律效力
*/
public String buildLegalPrompt(String question, LegalRetrievalResult retrieval) {
String articlesContext = formatArticlesForPrompt(retrieval.getArticles());
return """
你是一个法律合规助手。请基于以下法律条款回答问题。
重要规则:
1. 回答时必须引用具体条款,格式为"依据《XX》第X条:"
2. 不要改写或意译条款内容,应原文引用关键表述
3. 如果条款明确区分"应当"、"不得"、"可以",必须在回答中保留这种区别
4. 如果提供的条款不足以完整回答问题,请明确说明"现有资料中未涵盖..."
5. 不要基于推断给出法律建议,只陈述条款规定
相关法律条款:
%s
问题:%s
请给出准确的法律条款解析:
""".formatted(articlesContext, question);
}
private String formatArticlesForPrompt(List<LegalArticle> articles) {
StringBuilder sb = new StringBuilder();
for (LegalArticle article : articles) {
sb.append(String.format(
"【%s 第%s条】\n%s\n\n",
article.getDocName(),
article.getArticleNumber(),
article.getContent()
));
if (article.getNote() != null) {
sb.append("(注:").append(article.getNote()).append(")\n\n");
}
}
return sb.toString();
}
}结果验证:不能只靠LLM判断
法律场景必须加人工审核环节,或者至少加自动验证。
@Service
public class LegalAnswerValidator {
/**
* 验证AI生成的法律回答
* 检查是否有条款引用、引用是否存在、结论是否和条款一致
*/
public ValidationResult validate(String answer, List<LegalArticle> sourceArticles) {
List<ValidationIssue> issues = new ArrayList<>();
// 检查1:是否有明确的条款引用
if (!containsArticleReference(answer)) {
issues.add(ValidationIssue.error("回答缺少法律条款引用"));
}
// 检查2:引用的条款是否存在于提供的上下文中
List<String> referencedArticles = extractReferencedArticles(answer);
for (String ref : referencedArticles) {
boolean found = sourceArticles.stream()
.anyMatch(a -> articleMatchesReference(a, ref));
if (!found) {
issues.add(ValidationIssue.warning("回答引用了上下文未提供的条款:" + ref));
}
}
// 检查3:义务方向检查(应当/不得不能混淆)
for (LegalArticle article : sourceArticles) {
if (article.getObligationType() == ObligationType.PROHIBITED) {
// 如果条款是"不得",回答里不应该出现"可以"的表述
if (answerContradicts(answer, article)) {
issues.add(ValidationIssue.error(
"回答可能与" + article.getDocName() + "第" +
article.getArticleNumber() + "条的禁止性规定冲突"
));
}
}
}
return ValidationResult.of(issues);
}
}实践建议
法律合规RAG是RAG应用里风险最高的场景之一。几点经验:
1. 准确性优先于召回率:宁愿说"我没有找到相关规定",也不要给出不确定的回答。在系统提示里明确这个原则。
2. 必须有人工审核节点:至少在高风险操作前(如向监管部门报告前),需要合规专员确认AI的解读。
3. 建立条款变更监控:法规更新很频繁,需要自动监控监管机构发布的最新文件,有变更时触发人工审核和知识库更新。
4. 保留完整的引用链:每个回答都要记录是从哪些条款生成的,出了问题能追溯。
