法律领域一直是大语言模型应用的热门方向,但直接让通用模型回答法律问题往往会得到似是而非的结论:它可能编造不存在的法条,也可能对量刑幅度的判断偏离司法实践。要构建一个真正可用的法律Agent,核心在于把开放式的生成能力约束到一条严谨的推理链路上,也就是先检索法条、再匹配案例、最后预测判决。这篇文章将按照这个链路逐层拆解,给出具体的工程实现方案。

一、法条检索:把自然语言问题映射到精确的法律条文
法条检索是整个推理链路的第一环,它的质量直接决定了后续推理的上限。与普通的文档检索不同,法条检索有两个特殊要求:第一是精确性,法条的条、款、项必须严格对应,差一个款项可能结论完全相反;第二是体系性,检索到主法条的同时往往还需要附带对应的司法解释、修正案和指导性意见,否则推理时缺少关键细节。
工程上通常采用混合召回策略。基础做法是对法条库建立倒排索引做关键词召回,法律术语相对规范,关键词召回的准确率不错;再叠加向量召回处理口语化输入,比如当事人描述“别人借我钱一直不还”,向量模型能把它召回至民法典合同编借款合同相关条款。两路召回的结果通过RRF(倒数排名融合)合并后重排,重排模型可以基于交叉编码器训练,训练数据用历史裁判文书中“本院认为”部分引用的法条作为弱监督标签,效果往往好于通用重排模型。
值得注意的是法条的组织方式。建议把法条拆解为“法律—章—条文—款—项”的层级结构存储,每个节点带上生效时间、修正历史和关联司法解释。法律推理必须基于现行有效的条文,2021年民法典施行后大量旧法同步废止,如果法条库没有版本管理,Agent很容易引用已失效的婚姻法、合同法条文,这是法律AI系统最常见也最致命的错误之一。
import numpy as np
# 法条结构化存储示例
law_article = {
"law_name": "中华人民共和国刑法",
"article_no": "第二百六十六条",
"content": "诈骗公私财物,数额较大的,处三年以下有期徒刑、拘役或者管制……",
"effective_date": "2021-03-01",
"status": "valid", # valid / repealed / amended
"related_interpretations": [
"最高人民法院、最高人民检察院关于办理诈骗刑事案件具体应用法律若干问题的解释"
]
}
def hybrid_recall(query, keyword_hits, vector_hits, k=60):
# RRF倒数排名融合,合并关键词召回与向量召回
scores = {}
for hits in (keyword_hits, vector_hits):
for rank, article_id in enumerate(hits):
scores[article_id] = scores.get(article_id, 0) + 1.0 / (k + rank + 1)
return sorted(scores, key=scores.get, reverse=True)
# 关键词召回结果与向量召回结果融合
merged = hybrid_recall("网络诈骗金额五万怎么判",
keyword_hits=["art_266", "art_267"],
vector_hits=["art_266", "司法解释_xpzp_1"])
print("融合后排序:", merged)二、案例匹配:如何判断两个案子是不是“同类案”
案例匹配的目标是从海量裁判文书中找到与当前案件相似的判例,为判决预测提供经验依据。这里的难点在于“相似”的定义:法律意义上的相似不是文本表面相似,而是争议焦点、事实要素和法律关系上的相似。两个盗窃案的判决书文本可能差异很大(一个入室盗窃、一个扒窃),但量刑时考量的要素高度重合。
实践中有两种主流做法。第一种是基于要素的匹配:先定义罪名的量刑要素体系,比如诈骗罪包含诈骗数额、退赔情况、自首立功、主从犯地位、受害人数量等要素,用抽取模型从文书里抽取这些结构化要素,再通过要素向量的加权距离计算相似度,权重可以参考该罪名的量刑指导意见。第二种是基于语义嵌入的匹配:用法律语料微调后的嵌入模型编码“本院查明”部分的事实描述,直接计算语义余弦相似度,适合要素难以穷举的复杂民事案件。两种方法通常并行使用,要素匹配保证法律层面的可比性,语义匹配兜底覆盖长尾场景。
还有一个容易被忽视的细节是文书切分与去噪。裁判文书包含原告诉称、被告辩称、本院查明、本院认为等多个部分,匹配时应当只使用“本院查明”的事实部分和案由信息,如果把双方当事人的主观陈述也纳入编码,会引入大量噪声。另外要注意同案不同判的现实:同一地区同一时期的文书才有较强参考性,匹配时加入法院层级、地域、裁判年份的过滤条件,能显著提升案例参考价值。
from dataclasses import dataclass
@dataclass
class CaseElement:
amount: float # 涉案金额
confession: bool # 是否如实供述
restitution: bool # 是否退赔退赃
accomplice_role: str # 主犯/从犯
prior_record: int # 前科次数
def element_similarity(c1: CaseElement, c2: CaseElement) -> float:
# 简化的要素加权相似度,权重参考量刑指导意见
w_amount, w_conf, w_restitution, w_role = 0.4, 0.2, 0.25, 0.15
amount_sim = min(c1.amount, c2.amount) / max(c1.amount, c2.amount)
sim = (w_amount * amount_sim
+ w_conf * (c1.confession == c2.confession)
+ w_restitution * (c1.restitution == c2.restitution)
+ w_role * (c1.accomplice_role == c2.accomplice_role))
return sim
# 候选案例要素
current = CaseElement(50000, True, True, "主犯", 0)
history = CaseElement(48000, True, False, "主犯", 1)
print("要素相似度:", round(element_similarity(current, history), 3))三、判决预测:从法条与案例证据到可解释结论
判决预测并不是简单地让大模型“猜”一个结果,而是要把前两步得到的法条依据和案例证据组织成结构化的推理输入,让模型在约束条件下输出结论。典型的输出包括:罪与非罪的判断、量刑幅度区间、刑期点估计以及缓刑可能性。为了保证输出的严谨性,需要做两方面的约束。
第一是输入侧约束。把检索到的法条原文、匹配到的Top-K相似案例及其判决结果、当前案件的结构化要素一起注入提示词,并要求模型在回答中逐项引用:量刑幅度必须来自法条明文规定,刑期估计必须以相似案例的判决分布为锚点。这样即使模型输出有偏差,也能追溯到具体是哪条法条或哪个案例导致的,方便人工审核。第二是输出侧约束。量刑有法定刑幅度限制,可以在解码阶段做范围校验,模型给出的刑期超出法条规定的幅度时直接拒绝或触发重试,避免出现明显违反法律的输出。
评估判决预测效果时,不要只看刑期误差。更合理的指标是分级命中率:预测的量刑档位(如三年以下、三到十年、十年以上)是否与真实判决一致,以及预测的缓刑结论是否准确。司法实践中同一档位内的刑期差异带有较强的法官裁量成分,追求点估计的精确意义不大,档位判断正确才是系统可用性的关键指标。此外务必保留人工复核环节,法律Agent的定位是辅助法官和律师提高效率,最终判断责任必须由人来承担。
def build_prompt(query, law_hits, case_hits):
# 组装结构化推理输入
sections = []
sections.append(f"待判断案件:{query}")
sections.append("相关法条:")
for art in law_hits[:3]:
sections.append(f"- {art['law_name']}第{art['article_no']}条:{art['content']}")
sections.append("相似案例及判决:")
for case in case_hits[:5]:
sections.append(f"- 案号{case['case_no']},事实:{case['fact']},"
f"判决:有期徒刑{case['months']}个月,"
f"相似度{case['similarity']:.2f}")
sections.append("请基于上述法条与案例,给出量刑档位判断、"
"刑期估计区间及法律依据引用。")
return "\n".join(sections)
def validate_sentence(months, legal_range):
# 输出侧校验:刑期必须在法定幅度内
low, high = legal_range
return low <= months <= high
# 法定刑幅度:诈骗数额巨大对应三年以上十年以下
print(validate_sentence(60, (36, 120))) # True
print(validate_sentence(20, (36, 120))) # False,触发重试四、系统串联与常见踩坑点
把三个环节串联成完整的法律Agent时,建议采用显式的工具调用架构:Agent先根据用户输入调用法条检索工具,再依据命中的罪名调用案例匹配工具,最后调用判决推理模块,每一步的中间结果都落盘保存。这种显式拆分相比端到端生成有两个好处:一是每一步可以独立评估和迭代,法条检索的准确率、案例匹配的召回率都能单独度量;二是推理过程完整可审计,符合司法场景对可解释性的强制要求。
实践中最常见的坑有三个。其一是法条库更新滞后,新司法解释发布后没有同步入库,导致检索结果缺失关键依据,需要建立法条库的定期更新机制并记录版本快照。其二是嵌入模型用通用模型直接上手,法律术语的语义与日常用语差异很大,“善意取得”中的“善意”与日常含义完全不同,务必用法律语料做领域微调。其三是忽视地域差异,不同省份的量刑细则存在差异,匹配案例时不做地域过滤会让参考案例失真。把这些细节处理好,法律Agent才能从演示阶段走向真正可用。
最后需要强调合规边界。判决预测系统的输出只能作为办案参考,对外提供时必须明示其辅助定位,避免替代司法判断的表述。同时案例数据涉及当事人隐私,入库前要做好脱敏处理,案号、姓名、身份证号等字段的匿名化是系统上线前的必要步骤。