
最近在尝试将大模型应用到垂直领域时发现一个普遍痛点通用大模型在专业法律问题上表现不佳要么“一本正经地胡说八道”要么对量刑细节一问三不知。为了构建一个真正懂中国刑法的智能助手我决定动手“手撕”一个专精于刑法领域的模型。本文将完整记录基于 Qwen 大模型结合 RAG检索增强生成和 LoRA低秩适应微调技术实现“罪名识别”、“刑期预测”和“司法解释生成”三大核心功能的实战过程。从环境搭建、数据处理、模型训练到最终部署每一步都附带可运行的代码和踩坑记录无论你是想入门大模型应用还是已有基础想深入垂直领域微调都能从中获得一套可直接复用的解决方案。1. 背景与核心概念在深入代码之前我们需要厘清几个核心技术的概念及其在本项目中的作用这有助于理解后续每一步设计的“为什么”。1.1 为什么需要“刑法大模型”通用大语言模型LLM如 ChatGPT、Qwen 等在广泛的知识问答上表现卓越但其知识来源于训练时的通用语料。对于高度专业化、严谨且动态更新的中国法律体系特别是刑法通用模型存在明显不足知识滞后性法律条文、司法解释会更新而大模型的训练数据存在时间差。准确性风险模型可能基于“常识”或外域法律知识进行推理导致对中国刑法具体条款的引用错误或量刑建议偏差。缺乏专业推理链刑法适用需要严谨的三段论推理大前提-法律条文小前提-案件事实结论-判决通用模型难以自发构建这种结构。因此构建一个垂直领域的“刑法大模型”旨在将专业的法律知识注入模型使其回答更精准、可靠、符合中国司法实践。1.2 技术栈拆解Qwen、RAG 与 LoRA本项目采用“基座模型 知识增强 参数高效微调”的三层架构。Qwen通义千问作为基座大模型。我们选择 Qwen 系列如 Qwen2.5-7B-Instruct是因为其优秀的开源协议、强大的中文理解能力、适中的参数量便于本地微调和推理以及活跃的社区支持。它为我们提供了强大的语言理解和生成基础能力。RAG检索增强生成解决知识“外挂”与实时性问题。RAG 的核心思想不是改变模型本身的知识而是在模型回答问题时先从外部知识库如刑法法条、司法解释数据库中检索出最相关的文档片段然后将这些片段作为上下文和问题一起交给模型生成答案。这确保了答案有据可依并能方便地更新知识库以保持模型知识的时效性。LoRALow-Rank Adaptation解决模型“内化”专业能力与微调成本问题。仅靠 RAG模型可能不擅长理解法律问题的提问方式或组织法律语言的风格。LoRA 是一种参数高效微调技术它通过为模型权重添加低秩分解的适配器来学习特定任务如刑法问答的“偏好”而无需全量微调所有参数。这大大降低了计算和存储成本使我们能在消费级 GPU 上对 Qwen 这样的模型进行有效微调让其更“懂行”。简单比喻Qwen 是一个博学的“通用大学生”RAG 为他配备了一个随时可查的“刑法电子图书馆”而 LoRA 微调则是送他去法学院进修培养其法律思维和表达习惯。三者结合才能打造出专业的“法律AI助手”。2. 环境准备与版本说明本实战教程在以下环境中进行请确保你的环境尽可能一致以避免不必要的依赖冲突。2.1 硬件与基础软件操作系统Ubuntu 22.04 LTS (Windows 用户可使用 WSL2 或 Docker 获得类似体验)GPUNVIDIA RTX 4090 (24GB VRAM)至关重要微调需要大显存CUDA12.1Python3.10包管理Conda 或 venv2.2 核心Python库及版本创建一个新的虚拟环境并安装依赖conda create -n law_llm python3.10 -y conda activate law_llm pip install torch2.1.2 torchvision torchaudio --index-url https://download.pytorch.org/whl/cu121安装大模型相关库pip install transformers4.38.2 pip install accelerate0.27.2 pip install peft0.10.0 # LoRA 微调库 pip install datasets2.17.0 pip install trl0.7.11 # Transformer Reinforcement Learning用于SFT训练安装 RAG 相关库这里以 LangChain 和 Chroma 为例pip install langchain0.1.12 pip install langchain-community0.0.20 pip install chromadb0.4.22 # 轻量级向量数据库 pip install sentence-transformers2.2.2 # 用于文本嵌入 pip install pypdf # 用于解析PDF格式的法律文档2.3 项目结构建议按如下结构组织你的项目目录保持清晰criminal_law_llm/ ├── data/ # 存放原始数据和处理后的数据 │ ├── raw/ # 原始法律条文PDF/TXT │ ├── processed/ # 处理后的文本块 │ └── dataset/ # 微调用的指令数据集 ├── knowledge_base/ # RAG知识库ChromaDB存储 ├── scripts/ # 各类执行脚本 │ ├── process_data.py # 数据处理脚本 │ ├── build_kb.py # 构建知识库脚本 │ ├── train_lora.py # LoRA微调脚本 │ └── inference.py # 推理与问答脚本 ├── models/ # 存放下载的基座模型和训练后的LoRA权重 ├── config/ # 配置文件 └── requirements.txt # 项目依赖3. 核心流程与原理拆解3.1 RAG 流程如何让模型“有据可查”RAG 的工作流程可以分解为“索引”和“检索-生成”两个阶段。索引阶段离线文档加载与分割将《刑法》全文、重要的司法解释如关于盗窃、诈骗罪的司法解释等非结构化文本加载进来并按语义如按“条”分割成大小适中的文本块Chunk。文本向量化使用嵌入模型如BAAI/bge-large-zh将每个文本块转换为一个高维向量Embedding。这个向量表征了文本的语义。向量存储将这些向量及其对应的原始文本存储到向量数据库如 ChromaDB中。检索-生成阶段在线问题向量化当用户提问“盗窃数额巨大如何量刑”时使用同样的嵌入模型将问题转换为向量。相似度检索在向量数据库中计算问题向量与所有文本块向量的相似度如余弦相似度找出最相关的 K 个文本块例如关于刑法第264条盗窃罪及量刑幅度的条文。提示词构建将检索到的相关文本块作为“参考上下文”与原始问题一起构造成一个详细的提示词Prompt交给大模型。增强生成大模型基于这个包含了精准法律依据的提示词生成最终答案。3.2 LoRA 微调原理如何让模型“更懂行”全量微调一个 70B 的模型需要巨大的算力。LoRA 的巧妙之处在于它假设模型在适应新任务时权重的变化是低秩的。具体实现冻结原模型保持预训练好的 Qwen 模型所有参数不变。注入可训练层在原始模型的一些关键权重矩阵如 Attention 模块的 Q, K, V 投影矩阵旁并行添加两个低秩矩阵 A 和 B。其中 A 初始化为随机高斯分布B 初始化为零。设原始权重为 W更新后的权重为 W W BA。这里 B 和 A 的秩 r 远小于原始矩阵的维度。仅训练新增参数在微调过程中只训练这些新增的低秩矩阵 A 和 B 的参数而原始参数 W 被冻结。由于 (B, A) 的参数总量极少可能只有原模型的 0.1%训练变得非常高效。推理合并训练完成后可以将 BA 加到 W 上得到一个合并后的模型推理时无需额外计算几乎没有速度损失。在本项目中我们使用 LoRA 让 Qwen 模型学会理解法律领域的专业术语和提问方式。按照“法条引用 - 情节分析 - 量刑结论”的结构组织答案。生成更严谨、更符合法律文书的语言风格。4. 完整实战案例接下来我们一步步实现整个系统。4.1 数据准备与处理法律数据的质量直接决定模型的上限。我们需要准备两种数据1. 用于 RAG 的知识库文档2. 用于 LoRA 微调的指令数据集。步骤一收集知识库文档将《中华人民共和国刑法》全文、以及你关注的罪名相关的司法解释保存为data/raw/刑法.txt和data/raw/司法解释.pdf等格式。步骤二处理知识库文档scripts/process_data.py# scripts/process_data.py import re from langchain.text_splitter import RecursiveCharacterTextSplitter from langchain.document_loaders import TextLoader, PyPDFLoader def load_and_split_documents(): # 1. 加载文档 text_loader TextLoader(./data/raw/刑法.txt, encodingutf-8) pdf_loader PyPDFLoader(./data/raw/司法解释.pdf) documents text_loader.load() pdf_loader.load() # 2. 分割文本 # 法律条文结构清晰可以尝试按“第xxx条”分割这里用通用分割器示例 text_splitter RecursiveCharacterTextSplitter( chunk_size500, # 每个块约500字符 chunk_overlap50, # 块间重叠50字符保持上下文 separators[\n\n, \n, 。, , , , ] # 中文分隔符 ) splits text_splitter.split_documents(documents) # 3. 保存处理后的文本块 with open(./data/processed/law_chunks.txt, w, encodingutf-8) as f: for i, chunk in enumerate(splits): f.write(fChunk {i}:\n{chunk.page_content}\n{-*50}\n) print(f共生成 {len(splits)} 个文本块。) return splits if __name__ __main__: law_chunks load_and_split_documents()步骤三构建指令数据集SFT Data我们需要一个(instruction, input, output)格式的数据集来微调模型。可以手动构造、利用现有法律QA数据集或使用大模型辅助生成。 示例data/dataset/train.jsonl的一条数据{ instruction: 请根据中华人民共和国刑法对以下案件情节进行罪名识别和刑期预测。, input: 张三在公共汽车上趁乘客李四不备扒窃其钱包一个内有现金人民币3000元。, output: 【罪名识别】张三的行为涉嫌盗窃罪。\n【法条依据】根据《中华人民共和国刑法》第二百六十四条盗窃公私财物数额较大的处三年以下有期徒刑、拘役或者管制并处或者单处罚金。\n【情节分析】本案中张三在公共交通工具上扒窃且盗窃数额为3000元根据相关司法解释在公共交通工具上盗窃和盗窃数额较大均构成盗窃罪的加重情节。\n【刑期预测】综合考虑其行为属于盗窃罪且具有在公共交通工具上扒窃的情节量刑起点可能在一年左右有期徒刑并处罚金。具体刑期需由法院根据全部案情判定。 }你需要准备数百到数千条这样的高质量数据。4.2 构建 RAG 知识库scripts/build_kb.py# scripts/build_kb.py from langchain.vectorstores import Chroma from langchain.embeddings import HuggingFaceEmbeddings from langchain.document_loaders import TextLoader from langchain.text_splitter import RecursiveCharacterTextSplitter import os def create_knowledge_base(): # 1. 加载处理好的文本块这里从文件读回实际可接上一步的输出 with open(./data/processed/law_chunks.txt, r, encodingutf-8) as f: content f.read() # 简单解析实际应根据保存格式调整 from langchain.schema import Document chunks [Document(page_contentc) for c in content.split(--------------------) if c.strip()] # 2. 选择嵌入模型 # 使用中文优化的嵌入模型 model_name BAAI/bge-large-zh-v1.5 model_kwargs {device: cuda} # 如果有GPU encode_kwargs {normalize_embeddings: True} embeddings HuggingFaceEmbeddings( model_namemodel_name, model_kwargsmodel_kwargs, encode_kwargsencode_kwargs ) # 3. 创建并持久化向量数据库 vectorstore Chroma.from_documents( documentschunks, embeddingembeddings, persist_directory./knowledge_base/chroma_law_db # 持久化目录 ) vectorstore.persist() print(知识库构建完成并已持久化。) if __name__ __main__: create_knowledge_base()4.3 LoRA 微调 Qwen 模型scripts/train_lora.py这是核心步骤我们使用transformers和peft库进行微调。# scripts/train_lora.py import torch from transformers import AutoTokenizer, AutoModelForCausalLM, TrainingArguments, DataCollatorForSeq2Seq from peft import LoraConfig, get_peft_model, TaskType from trl import SFTTrainer from datasets import load_dataset import os # 0. 参数设置 MODEL_NAME Qwen/Qwen2.5-7B-Instruct # 根据你的显存选择模型如 1.5B, 3B OUTPUT_DIR ./models/qwen_lora_law DATASET_PATH ./data/dataset/train.jsonl # 1. 加载模型和分词器 tokenizer AutoTokenizer.from_pretrained(MODEL_NAME, trust_remote_codeTrue) # 设置padding token如果模型没有 if tokenizer.pad_token is None: tokenizer.pad_token tokenizer.eos_token model AutoModelForCausalLM.from_pretrained( MODEL_NAME, torch_dtypetorch.bfloat16, # 节省显存 device_mapauto, trust_remote_codeTrue ) # 2. 配置 LoRA lora_config LoraConfig( task_typeTaskType.CAUSAL_LM, # 因果语言模型任务 r8, # LoRA 秩 lora_alpha32, # 缩放参数 lora_dropout0.1, target_modules[q_proj, k_proj, v_proj, o_proj, gate_proj, up_proj, down_proj], # 针对 Qwen 结构 biasnone ) model get_peft_model(model, lora_config) model.print_trainable_parameters() # 打印可训练参数量应该很少 # 3. 加载并预处理数据集 def format_instruction(example): # 将 instruction, input 组合成模型输入 text f### 指令{example[instruction]}\n### 输入{example[input]}\n### 回答{example[output]} return {text: text} dataset load_dataset(json, data_filesDATASET_PATH, splittrain) dataset dataset.map(format_instruction) def tokenize_function(examples): # 对文本进行分词 return tokenizer(examples[text], truncationTrue, paddingmax_length, max_length1024) tokenized_dataset dataset.map(tokenize_function, batchedTrue, remove_columnsdataset.column_names) # 4. 配置训练参数 training_args TrainingArguments( output_dirOUTPUT_DIR, num_train_epochs3, # 训练轮数 per_device_train_batch_size2, # 根据GPU调整 gradient_accumulation_steps4, # 梯度累积模拟更大batch size warmup_steps100, logging_steps50, save_steps500, learning_rate2e-4, fp16True, # 混合精度训练节省显存 optimadamw_torch, report_tonone, # 不报告给wandb等 save_total_limit2, push_to_hubFalse, ) # 5. 创建 Trainer 并开始训练 trainer SFTTrainer( modelmodel, argstraining_args, train_datasettokenized_dataset, tokenizertokenizer, data_collatorDataCollatorForSeq2Seq(tokenizertokenizer, paddingTrue), ) trainer.train() trainer.save_model(OUTPUT_DIR) tokenizer.save_pretrained(OUTPUT_DIR) print(fLoRA 模型已保存至 {OUTPUT_DIR})注意训练非常消耗显存。Qwen2.5-7B 模型全参数加载需要约 14GB 显存配合 LoRA 和梯度累积在 24GB 显存的 GPU 上可以运行。如果显存不足可以考虑使用 Qwen2.5-1.5B 或 Qwen2.5-3B 模型或启用load_in_4bit/load_in_8bit量化加载。4.4 推理整合RAG LoRA 模型scripts/inference.py现在我们将训练好的 LoRA 模型与 RAG 系统结合进行端到端的问答。# scripts/inference.py import torch from transformers import AutoTokenizer, AutoModelForCausalLM from peft import PeftModel, PeftConfig from langchain.vectorstores import Chroma from langchain.embeddings import HuggingFaceEmbeddings from langchain.prompts import PromptTemplate class LawQAWithRAG: def __init__(self, base_model_name, lora_model_path, kb_path): # 1. 加载基座模型和LoRA适配器 self.tokenizer AutoTokenizer.from_pretrained(base_model_name, trust_remote_codeTrue) if self.tokenizer.pad_token is None: self.tokenizer.pad_token self.tokenizer.eos_token base_model AutoModelForCausalLM.from_pretrained( base_model_name, torch_dtypetorch.bfloat16, device_mapauto, trust_remote_codeTrue ) self.model PeftModel.from_pretrained(base_model, lora_model_path) self.model.eval() print(LoRA 模型加载完毕。) # 2. 加载RAG知识库 embeddings HuggingFaceEmbeddings(model_nameBAAI/bge-large-zh-v1.5) self.vectorstore Chroma(persist_directorykb_path, embedding_functionembeddings) self.retriever self.vectorstore.as_retriever(search_kwargs{k: 3}) # 检索3个最相关片段 print(RAG 知识库加载完毕。) # 3. 定义提示词模板 self.prompt_template PromptTemplate( input_variables[context, question], template你是一个专业的中国刑法AI助手请严格依据提供的法律依据回答问题。 相关法律依据 {context} 问题{question} 请按以下结构组织你的回答 1. 罪名识别 2. 法条依据 3. 情节分析 4. 刑期预测/法律意见 回答 ) def retrieve_context(self, question): 检索相关法律条文 docs self.retriever.get_relevant_documents(question) context \n\n.join([doc.page_content for doc in docs]) return context def generate_answer(self, question): 生成答案 # 检索 context self.retrieve_context(question) print(f检索到的上下文:\n{context[:500]}...\n) # 构建提示词 prompt self.prompt_template.format(contextcontext, questionquestion) # 模型推理 inputs self.tokenizer(prompt, return_tensorspt, truncationTrue, max_length2048).to(self.model.device) with torch.no_grad(): outputs self.model.generate( **inputs, max_new_tokens512, temperature0.7, do_sampleTrue, top_p0.9, repetition_penalty1.1 ) answer self.tokenizer.decode(outputs[0][inputs[input_ids].shape[1]:], skip_special_tokensTrue) return answer.strip() if __name__ __main__: # 初始化问答系统 qa_system LawQAWithRAG( base_model_nameQwen/Qwen2.5-7B-Instruct, lora_model_path./models/qwen_lora_law, kb_path./knowledge_base/chroma_law_db ) # 测试问题 test_questions [ 盗窃数额巨大但主动投案并退赃会怎么判, 酒后驾车撞死人构成什么罪, 请解释一下什么是‘故意伤害罪致人重伤’的认定标准。 ] for q in test_questions: print(f\n问题{q}) print(*50) answer qa_system.generate_answer(q) print(f回答\n{answer}) print(*50)4.5 运行与结果说明依次运行数据处理、知识库构建、模型训练脚本。运行inference.py进行测试。预期效果罪名识别模型应能准确识别出“盗窃罪”、“交通肇事罪”或“危险驾驶罪”、“故意伤害罪”。法条依据答案中应引用检索到的具体刑法条文如第二百六十四条、第一百三十三条等。刑期预测基于检索到的量刑指导意见和模型微调后的理解给出有一定参考价值的刑期范围或量刑情节分析并强调“最终由法院裁定”。语言风格回答应结构清晰、用语专业严谨体现出经过法律数据微调的效果。相比于直接使用原始 Qwen 模型经过 RAGLoRA 增强的系统其答案的准确性、依据的明确性和专业性都会有显著提升。5. 常见问题与排查思路在实战过程中你可能会遇到以下典型问题问题现象常见原因解决思路训练时 GPU 内存溢出 (OOM)1. 模型太大。2. Batch size 或序列长度设置过高。3. 未使用梯度累积或混合精度。1. 换用更小模型如 1.5B。2. 启用load_in_4bit或load_in_8bit量化加载模型。3. 减小per_device_train_batch_size和max_length。4. 增加gradient_accumulation_steps减小batch_size保持总 batch 不变。5. 确认fp16True已开启。模型生成无关或胡言乱语1. 训练数据质量差或格式不对。2. 训练轮数过多导致过拟合。3. 提示词模板未与训练数据格式对齐。1. 检查训练数据格式是否为(instruction, input, output)且output质量高。2. 减少num_train_epochs或在验证集上早停。3. 确保推理时的提示词格式与训练时format_instruction函数构建的格式尽可能相似。RAG 检索结果不相关1. 文本分割策略不合理破坏了语义。2. 嵌入模型不适用于中文法律文本。3. 检索数量k不合适。1. 尝试按“条”、“款”等法律自然结构分割或调整chunk_size和chunk_overlap。2. 换用针对中文优化的嵌入模型如BAAI/bge-*系列。3. 调整search_kwargs{k: n}尝试不同的 n 值如 2, 3, 5。回答未引用法条或引用错误1. RAG 检索到的上下文未有效融入提示词。2. 模型在微调时未学会引用上下文。1. 优化提示词模板明确要求“根据以下法律依据回答”并将{context}放在显著位置。2. 在指令数据集中确保output部分明确包含了从input和隐含上下文中推导出法条的过程。推理速度慢1. 模型较大未量化。2. RAG 检索环节耗时。1. 使用 GPTQ 或 AWQ 量化模型后加载可大幅提升推理速度。2. 对于生产环境考虑将向量数据库部署在内存或更快的引擎中或对嵌入模型进行优化。6. 最佳实践与工程建议将原型推进到可用的工程化系统还需要注意以下几点数据质量是生命线知识库文档确保法律文本的权威性、完整性和时效性。定期更新司法解释和指导案例。微调数据集构造数据时应聘请法律专业人士或参考权威裁判文书确保罪名、刑期预测的准确性。数据应覆盖常见罪名和复杂情节。提示词工程优化本示例的提示词较为基础。可以设计更复杂的思维链Chain-of-Thought提示要求模型先复述事实再匹配法条最后分析情节。在提示词中明确限制模型“仅基于提供的上下文回答”对于上下文未覆盖的问题应回答“根据现有信息无法判断”避免幻觉。模型评估与迭代建立一个小型的、有标准答案的测试集从事实准确性、法条引用正确率、逻辑一致性和语言专业性多个维度评估模型。可以采用RAGAS等框架进行自动化评估。根据评估结果迭代优化数据、提示词和模型参数。安全与伦理边界明确免责声明系统的输出仅为法律信息参考不构成正式法律意见。必须在交互界面显著位置提示用户咨询专业律师。内容过滤在模型输入输出端部署内容安全过滤器防止模型被用于生成违法、有害或误导性内容。可控生成通过设置temperature、top_p等参数控制生成结果的随机性和创造性对于法律应用应倾向于较低的温度值如 0.3以保证回答的稳定性。部署性能优化模型量化使用bitsandbytes的 8-bit/4-bit 量化或 GPTQ/AWQ 量化技术在精度损失极小的情况下大幅降低模型内存占用和提升推理速度。服务化使用FastAPI或vLLM将模型封装成 API 服务并考虑使用NGINX做负载均衡。缓存机制对常见的法律问题如“盗窃罪立案标准”的 RAG 检索结果和模型生成结果进行缓存减少重复计算。通过以上步骤你不仅能够搭建一个演示级别的刑法大模型更能掌握构建垂直领域大模型应用的核心方法论利用 RAG 解决知识实时性与准确性问题通过 LoRA 微调让模型深度适应专业领域再辅以严谨的工程化实践确保系统可靠可用。这套组合拳可以推广到医疗、金融、教育等众多需要专业知识与复杂推理的垂直场景。