中文法律大模型落地实战:从zip包到法条问答的最小闭环

发布时间:2026/10/5 11:18:50
中文法律大模型落地实战:从zip包到法条问答的最小闭环 简介这份资源面向希望将大语言模型落地到中文法律场景的开发者与算法学习者围绕法律知识问答、法条理解与指令微调等任务提供了一套可运行的工程实践材料。压缩包共42个文件约3.41MB以Python脚本为主体配合JSON指令与配置数据、Shell训练与推理脚本以及少量图片、说明文档和许可证文件覆盖数据处理、模型微调、推理评估到Web界面演示的完整链路。目录中可见法律词表、罪名数据、指令训练与推理样例、提示词模板及LoRA权重占位等模块便于读者按需替换数据、复现训练流程并搭建本地问答演示。已有120人学习关注适合具备一定Python与深度学习基础、希望快速理解法律大模型工程结构并动手实验的读者参考。1. 中文法律大模型落地从一份 zip 到能回答法条的最小闭环你拿到一个叫《AI大模型应用》-基于中文法律知识的大语言模型.zip 的包第一反应大概率是解压之后怎么让它跑起来而不是先去看它用了什么架构。这个标题背后其实是一类很典型的工程需求——把通用大模型用中文法律语料做领域适配让它能回答法条、解释罪名、辅助合同审查。它解决的不是“模型会不会说话”而是“模型说的对不对、引的法条是不是现行有效”。适合两类人一是想入门 AI大模型应用开发、拿法律场景练手的工程师二是法务或法律科技产品团队里需要快速验证本地部署大语言模型可行性的技术负责人。中文法律知识这个领域有个特点语料相对封闭、术语密度高、对准确性容忍度极低所以它既是领域大模型的好试验田也是翻车重灾区。下面按“先搞懂它是什么、再动手跑通、最后避开坑”的顺序拆开讲。2. 中文法律知识喂给大模型为什么不能直接拿通用模型改改就用2.1 法律语料的三个特殊性决定了微调策略通用大模型在中文法律场景下直接问答最典型的问题是“法条张冠李戴”。它知道“故意杀人”大概判什么刑但会把刑法第232条和第234条的内容混在一起。原因不复杂法律文本的语义空间和日常语言差异很大同一个词在法条里有严格定义比如“故意”在刑法里和日常口语里的“故意”不是一回事。中文法律知识作为训练信号有三个特殊性。第一是术语的封闭性法律概念之间有明确的上下位和并列关系模型必须学会这套体系而不是靠常识猜。第二是时效性法条会修订模型不能把旧法当现行法用。第三是引用格式的刚性回答里出现“根据《中华人民共和国刑法》第X条”时条号必须准确不能编。这三点决定了拿通用模型直接 prompt 工程很难稳定必须做领域适配。常见做法是继续预训练加指令微调两阶段先用法律语料做领域继续预训练让模型熟悉法律文本的分布再用问答对做指令微调教它按法律人的方式输出。2.2 从 zip 到可运行环境准备与依赖确认拿到包之后别急着 pip install。先做三件事看目录结构、看依赖文件、看模型权重格式。法律大模型常见的权重格式有 HuggingFace 的 safetensors、PyTorch 的 bin以及量化后的 gguf。不同格式对应不同的加载方式选错了后面全白搭。下面是一段环境确认的脚本我一般会先跑这个再动手。# 查看压缩包内容结构不直接解压先看文件清单 unzip -l 基于中文法律知识的大语言模型.zip | head -50 # 解压到独立目录避免污染当前工作区 mkdir -p ./law_llm unzip 基于中文法律知识的大语言模型.zip -d ./law_llm # 查看依赖文件确认是 requirements.txt 还是 environment.yml ls ./law_llm cat ./law_llm/requirements.txt 2/dev/null || cat ./law_llm/environment.yml 2/dev/null # 确认权重文件格式和大小 find ./law_llm -name *.safetensors -o -name *.bin -o -name *.gguf | xargs ls -lh这段脚本的逻辑是先侦察再动手。unzip -l不落盘避免解压出一堆没用的中间文件。解压到独立目录是为了后面清理方便。看依赖文件时如果只有 requirements.txt说明是 Python 生态大概率用 transformers 加载如果是 environment.yml可能是 conda 环境需要先建环境。权重文件那一步最关键safetensors 和 bin 通常配合 transformers 用gguf 则是 llama.cpp 或 ollama 的格式加载方式完全不同。参数上注意如果权重文件被切成了多个分片比如 model-00001-of-00003.safetensors加载时指向目录即可不要手动拼分片。2.3 最小推理闭环加载模型并问出第一个法律问题环境确认完下一步是跑通一次推理。不要一上来就搞微调先确认模型能加载、能生成、生成内容不崩。下面这段代码用 transformers 加载本地模型并做一次法律问答适合 safetensors 或 bin 格式。from transformers import AutoTokenizer, AutoModelForCausalLM import torch # 模型路径指向解压后的目录不要指向单个权重文件 model_path ./law_llm # 加载分词器trust_remote_code 视模型是否包含自定义代码而定 tokenizer AutoTokenizer.from_pretrained(model_path, trust_remote_codeTrue) # 加载模型根据显存选择精度fp16 需要约 2 倍参数量的显存 model AutoModelForCausalLM.from_pretrained( model_path, torch_dtypetorch.float16, # 显存不够可改 torch.int8 或使用量化加载 device_mapauto, # 自动分配到 GPU 或 CPU trust_remote_codeTrue ) # 构造法律领域 prompt指令要具体避免开放式提问 prompt 请根据《中华人民共和国刑法》回答故意伤害致人轻伤的一般如何量刑 inputs tokenizer(prompt, return_tensorspt).to(model.device) # 生成参数max_new_tokens 控制回答长度temperature 控制随机性 with torch.no_grad(): outputs model.generate( **inputs, max_new_tokens256, temperature0.3, # 法律场景要低温度减少胡编 do_sampleTrue, top_p0.9, repetition_penalty1.1 ) answer tokenizer.decode(outputs[0], skip_special_tokensTrue) print(answer)逻辑说明先加载分词器和模型device_mapauto让 transformers 自动决定放 GPU 还是 CPU省去手动分配。prompt 构造上法律问题要带法条名称这样模型更容易定位到正确知识区域。生成参数里 temperature 设 0.3 是血泪经验法律问答温度高了会开始“编法条”0.3 左右能在准确性和表达流畅之间取平衡。max_new_tokens 设 256 是因为法律回答通常不会太长设太大反而增加胡编概率。如果显存不够把 torch_dtype 改成 torch.int8 或者用 bitsandbytes 做 4bit 量化但量化后法律术语的准确性会下降需要自己测。跑通这一步说明模型本身可用接下来才是领域适配的事。3. 法律知识注入的实操路径继续预训练与指令微调怎么选3.1 继续预训练让模型先“读法条”继续预训练的目标是让模型适应法律文本的用词和句式。这一步不需要标注数据只需要大量法律文本比如法律法规全文、裁判文书、法学教材。数据格式通常是纯文本每行一段或者用特殊 token 分隔文档。下面是一个数据预处理脚本把原始法律文本转成训练用的 token 序列。from transformers import AutoTokenizer from datasets import Dataset import json tokenizer AutoTokenizer.from_pretrained(./law_llm) # 假设原始数据是 jsonl每行一个 {text: ...} def load_corpus(path): texts [] with open(path, r, encodingutf-8) as f: for line in f: item json.loads(line) texts.append(item[text]) return texts # 对文本做 tokenize截断到模型最大长度 def tokenize_fn(example): return tokenizer( example[text], truncationTrue, max_length1024, # 法律文本较长可适当加大但受显存限制 paddingFalse ) raw_texts load_corpus(./data/law_corpus.jsonl) dataset Dataset.from_dict({text: raw_texts}) tokenized dataset.map(tokenize_fn, batchedTrue, remove_columns[text]) # 保存 tokenized 数据集供 Trainer 使用 tokenized.save_to_disk(./data/tokenized_law)逻辑说明继续预训练的数据质量比数量重要。法律语料里裁判文书有大量重复模板需要去重法律法规要确认是现行有效版本。max_length 设 1024 是折中法律条文经常超过 512但设太大显存吃不消。tokenize 时不 padding交给 DataCollator 动态 padding效率更高。参数上注意如果模型本身支持 2048 或更长上下文可以适当加大 max_length但继续预训练的计算量会线性增长。这一步跑完模型对法律文本的困惑度会下降但还不具备问答能力需要下一步。3.2 指令微调教模型按法律人的方式回答指令微调的数据是问答对格式通常是 instruction、input、output 三列。法律场景的指令数据可以来自法考题目、法律咨询问答、合同审查示例。下面是一个指令微调的 Trainer 配置基于 transformers 的 Trainer API。from transformers import TrainingArguments, Trainer, DataCollatorForSeq2Seq # 假设已经有一个 instruction 数据集字段为 instruction/input/output def format_instruction(example): # 构造统一的 prompt 模板法律场景建议带法条引用要求 prompt f### 指令{example[instruction]}\n### 输入{example[input]}\n### 回答 response example[output] # 对 prompt 和 response 分别 tokenizelabel 只对 response 计算损失 prompt_ids tokenizer(prompt, add_special_tokensFalse)[input_ids] response_ids tokenizer(response, add_special_tokensFalse)[input_ids] input_ids prompt_ids response_ids [tokenizer.eos_token_id] labels [-100] * len(prompt_ids) response_ids [tokenizer.eos_token_id] return {input_ids: input_ids, labels: labels} train_dataset raw_dataset.map(format_instruction, remove_columnsraw_dataset.column_names) training_args TrainingArguments( output_dir./law_llm_finetuned, per_device_train_batch_size2, # 显存小就调小配合梯度累积 gradient_accumulation_steps8, # 等效 batch size 2 * 8 16 learning_rate2e-5, # 指令微调学习率通常比预训练小 num_train_epochs3, logging_steps10, save_strategyepoch, fp16True, # 混合精度训练省显存 warmup_ratio0.03, lr_scheduler_typecosine ) trainer Trainer( modelmodel, argstraining_args, train_datasettrain_dataset, data_collatorDataCollatorForSeq2Seq(tokenizer, paddingTrue) ) trainer.train()逻辑说明prompt 模板里加了“### 指令/输入/回答”的分隔符是为了让模型学会区分任务描述和回答内容。labels 里把 prompt 部分设为 -100意思是只对回答部分计算损失这样模型学的是“怎么答”而不是“怎么复述问题”。学习率 2e-5 是指令微调的常见起点太大容易灾难性遗忘太小收敛慢。gradient_accumulation_steps 是显存不够时的后悔药等效增大 batch size 但计算是串行的。warmup_ratio 设 0.03 让学习率慢慢升上去避免一开始就大步更新破坏预训练知识。训练完成后用第 2 章的最小推理脚本加载新权重对比微调前后的回答重点看条号引用是否更准。3.3 法律场景的评测别只看 loss要看条号对不对微调完 loss 下降不代表模型变好。法律场景必须做人工评测重点看三个指标法条引用准确率、回答完整性、是否存在编造。下面是一个简单的评测脚本框架用规则匹配检查回答里是否包含正确的法条编号。import re # 测试集每条包含问题、标准法条编号 test_cases [ {question: 故意伤害致人轻伤如何量刑, expected_article: 第二百三十四条}, {question: 盗窃数额较大如何处罚, expected_article: 第二百六十四条}, ] def extract_articles(text): # 匹配“第X条”格式中文数字 pattern r第[一二三四五六七八九十百零]条 return re.findall(pattern, text) correct 0 for case in test_cases: answer ask_model(case[question]) # 调用推理函数 articles extract_articles(answer) if case[expected_article] in articles: correct 1 else: print(f错误{case[question]} - 期望 {case[expected_article]}实际 {articles}) print(f条号准确率{correct}/{len(test_cases)})逻辑说明这个脚本用正则从回答里抽“第X条”和标准答案比对。法律条号是中文数字正则要覆盖“百”“零”等。实际评测时测试集要覆盖常见罪名和法条至少几十条才有统计意义。如果条号准确率低于 80%说明微调数据里法条引用格式不统一或者模型没学会定位知识。这时候要回去检查训练数据把法条编号统一成“第X条”格式不要混用“第X条”和“X条”。参数上注意正则匹配是粗筛最终还要人工看回答的推理逻辑是否成立因为模型可能引对了条号但解释错了内容。4. 避坑与排查中文法律大模型落地时最容易翻车的五件事4.1 现象模型回答里出现不存在的法条编号原因指令微调数据里混入了模型自己生成的伪法条或者训练时 temperature 太高导致模型学会“编条号”。解决清洗训练数据所有法条引用必须来自权威文本推理时 temperature 降到 0.1 到 0.3在 prompt 里加一句“如果无法确定法条编号请回答‘不确定’”给模型一个退路。4.2 现象微调后模型通用能力断崖式下降原因学习率太大或者训练轮数太多导致灾难性遗忘。解决学习率控制在 1e-5 到 3e-5 之间epoch 不超过 3在微调数据里混入 10% 到 20% 的通用指令数据让模型保持通用能力用 LoRA 做参数高效微调只更新部分参数对原模型影响更小。4.3 现象显存不够加载模型直接 OOM原因法律大模型参数量通常 7B 起步fp16 需要约 14GB 显存加上 tokenizer 和中间激活16GB 卡很紧张。解决用 4bit 或 8bit 量化加载bitsandbytes 的 load_in_4bit 能把显存降到 6GB 左右或者用 CPU 加内存跑但推理速度会慢到无法交互32G 内存能装量化后的模型但只适合验证不适合生产。4.4 现象回答里法条内容对但条号错或者条号对但内容旧原因训练语料里混入了旧版法条或者模型把不同法条的内容混在一起。解决训练前用法条数据库做一次版本校验只保留现行有效文本在指令数据里显式加入“根据现行《XX法》”的限定词评测时专门测修订过的法条看模型是否引用旧版。4.5 现象模型对同一个法律问题每次回答不一样原因temperature 和 top_p 设得太高采样随机性大。解决法律问答场景 temperature 设 0.1 到 0.3top_p 设 0.9 以下或者直接用 greedy searchdo_sampleFalse如果必须用采样固定随机种子保证可复现。5. 进阶技巧用检索增强把法条准确率再拉一截微调能教会模型法律语言风格但没法保证它记住所有法条。更稳的做法是检索增强生成也就是 RAG。思路是先把法律法规全文切成片段存进向量库用户提问时先检索最相关的法条片段再把片段和问题一起塞进 prompt让模型基于检索到的法条回答。这样模型不需要记住所有条号只需要学会“根据给定法条回答问题”。下面是一个最小 RAG 流程的代码骨架。from sentence_transformers import SentenceTransformer import numpy as np # 1. 法条库向量化 encoder SentenceTransformer(shibing624/text2vec-base-chinese) law_articles [ {id: 刑法第234条, text: 故意伤害他人身体的处三年以下有期徒刑、拘役或者管制。犯前款罪致人重伤的处三年以上十年以下有期徒刑……}, {id: 刑法第264条, text: 盗窃公私财物数额较大的或者多次盗窃、入户盗窃、携带凶器盗窃、扒窃的处三年以下有期徒刑、拘役或者管制并处或者单处罚金……}, ] article_texts [a[text] for a in law_articles] article_embeddings encoder.encode(article_texts, normalize_embeddingsTrue) # 2. 检索把问题向量和法条向量做余弦相似度 def retrieve(query, top_k2): query_emb encoder.encode([query], normalize_embeddingsTrue) scores np.dot(article_embeddings, query_emb.T).flatten() top_indices np.argsort(scores)[::-1][:top_k] return [law_articles[i] for i in top_indices] # 3. 构造增强 prompt def build_rag_prompt(query): retrieved retrieve(query) context \n.join([f{a[id]}{a[text]} for a in retrieved]) prompt f根据以下法条回答问题不要编造法条\n{context}\n\n问题{query}\n回答 return prompt # 4. 把增强 prompt 喂给模型生成回答 query 故意伤害致人轻伤怎么判 rag_prompt build_rag_prompt(query) # answer model.generate(rag_prompt) # 复用第 2 章的生成逻辑 print(rag_prompt)逻辑说明向量检索用 text2vec 这类中文模型把法条和问题映射到同一向量空间余弦相似度越高越相关。top_k 设 2 到 3 是平衡检索太多会超出模型上下文太少可能漏掉关键法条。prompt 里明确写“不要编造法条”是给模型一个强约束。RAG 的好处是法条更新时只需要更新向量库不用重新微调模型。参数上注意检索的相似度阈值要调太低会召回不相关法条太高会漏召回。我一般会先跑一批测试问题看检索结果里正确法条排在第几位如果经常排到第三以后就要换更好的 embedding 模型或者调整切分粒度。RAG 和微调不冲突可以叠加微调让模型学会法律表达RAG 保证法条准确。实际落地时如果算力有限优先做 RAG因为微调成本高且更新慢。如果法条库不大甚至可以不微调直接用通用模型加 RAG效果也能接受。我自己的习惯是先跑通 RAG 验证需求再决定要不要投入微调。这个方向值不值得做取决于你的场景对法条准确率的要求如果只是辅助检索RAG 够了如果要生成法律文书微调加 RAG 才稳。希望帮到你。本文还有配套的精品资源点击获取