
简介这份资源面向希望将大语言模型落地到中文法律场景的开发者与算法学习者聚焦法律问答、法条理解与指令微调等应用方向适合具备一定Python与深度学习基础、想快速跑通法律领域模型训练与推理流程的读者。压缩包共42个文件约3.41MB以Python脚本为主体配合JSON指令与配置数据、Shell训练与推理脚本、少量图片与说明文档覆盖数据处理、词表合并、微调、推理、评估及WebUI演示等环节。目录中可见法律词表、罪名数据、指令训练与推理样例、提示词模板以及LoRA权重与基础模型占位结构便于按模块理解一套法律大模型的完整工程组织方式。目前已有120人学习下载。读者可据此梳理从语料清洗、指令构造到微调评估的实践路径并借助脚本与模板快速搭建自己的法律问答实验环境。1. 中文法律大模型落地从“能聊法条”到“能办案子”的那道坎很多团队第一次做法律 AI都会掉进同一个坑拿一个通用中文大模型喂几百条法条做个 RAG演示时问“民间借贷利率上限是多少”答得头头是道一上真实业务就翻车——问“这份合同里第 7 条和第 12 条冲突按现行司法解释哪个优先”模型开始一本正经地胡说。问题不在模型不够大而在于中文法律知识有它自己的结构法条、司法解释、指导性案例、地方裁判口径层层嵌套还有大量“原则性表述 具体适用边界”的组合。基于中文法律知识的大语言模型本质上是把这种结构化的法律语义通过继续预训练、指令微调、检索增强三条路径灌进模型让它在“法言法语”的语境里做推理而不是背答案。这套方案适合三类人想给律所/法务做内部问答工具的后端工程师、要做法条检索摘要的算法同学、以及评估“本地部署大语言模型”能不能扛住法律场景的架构师。下面按我实际趟过的路径拆开讲。2. 中文法律语料怎么选、怎么洗、怎么切数据决定天花板2.1 法律语料的四个层级与配比逻辑法律领域的数据不是“越多越好”而是“层级越清晰越好”。我一般把语料分成四层第一层是法律法规原文包括宪法、法律、行政法规、地方性法规、部门规章这部分权威性最高但语义密度低适合做继续预训练打底第二层是司法解释和最高法/最高检的批复、答复这类文本直接解决“法条怎么用”的问题是微调的核心燃料第三层是指导性案例和公报案例带裁判要旨和判决理由是让模型学会“从事实到法条再到结论”推理链的关键第四层是裁判文书网公开的普通判决书量大但噪声高需要严格清洗。配比上我的经验是继续预训练阶段法条原文占 50%、司法解释 20%、案例 30%指令微调阶段反过来案例和司法解释占 70% 以上因为微调要的是“问答对”和“推理链”不是单纯的语言建模。提示不要直接把裁判文书网的全量文书丢进去。里面大量重复、格式错乱、当事人隐私信息必须先做去重和脱敏否则模型会学会“本院认为”后面接一堆无意义套话。2.2 清洗流水线的三个关键脚本法律文本清洗最麻烦的是格式不统一有的法条带“第X条”编号有的带“X”有的案例里混着 HTML 标签和页眉页脚。我通常写三段式清洗脚本第一段做正则归一化第二段做段落级去重第三段做敏感信息替换。import re import hashlib def normalize_legal_text(text): # 统一全角半角去掉多余空白 text text.replace(\u3000, ).replace(\xa0, ) text re.sub(r\s, , text) # 统一法条编号格式第X条 - 第X条保留中文数字 text re.sub(r第\s*([一二三四五六七八九十百千])\s*条, r第\1条, text) # 去掉页眉页脚常见模式 text re.sub(r—\s*\d\s*—, , text) text re.sub(r第\s*\d\s*页, , text) return text.strip() def dedup_by_hash(paragraphs, threshold0.95): seen set() result [] for p in paragraphs: if len(p) 20: # 太短的段落直接丢弃 continue h hashlib.md5(p.encode(utf-8)).hexdigest() if h not in seen: seen.add(h) result.append(p) return result def mask_pii(text): # 替换身份证号、手机号、具体人名简单规则 text re.sub(r\d{17}[\dXx], [身份证], text) text re.sub(r1[3-9]\d{9}, [手机号], text) text re.sub(r[\u4e00-\u9fa5]{2,4}(?|。|、|), [当事人], text) return text这段脚本的逻辑是先做字符级归一化把法律文本里常见的全角空格、页眉页脚干掉然后用 MD5 做段落级精确去重比模糊去重快且够用最后用正则把身份证、手机号和简单人名替换掉。参数上threshold我实际没用到模糊匹配因为法律文本重复往往是完全一致的精确哈希足够len(p) 20这个阈值可以按需调整法条标题有时很短但正文段落一般不会低于 20 字。2.3 切分策略按“条”切还是按“段”切法律文本的切分不能按固定 token 数硬切否则会把一个完整的法条切成两半检索时召回的都是残片。我的做法是法条原文按“第X条”为边界切每条作为一个 chunk司法解释按“第X条”或“第X款”切案例按“本院认为”“裁判要旨”“判决结果”三个段落切。每个 chunk 控制在 256512 token超过就再按句子边界切。这样切出来的 chunk 语义完整做 RAG 时召回精度明显高于固定窗口切分。3. 继续预训练与指令微调让模型学会“法言法语”3.1 继续预训练不是必须但做了效果更稳很多人问“我直接拿通用模型做 RAG 行不行”行但你会发现模型对法律术语的理解总是差一层。比如“善意取得”在通用语料里可能被理解成“好心得到”在法律语境里是“不知情且无重大过失地取得”。继续预训练就是让模型在大量法律文本上再跑一遍语言建模任务把这种术语的向量表示拉近。我一般用 LoRA 做继续预训练秩设 64学习率 1e-5跑 12 个 epoch。数据量在 12GB 法律文本时单卡 24G 显存够用。# 以 LLaMA-Factory 为例的继续预训练配置片段 # 保存为 legal_pt.yaml model_name_or_path: /path/to/base_model stage: pt do_train: true dataset: legal_corpus template: default finetuning_type: lora lora_rank: 64 lora_target: all learning_rate: 1e-5 num_train_epochs: 2 per_device_train_batch_size: 4 gradient_accumulation_steps: 8 cutoff_len: 1024 output_dir: /path/to/output_pt这段配置的关键参数stage: pt表示继续预训练lora_rank: 64是法律领域常用的秩再低会欠拟合再高容易过拟合cutoff_len: 1024是因为法律文本段落较长512 会截断太多gradient_accumulation_steps: 8配合 batch size 4等效 batch 32单卡 24G 能跑。跑完继续预训练后模型对“标的物”“不可抗力”“除斥期间”这类词的敏感度会明显提升。3.2 指令微调构造“法条-事实-结论”三元组指令微调的数据质量比数量重要得多。我构造数据的方式是从指导性案例里抽“裁判要旨”作为结论从“本院认为”里抽推理过程从判决书开头抽案件事实然后拼成 instruction-input-output 格式。比如{ instruction: 根据以下案件事实分析被告是否构成违约并引用相关法条。, input: 原告与被告签订买卖合同约定被告于2023年6月1日前交付货物。被告直至2023年7月15日仍未交付原告催告后被告仍未履行。, output: 被告构成违约。根据《中华人民共和国民法典》第五百七十七条当事人一方不履行合同义务或者履行合同义务不符合约定的应当承担继续履行、采取补救措施或者赔偿损失等违约责任。本案中被告未按约定时间交付货物经催告后仍未履行已构成根本违约。 }这种三元组我一般准备 500010000 条覆盖合同、侵权、婚姻家事、劳动争议等高频领域。微调时学习率设 2e-5比继续预训练稍大epoch 控制在 3 以内多了会过拟合到具体案例上。评估时不要只看 loss要拿 50 条真实法律咨询问题做人工打分看“法条引用是否正确”“推理是否连贯”“结论是否明确”三个维度。3.3 LoRA 合并与量化部署的取舍微调完的 LoRA 权重可以合并回基座模型也可以动态加载。合并的好处是推理时不用额外加载适配器速度快坏处是失去灵活性想换任务得重新合并。我一般做法是开发阶段动态加载 LoRA方便对比不同版本上线前合并并做 4-bit 量化用 GPTQ 或 AWQ显存占用能降到原来的 1/3 左右。量化后法律术语的生成质量会有轻微下降但实测在 7B 模型上4-bit 量化的法条引用准确率只掉 23 个百分点可以接受。4. 检索增强生成法律 RAG 的召回与重排怎么调4.1 法律向量库的构建与索引参数法律 RAG 的核心不是生成是召回。召回不准生成再强也是胡扯。我一般用 BGE-large-zh 或 text-embedding-3-large 做向量化向量维度 1024 或 1536。索引用 FAISS 的 IVF-Flatnlist设 1024nprobe设 32。为什么不用 HNSW因为法律库更新频繁IVF 支持动态增删更方便。构建索引时每个 chunk 除了向量还要存元数据法条编号、生效日期、效力级别、所属领域。检索时先按元数据过滤再做向量相似度搜索能大幅减少跨领域误召回。import faiss import numpy as np from sentence_transformers import SentenceTransformer model SentenceTransformer(BAAI/bge-large-zh-v1.5) dim 1024 index faiss.IndexIVFFlat(faiss.IndexFlatIP(dim), dim, 1024) index.nprobe 32 # 假设 chunks 是法律文本列表metas 是对应元数据 embeddings model.encode(chunks, normalize_embeddingsTrue) index.train(embeddings) index.add(embeddings) def search(query, top_k10, domain_filterNone): q_vec model.encode([query], normalize_embeddingsTrue) scores, ids index.search(q_vec, top_k * 3) # 多召回一些用于重排 results [] for score, idx in zip(scores[0], ids[0]): if idx -1: continue meta metas[idx] if domain_filter and meta[domain] ! domain_filter: continue results.append((chunks[idx], meta, score)) if len(results) top_k: break return results这段代码的关键点normalize_embeddingsTrue让内积等价于余弦相似度top_k * 3是多召回给重排留空间domain_filter是元数据过滤比如问劳动法问题就只召回劳动法领域的 chunk。参数上nprobe32是召回率和速度的平衡点再高延迟明显增加再低召回率掉得快。4.2 重排模型的选择与阈值设定向量召回是粗筛重排是精排。法律场景我强烈建议加重排因为法条之间语义相似度很高向量模型容易把“第X条”和“第Y条”排混。重排用 BGE-reranker-large 或 Cohere rerank输入是 query 和候选 chunk 对输出相关性分数。阈值我一般设 0.30.5低于阈值的直接丢弃不送进生成模型。这样能减少幻觉因为模型只看到真正相关的法条。注意重排模型比向量模型慢一个数量级如果候选有 30 条重排耗时可能 200500ms。线上服务要控制候选数量我一般向量召回 20 条重排后保留 5 条。4.3 生成阶段的提示词模板与引用约束法律 RAG 的提示词不能太自由必须约束模型“只根据给定法条回答并标注引用来源”。我用的模板大致是你是一名法律助手。请根据以下法条和司法解释回答问题。 要求 1. 只使用提供的法条内容不要编造。 2. 如果提供的法条不足以回答请明确说“根据现有法条无法确定”。 3. 回答中必须标注引用的法条编号。 相关法条 {context} 问题{question} 回答这个模板的关键是第 2 条“无法确定”的兜底能大幅降低幻觉率。实测在 100 条法律咨询测试集上加了兜底约束后编造法条的比率从 18% 降到 4% 左右。5. 避坑与排查法律大模型落地时最容易翻车的五件事5.1 法条时效性错乱模型引用已废止的旧法现象问“合同法第 107 条怎么规定的”模型答得头头是道但《合同法》已经废止应该引用《民法典》第 577 条。原因训练语料里混入了大量旧法文本且没有标注生效/废止日期。解决在元数据里加effective_date和abolished_date字段检索时过滤掉已废止的法条微调数据里显式加入“旧法已废止现行法为XX”的对比样本。5.2 跨领域误召回问婚姻问题召回公司法条文现象用户问“离婚时房产怎么分割”检索结果里混进了“公司股权转让”的条文。原因向量模型对“财产分割”和“股权分割”的语义区分不够且没有领域过滤。解决给每个 chunk 打上领域标签婚姻、合同、侵权、劳动等检索时先按领域过滤如果用户问题领域不明确先用一个小分类模型判断领域再检索。5.3 长法条截断一个法条被切成多个 chunk 导致语义不全现象模型回答时只引用了法条的前半句漏掉了“但书”条款结论完全相反。原因切分时按固定 token 切把一个完整法条切成了两段检索只召回了前半段。解决按“第X条”为边界切分保证每个 chunk 是一个完整法条如果法条超过 512 token按“款”切但要在元数据里记录所属法条编号检索时把同一法条的所有款一起召回。5.4 量化后精度骤降4-bit 模型开始胡编法条编号现象合并 LoRA 并做 4-bit 量化后模型引用的法条编号开始出现“第 1234 条”这种不存在的编号。原因量化损失对数字和编号的生成影响最大法律场景对编号精度要求极高。解决量化时对 embedding 层和 lm_head 层保持 FP16只量化中间层或者用 AWQ 替代 GPTQ实测 AWQ 对编号的保真度更好。如果还不行退回 8-bit 量化。5.5 并发下的检索延迟重排模型成为瓶颈现象单条测试时响应 1 秒压测到 10 并发时延迟飙到 5 秒以上。原因重排模型是 cross-encoder计算量随候选数线性增长且没有做批处理。解决把重排候选数从 20 降到 10用 ONNX Runtime 或 TensorRT 加速重排模型对重排结果做缓存相同 query 在短时间内直接返回缓存结果。6. 进阶技巧用“法条引用一致性”做自动化评估法律大模型最难的是评估人工打分太慢BLEU/ROUGE 又测不出法条引用对不对。我后来用了一个取巧的办法构建一个“法条引用一致性”指标。具体做法是对每个测试问题先用检索模块拿到标准法条集合然后让模型生成回答再用正则从回答里抽出所有“第X条”引用计算模型引用的法条与标准法条的 Jaccard 相似度。这个指标不需要人工标注且和人工评分相关性很高。import re def extract_article_refs(text): # 抽取“第X条”引用支持中文数字和阿拉伯数字 pattern r第\s*([一二三四五六七八九十百千\d])\s*条 return set(re.findall(pattern, text)) def citation_consistency(model_answer, gold_articles): pred_refs extract_article_refs(model_answer) gold_refs set(gold_articles) if not pred_refs and not gold_refs: return 1.0 if not pred_refs or not gold_refs: return 0.0 intersection pred_refs gold_refs union pred_refs | gold_refs return len(intersection) / len(union)这个脚本的逻辑很简单从模型回答里抽法条编号和标准答案的法条编号做 Jaccard。参数上正则要覆盖中文数字和阿拉伯数字因为模型有时写“第577条”有时写“第五百七十七条”。实测这个指标在 200 条测试集上和人工评分的 Spearman 相关系数在 0.82 左右足够用来做版本对比。我一般用它来快速筛选微调版本每次微调后跑一遍测试集一致性低于 0.6 的版本直接淘汰不再浪费人力做人工评估。还有一个习惯每次上线新版本前我会手动构造 20 条“边界问题”比如“本法施行前的行为是否适用”“但书条款是否优先于一般条款”专门看模型会不会掉进陷阱。这些边界问题不放进自动化测试集因为太容易被过拟合但每次人工过一遍能发现很多自动化指标看不出的问题。法律场景容错率低宁可上线慢一点也别让模型在真实咨询里给出错误法条。希望帮到你。本文还有配套的精品资源点击获取