SAILER启发的法律类案推荐系统:向量检索+结构化要素四维匹配
SAILER启发的法律类案推荐系统:向量检索+结构化要素四维匹配
核心技术: ChromaDB向量相似度 × 结构化要素匹配 = 混合重排序
论文灵感: SAILER (SIGIR 2023) + CaseGNN (EMNLP 2023)
最终公式:final_score = 0.6 × vector_sim + 0.4 × element_sim
一、前言:纯向量检索的"语义漂移"问题
在法律AI中,类案推荐是高频需求——法官需要参照先例,律师需要援引判例。基于向量检索(ChromaDB/Milvus)的方案已经很成熟,但存在一个致命问题:语义漂移。
什么是语义漂移?
案例A: 张三诉李四买卖合同纠纷,争议焦点是"违约金计算方式"
案例B: 王五诉赵六买卖合同纠纷,争议焦点是"货物质量不合格"
案例C: 甲公司诉乙公司建设工程合同纠纷,争议焦点是"违约金计算方式"
纯向量检索:A↔B 相似度=0.85(同为"买卖合同",文本重叠大)
实际相关性:A↔C 更相关(同一争议焦点,虽然合同类型不同)
问题本质:向量嵌入捕获了表面语义,但忽略了法律文书的结构化要素。
二、技术方案对比
| 方案 | 论文 | 核心思路 | 优势 | GPU需求 |
|---|---|---|---|---|
| SAILER | SIGIR 2023 | 结构化预训练法律语言模型 | 深度理解法律结构 | 是 |
| CaseGNN | EMNLP 2023 | 构建案例关系图,GNN推理 | 捕获跨案例关联 | 是 |
| Contrastive Learning | ACL 2022 | 对比学习训练case encoder | 高精度相似度 | 是 |
| 混合检索(本文) | SAILER启发 | 向量+结构化要素融合 | 无GPU,API友好 | 否 |
核心思想借鉴:SAILER 论文的关键洞察是——法律文书可以分解为"事实认定(Fact)"、“争议焦点(Issue)”、"法条引用(Statute)"等结构化要素,基于这些要素的匹配比纯文本语义更准确。
我们不需要预训练SAILER模型,但可以用已有的字段提取结果实现同样的结构化匹配。
三、四维结构化要素匹配算法
3.1 算法总览
┌─────────────────────────────────────────┐
│ 文档A (查询文档) │
│ case_type: civil │
│ cause_of_action: "买卖合同纠纷" │
│ dispute_focus: ["违约金", "合同效力"] │
│ judgment: "...依照第107条..." │
└──────────────┬──────────────────────────┘
│ 对比
┌──────────────▼──────────────────────────┐
│ 文档B (候选文档) │
│ case_type: civil │
│ cause_of_action: "合同纠纷" │
│ dispute_focus: ["违约金计算"] │
│ judgment: "...依照第107条、第114条..." │
└─────────────────────────────────────────┘
element_sim=w1⋅Stype+w2⋅Scause+w3⋅Sfocus+w4⋅Slawelement\_sim = w_1 \cdot S_{type} + w_2 \cdot S_{cause} + w_3 \cdot S_{focus} + w_4 \cdot S_{law}element_sim=w1⋅Stype+w2⋅Scause+w3⋅Sfocus+w4⋅Slaw
其中 w1=0.15,w2=0.30,w3=0.25,w4=0.30w_1=0.15, w_2=0.30, w_3=0.25, w_4=0.30w1=0.15,w2=0.30,w3=0.25,w4=0.30。
3.2 维度1:案件类型完全匹配 (权重0.15)
# 最简单但最有用的特征
if fields_a.get('case_type') and fields_b.get('case_type'):
if fields_a['case_type'] == fields_b['case_type']:
score += 0.15
civil/criminal/administrative 三分类,完全匹配得满分。权重设置较低(0.15)是因为同类型内差异巨大。
3.3 维度2:案由Jaccard相似度 (权重0.30)
法律案由是半结构化文本(如"买卖合同纠纷"、“合同纠纷”),我们使用字符级Jaccard系数:
cause_a = set(fields_a.get('cause_of_action', '') or '')
cause_b = set(fields_b.get('cause_of_action', '') or '')
if cause_a and cause_b:
jaccard = len(cause_a & cause_b) / len(cause_a | cause_b)
score += 0.30 * jaccard
为什么用字符级而不是词级?
案由1: "买卖合同纠纷" → 字符集合: {买,卖,合,同,纠,纷}
案由2: "合同纠纷" → 字符集合: {合,同,纠,纷}
字符Jaccard = |{合,同,纠,纷}| / |{买,卖,合,同,纠,纷}| = 4/6 = 0.667
如果用jieba分词:
案由1: ["买卖", "合同", "纠纷"]
案由2: ["合同", "纠纷"]
词级Jaccard = 2/3 = 0.667 (恰好一样)
但对于: "房屋买卖合同纠纷" vs "买卖合同纠纷"
字符Jaccard = 5/7 = 0.714
词级Jaccard = 2/4 = 0.500 (分词歧义导致差异大)
字符级Jaccard对中文案由更鲁棒。
3.4 维度3:争议焦点重叠度 (权重0.25)
争议焦点是列表形式(如 ["违约金", "合同效力"]),计算集合交集比例:
focus_a = set(fields_a.get('dispute_focus', []) or [])
focus_b = set(fields_b.get('dispute_focus', []) or [])
if focus_a and focus_b:
overlap = len(focus_a & focus_b) / max(len(focus_a | focus_b), 1)
score += 0.25 * overlap
3.5 维度4:法条引用重叠度 (权重0.30)
这是法律领域最强的关联信号——引用相同法条的案件几乎必然高度相关。
def extract_law_refs(text):
"""从判决书/事实认定中提取法条引用"""
if not text:
return set()
pattern = r'第[\d一二三四五六七八九十百千]+条'
return set(re.findall(pattern, str(text)))
text_a = f"{fields_a.get('judgment', '')} {fields_a.get('facts', '')}"
text_b = f"{fields_b.get('judgment', '')} {fields_b.get('facts', '')}"
laws_a = extract_law_refs(text_a)
laws_b = extract_law_refs(text_b)
if laws_a and laws_b:
law_overlap = len(laws_a & laws_b) / max(len(laws_a | laws_b), 1)
score += 0.30 * law_overlap
正则解析: r'第[\d一二三四五六七八九十百千]+条' 同时支持阿拉伯数字和中文数字。
四、混合检索流程
4.1 候选扩展 + 重排序
@router.get("/similar/{doc_id}")
async def get_similar_cases(doc_id: str, top_k: int = 5):
# Step 1: 向量检索 — 多取3倍候选
candidates = search_similar_docs(query, exclude_doc_id=doc_id, top_k=top_k * 3)
# Step 2: 结构化要素计算
alpha, beta = 0.6, 0.4
ranked = []
for r in candidates:
vec_dist = r.get("distance", 1.0)
vec_sim = max(0, 1 - vec_dist / 2) # ChromaDB cosine distance → similarity
elem_sim = _compute_element_similarity(fields_a_dict, fields_b)
final_score = alpha * vec_sim + beta * elem_sim
ranked.append({...scores: {vector_similarity, element_similarity, final_score}...})
# Step 3: 按融合分数重排序
ranked.sort(key=lambda x: x["scores"]["final_score"], reverse=True)
return {"similar_cases": ranked[:top_k]}
4.2 ChromaDB距离转相似度
ChromaDB 使用 cosine distance(范围 [0, 2]),需要转换:
vector_similarity=max(0,1−cosine_distance2)vector\_similarity = \max(0, 1 - \frac{cosine\_distance}{2})vector_similarity=max(0,1−2cosine_distance)
4.3 融合权重选择
final_score=0.6×vector_sim+0.4×element_simfinal\_score = 0.6 \times vector\_sim + 0.4 \times element\_simfinal_score=0.6×vector_sim+0.4×element_sim
为什么是 0.6/0.4 而不是 0.5/0.5?
- 向量语义已经编码了大量信息(包括部分结构化信息)
- 结构化要素提供的是"硬约束"型信号(法条匹配、类型匹配)
- 如果字段提取质量不高,过大的element权重会引入噪声
- 实测:0.6/0.4 在3个测试集上的 NDCG@5 最高
五、前端展示
5.1 案例卡片 + 分数条
<div v-for="(c, i) in cases" :key="i" class="case-card">
<div class="card-header">
<span class="case-number">{{ c.case_number }}</span>
<span class="type-badge" :class="c.case_type">{{ caseTypeMap[c.case_type] }}</span>
<span class="final-score" :style="{ color: scoreColor(c.scores.final_score) }">
综合 {{ pct(c.scores.final_score) }}
</span>
</div>
<!-- 分数分解条 -->
<div class="score-breakdown">
<div class="score-item">
<span class="score-label">语义相似度</span>
<div class="score-bar" :style="{ width: pct(c.scores.vector_similarity) }"></div>
<span class="score-val">{{ pct(c.scores.vector_similarity) }}</span>
</div>
<div class="score-item">
<span class="score-label">要素匹配</span>
<div class="score-bar" :style="{ width: pct(c.scores.element_similarity) }"></div>
<span class="score-val">{{ pct(c.scores.element_similarity) }}</span>
</div>
</div>
</div>
帮助用户理解为什么这个案例被推荐——是因为文本语义像(向量高),还是因为法律结构像(要素高)。
六、实战案例分析
假设查询文档是一份"民事买卖合同纠纷"判决书,引用了《合同法》第107条和第114条:
| 候选案例 | 向量相似度 | 要素相似度 | 融合分数 | 排名变化 |
|---|---|---|---|---|
| 买卖合同纠纷A (同法条) | 0.78 | 0.72 | 0.756 | ↑ 1→1 |
| 买卖合同纠纷B (不同法条) | 0.82 | 0.15 | 0.552 | ↓ 2→3 |
| 承揽合同纠纷C (同法条) | 0.65 | 0.60 | 0.630 | ↑ 3→2 |
关键发现: 案例B虽然向量相似度最高(同为"买卖合同",表面用词相似),但法条不匹配。案例C引用了相同法条,虽然合同类型不同但争议实质相同,融合后排名上升。
七、常见问题
Q1: 如果字段提取不完整怎么办?
A: 缺失的维度自动得0分,不会影响其他维度。这就是独立加权的好处。
Q2: 为什么不用更细粒度的法条匹配(考虑具体条款)?
A: 法条编号的正则已经够用了。"第107条"级别的匹配已经提供了很强的信号。条款级别需要法律知识库支撑。
Q3: 文档数量太少(<10)时效果如何?
A: 候选池小时,向量检索和要素匹配都会退化。建议至少上传20份文书以上才能获得有意义的推荐。
八、总结
本文贡献
- 将 SAILER 论文的结构化思想应用于无GPU的工程系统
- 四维要素匹配提供了可解释的法律关联度量
- 前端分数分解让用户理解推荐理由
优化方向
- 更多维度: 加入当事人类型、判决结果相似度等
- 权重学习: 用用户点击反馈自动调整四维权重
- 跨库检索: 接入裁判文书网公开数据,扩大候选池
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)