
之前在接一个法律领域的智能化项目时我面临的需求非常朴素把刑法条文、司法解释、公开裁判文书和脱敏后的卷宗材料统一管理起来让办案人员、律师能够用自然语言快速找到相关法条、类案和量刑参考而且数据不能出内网必须本地部署。真正动手以后才发现通用大模型解决不了这个场景。闲聊、写文案都没问题一旦问到“这个行为构成什么罪、对应的是哪一条”回答经常是七分像、三分错法条编号说错、量刑情节张冠李戴的情况非常普遍。法律文本本身要求的是“法条引用必须准确、结论必须有依据”而通用模型的幻觉在这里是完全不可接受的。经过两个多月的反复调试我最终把技术链路拆成了两部分用 LoRA 微调让模型学会按法律文书的语气和格式回答用 RAG 让模型在回答时“先查资料再说话”保证答案能够溯源。这套方案完整落地后效果比较理想。本文就把整个实操过程写出来包括 Qwen 模型选型、LoRA 微调原理、数据集构建、训练配置、百万级卷宗 RAG 的落地细节以及那些凌晨出现的报错和排查思路。1. 为什么需要“刑法大模型”背景与核心概念1.1 法律文本处理为什么难刑法领域的知识体系和通用文本有非常大的差异。首先是法条表达极度凝练一个“情节严重”“数额较大”在不同的司法解释、不同罪名里可能有完全不同的认定标准其次是法律渊源层级多刑法典、修正案、司法解释、指导性案例、各省高院的量刑指导意见权威性和适用范围都不一样第三是卷宗材料形态很杂有扫描件、判决书、起诉书、讯问笔录、证据清单格式不统一。传统的关键词检索只能解决“搜到包含某个词的文件”没办法解决“我描述一个行为帮我判断这可能涉及哪个罪名”的需求。这个需求本质上是一个语义检索加法律推理的复合问题只有大模型加向量检索的组合才能比较理想地覆盖。1.2 为什么选 Qwen LoRA RAG中间我也对比过几家开源模型最终选择 Qwen 系列主要基于三点中文能力扎实对法律术语、文言化表达的理解明显优于同等参数规模的英文主导模型开源且商用授权友好支持本地化部署能保证数据不出内网社区生态完整无论是 Hugging Face 还是 ModelScope 都有现成权重和 LLaMA-Factory、vLLM 等工具链配合得很好。整套方案里LoRA 和 RAG 各司其职。LoRA 负责“行为风格”层面让模型学会法律答问的格式、语气和推理结构RAG 负责“知识来源”层面让模型在回答时引用真实法条和真实文书。用一个不太严谨但很好理解的比喻微调是训练一个人说话的专业性RAG 是给他一本必须随时查阅的法规汇编而且要求每次回答都标注出处。1.3 整体技术链路下面这张图描述了项目的核心流程我不画复杂的架构图直接按数据流拆开讲公开法条、司法解释、裁判文书、脱敏卷宗 │ ├─→ 微调数据集 → LoRA 微调 Qwen → 合并模型权重 │ └─→ RAG 知识库 → PDF解析 → 文本切片 → Embedding → 向量库 │ 用户提问 → 意图识别 → 多路召回(向量BM25) → 重排 → 拼接 Prompt │ 微调后的 Qwen 模型生成答案 → 返回引用来源后续所有章节基本都是围绕这条链路展开的。下面先从环境准备说起。2. 环境准备与版本说明2.1 硬件与运行环境微调 Qwen 这样的 7B 模型显存压力是绕不开的。如果你的 GPU 显存是 24GB 及以上LoRA 微调和本地推理都很从容如果只有 12GB 到 16GB建议用 Qwen 的 1.5B 或 3B 版本先跑通流程或者把 LoRA 的秩调小、开启梯度检查点如果完全没有 GPU也可以用小模型在 CPU 上做一次全流程体验但训练时间会非常久不适合正式实验。本文示例环境以常见的 Linux 服务器为例Python 版本建议 3.10 或 3.11。具体版本需要根据你的项目实际情况调整下面是演示用的环境清单组件示例版本/型号说明操作系统Ubuntu 22.04其他 Linux 发行版同样适用GPUNVIDIA 4090 24GB显存不足时可换小模型Python3.11建议使用 conda 虚拟环境CUDA12.1以本机 nvidia-smi 为准PyTorch2.x安装命令随 CUDA 版本变化QwenQwen2.5-7B-Instruct以实际拉取到的权重为准2.2 安装基础依赖创建虚拟环境并安装 PyTorchconda create -n legal-llm python3.11 -y conda activate legal-llm # 根据你的 CUDA 版本选择安装命令这里以 CUDA 12.1 为例 pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu121 # 安装 LLaMA-Factory用于微调 pip install llama-factory[torch] # 安装检索与推理相关库 pip install sentence-transformers faiss-cpu pymupdf transformers这里要注意llama-factory[torch]会自动拉取对应的 transformers、datasets、peft 等依赖。如果之前已经装过其他版本的 transformers建议在虚拟环境里统一安装避免版本冲突。2.3 模型下载与项目目录从 ModelScope 下载 Qwen 权重比较快国内网络环境友好pip install modelscope modelscope download --model Qwen/Qwen2.5-7B-Instruct --local_dir ./models/Qwen2.5-7B-Instruct项目的目录结构建议提前规划好训练和检索脚本多起来以后目录混乱是最容易出低级错误的地方legal-llm/ ├── models/ # 本地模型权重 ├── data/ │ ├── raw/ # 原始法律文本 │ ├── processed/ # 清洗后的微调数据 │ └── knowledge/ # RAG 知识库原始文件 ├── config/ # 微调与推理配置 ├── scripts/ # 训练、推理、检索脚本 ├── outputs/ # 训练日志、适配器与合并模型 └── vector_db/ # 向量索引与元数据3. 微调方式对比全量微调、Freeze 微调与 LoRA3.1 三种微调方式的核心区别在开始训练之前有必要把全量微调、Freeze 微调和 LoRA 的区别讲清楚因为很多人一上来就跟着教程跑 LoRA却不知道它在整个微调方案里处于什么位置。微调方式更新参数范围显存占用训练速度适用场景全量微调模型全部参数很高慢数据量充足、算力充足、需要极限效果Freeze 微调冻结大部分层只更新部分参数中等中等领域差异较大但资源有限LoRA 微调只训练注入的低秩矩阵低快单卡、快速迭代、多领域并行全量微调的效果上限最高但对显存和数据量的要求也最高。7B 模型全量微调即使开了各种显存优化策略仍然需要多卡或者大显存才能跑得舒服。Freeze 微调通常冻结底层微调高层思路是让底层通用特征保持不变只调整高层语义映射。LoRA 则走了另一条路不直接改原权重而是给某些层并联一套低秩矩阵训练完只保存这套小矩阵。3.2 LoRA 的低秩分解原理LoRA 的全称是 Low-Rank Adaptation低秩适配。它的核心观点是模型权重在微调过程中的变化量 ΔW 并不需要是满秩的完全可以把它近似分解成两个低秩矩阵的乘积W W ΔW W B × A其中 W 是原始权重矩阵形状为 d × dA 的形状是 r × dB 的形状是 d × r秩 r 远小于 d。训练时冻结 W只更新 A 和 B最终保存的适配器文件只有几十到几百 MB和动辄十几 GB 的原模型权重相比轻量得多。为什么低秩分解有效原因在于预训练模型已经学到了通用的语言能力和基础世界知识微调只是在这个基础上做领域偏移这种偏移的“有效自由度”并不高所以用少数参数就能逼近理想效果。这也是 LoRA 在参数高效微调领域成为主流的原因。3.3 什么时候选 LoRA从我这次项目经验看LoRA 适合绝大多数领域微调场景尤其是单卡资源、数据量几万条、需要在多个领域模型之间快速切换的情况。但也要注意两个前提LoRA 不是万能药秩 r 太小会欠拟合r 太大会显存压力变大且容易过拟合微调解决的是“回答方式和风格”不是“知识注入”。指望微调让模型记住所有法条编号是不现实的这也是项目必须搭配 RAG 的原因。4. 构建刑法领域微调数据集4.1 数据来源与合规边界数据是微调效果的上限。刑法领域数据集我主要用了四类来源公开的刑法条文、修正案、司法解释文本最高人民法院、最高人民检察院公开发布的指导性案例和典型案例中国裁判文书网公开的裁判文书注意做个人信息脱敏经过授权的脱敏卷宗材料这部分不进入公开模型和公开数据集。这里必须反复强调涉及个人信息、商业秘密、国家秘密或未授权的内容一律不能进入训练集和知识库。法律行业对数据安全极其敏感项目上线前一定要做合规审查建议以授权的公开数据为主。至于“百万卷宗”我理解它指的是检索规模的设计目标本文演示的是单机原型说明如何按百万级架构演进而不是把所有卷宗塞进训练集。4.2 数据格式指令微调与偏好数据这次微调主要用指令微调数据LLaMA-Factory 支持 alpaca 格式一个样本包含 instruction、input、output 三个字段。下面是一条演示样本用于说明格式不代表任何具体司法意见[ { instruction: 张三在公交车上趁被害人不备公然夺取其手机后逃离手机经鉴定价值人民币三千元。请分析张三可能涉及的罪名。, input: , output: 根据《中华人民共和国刑法》第二百六十七条抢夺公私财物数额较大的构成抢夺罪。本案中张三趁人不备公然夺取手机符合抢夺罪“公然夺取”的行为特征且手机价值三千元已达到数额较大标准。具体定性还需结合是否存在暴力、威胁等手段综合分析若使用暴力或以暴力相威胁则可能构成抢劫罪。以上分析仅供参考不构成法律意见。 } ]再补充一点除指令微调数据外还有强化学习偏好数据集用于让模型学会区分“好回答”和“差回答”。你可以把同一问题写成几个候选答案标注哪个更准确、更有条理用于后续的 DPO 或 RLHF 对齐。但从零基础入门角度先把指令微调跑通再考虑偏好对齐。4.3 数据清洗与去重原始法律文本清洗时要注意几个细节统一编码为 UTF-8处理全角半角、多余空格和 HTML 标签裁判文书去掉案号、日期、当事人信息等敏感字段用 MinHash 或 SimHash 做大规模去重避免相似文书反复出现导致过拟合过滤长度过短的样本例如 output 少于 30 个字符的样本质量通常不高做“题目—答案”一致性检查至少抽 10% 的样本人工核对。清洗脚本不必写得很花哨核心就是让数据干净、可控、可追溯。每条样本最好保留来源字段方便后面排查模型为什么回答成某个样子。4.4 数据集规模与划分很多人会问“到底要多少条数据”。以 7B 模型 LoRA 微调为例我的经验是指令微调数据 2 万到 5 万条效果就会有明显提升低于 5000 条时重点不是堆数量而是保证多样性和质量数据要划分训练集和验证集比例建议 95:5验证集不参与训练数据里法条问答、类案分析、术语解释三类任务的比例根据你的实际业务场景调整。5. 使用 LLaMA-Factory 对 Qwen 做 LoRA 微调5.1 注册数据集LLaMA-Factory 使用data/dataset_info.json来管理数据集。在 LLaMA-Factory 安装目录的 data 文件夹下把自定义数据集注册进去{ criminal_law_qa: { file_name: criminal_law_qa.json, formatting: alpaca, columns: { prompt: instruction, query: input, response: output } } }然后把上一节准备好的 criminal_law_qa.json 放到 data 目录下并和 dataset_info.json 里的文件名保持一致。很多新手在第一步就踩坑最常见的问题是文件名不一致或者 JSON 的列名和实际字段对不上导致训练启动时报格式校验错误。5.2 配置 LoRA 训练参数我使用 YAML 配置文件来管理训练参数方便复现和版本管理。下面是一份可以直接套用的配置# config/lora_sft.yaml model_name_or_path: ./models/Qwen2.5-7B-Instruct dataset: criminal_law_qa template: qwen finetuning_type: lora lora_rank: 32 lora_alpha: 64 lora_dropout: 0.05 output_dir: ./outputs/qwen-lora-criminal per_device_train_batch_size: 2 gradient_accumulation_steps: 8 learning_rate: 2.0e-4 num_train_epochs: 3.0 lr_scheduler_type: cosine warmup_ratio: 0.03 fp16: true几个关键参数我重点解释一下lora_rankLoRA 矩阵的秩决定了可训练参数量。32 是 7B 模型比较常用的起点数据量大可以试 64lora_alpha缩放因子实际缩放比例是 alpha 除以 rank一般设成 rank 的两倍learning_rateLoRA 微调常用 1e-4 到 3e-4比全量微调的 1e-5 大一个量级gradient_accumulation_steps梯度累积用时间换显存2 × 8 等效于 batch size 16template必须和模型匹配Qwen 使用 qwen 模板错了会出现对话格式混乱。5.3 启动训练配置写好后一行命令启动llamafactory-cli train config/lora_sft.yaml训练日志里重点关注 loss 数值的变化。以我的项目为例初始 loss 在 1.1 左右第一个 epoch 末尾降到 0.6 左右三个 epoch 结束后稳定在 0.4 附近。如果 loss 在前几个 step 就急速下降然后几乎不变说明数据集太简单或样本重复度过高如果验证集 loss 在训练后期反而开始上升说明出现过拟合应该减少 epoch 或增大 dropout。5.4 合并模型权重LoRA 训练完保存的是适配器文件不能直接作为独立模型部署。推理前需要把适配器合并回原模型权重llamafactory-cli export \ --model_name_or_path ./models/Qwen2.5-7B-Instruct \ --adapter_name_or_path ./outputs/qwen-lora-criminal \ --template qwen \ --finetuning_type lora \ --export_dir ./outputs/qwen-lora-criminal-merged \ --export_size 4 \ --export_legacy_format false合并后的模型可以直接被 vLLM 或 Ollama 加载。建议合并前先复制一份原始权重备份避免误操作。6. 百万卷宗 RAG 落地实践6.1 RAG 整体架构RAG全称 Retrieval-Augmented Generation检索增强生成。RAG 要解决的核心问题就是让模型在生成前先检索相关资料把检索结果塞进 Prompt再生成答案。项目之所以必须上 RAG原因在前文已经说过微调不能解决“记忆所有法条”的问题。法条、司法解释、类案知识是动态变化的如果靠训练参数去记忆每出台一个司法解释就要重新微调成本完全不可接受。RAG 把知识外移到向量库模型只负责理解问题和组织答案这样更新知识只需要更新知识库。6.2 文档解析与切片刑法相关 PDF 文档的解析我推荐 PyMuPDF。它对纯文本型 PDF 的解析速度快、效果稳定# scripts/extract_pdf.py import fitz from pathlib import Path def extract_text(pdf_path: str) - str: doc fitz.open(pdf_path) text \n.join(page.get_text() for page in doc) doc.close() return text if __name__ __main__: path Path(data/knowledge/刑法.txt) text extract_text(data/knowledge/刑法.pdf) path.write_text(text, encodingutf-8) print(提取完成字符数, len(text))需要注意这个脚本只对文本型 PDF 有效。扫描件、图片型卷宗必须先用 OCR 识别。我当时用 PaddleOCR 做了一批扫描卷宗的识别识别率在清晰文书上能达到 95% 以上但手写笔录和盖章遮挡区域需要人工复核。切片策略直接决定了检索质量。我实验下来效果比较好的方式是“按语义结构优先辅以长度截断”优先按章节标题和条款编号切遇到超长文本再按固定窗口切窗口长度大概 300 到 500 字相邻窗口保留 50 字左右的 overlap避免把关键上下文从中间截断。6.3 向量化与向量入库中文语义向量模型我测试过多个最终选定了 BGE 系列的中文模型。原因是它对法律长文本的泛化能力较好而且支持为 query 单独添加指令前缀能改善“查询短、文档长”场景下的检索效果。下面是入库的核心代码使用 FAISS 作为单机向量库# scripts/build_vector_db.py from sentence_transformers import SentenceTransformer import faiss import numpy as np import json model SentenceTransformer(BAAI/bge-large-zh-v1.5) # chunks 是从上一步切片生成的文本列表meta 保存文档名、页码、来源等 chunks [法条文本切片1, 法条文本切片2] meta [{source: 刑法.txt, page: 1, idx: 0}] vectors model.encode(chunks, normalize_embeddingsTrue) dim vectors.shape[1] index faiss.IndexFlatIP(dim) # 内积检索配合归一化就等价于余弦相似度 index.add(vectors.astype(float32)) faiss.write_index(index, vector_db/legal_kb.index) with open(vector_db/legal_kb_meta.json, w, encodingutf-8) as f: json.dump(meta, f, ensure_asciiFalse, indent2) print(向量数量, index.ntotal)FAISS 适合百万级以内的单机场景。如果目标是真正支撑百万卷宗并且需要多副本、实时更新建议迁移到 Milvus 或 Elasticsearch原理上是一致的只是部署形态更复杂。6.4 检索增强生成检索时把用户问题编码成向量在 FAISS 中取 Top-K再把命中的原文拼进 Prompt调用本地部署的 Qwen 生成答案# scripts/rag_infer.py import numpy as np import faiss from openai import OpenAI index faiss.read_index(vector_db/legal_kb.index) meta json.load(open(vector_db/legal_kb_meta.json, encodingutf-8)) encoder SentenceTransformer(BAAI/bge-large-zh-v1.5) client OpenAI( base_urlhttp://127.0.0.1:8000/v1, api_keyEMPTY, ) def retrieve(query: str, top_k: int 5): q_vec encoder.encode([query], normalize_embeddingsTrue) scores, ids index.search(q_vec.astype(float32), top_k) docs [] for score, idx in zip(scores[0], ids[0]): docs.append({meta: meta[idx], score: float(score)}) return docs def build_prompt(query: str, docs: list) - str: context \n\n.join( f[来源{i 1}]\n{doc[meta].get(source, 未知)}{doc[content]} for i, doc in enumerate(docs) ) prompt f你是一名法律助理。请严格根据以下参考资料回答用户问题。 参考资料 {context} 问题{query} 要求 1. 只依据参考资料回答不要编造法条或事实。 2. 如果资料不足明确回答“资料不足无法准确回答”。 3. 针对法条引用请说明条文出处。 4. 最后提示“以上回答仅供参考不构成法律意见”。 return prompt def answer(query: str): docs retrieve(query) prompt build_prompt(query, docs) resp client.chat.completions.create( modellegal-llm, messages[{role: user, content: prompt}], temperature0.3, max_tokens1024, ) return resp.choices[0].message.content本地使用 vLLM 启动推理服务vllm serve ./outputs/qwen-lora-criminal-merged \ --served-model-name legal-llm \ --port 8000 \ --max-model-len 8192 \ --gpu-memory-utilization 0.9注意上面的retrieve代码里我假设了 meta 中保存了切片原文的 content 字段实际项目中建议把切片文本冗余保存一份在 meta 中否则还要根据 idx 反查原文文件多一次磁盘 IO。6.5 从单路召回升级为多路召回单靠向量检索有一个典型问题法律文书里的专业术语、案号、法条编号属于“精确匹配”信息“强制猥亵”和“侮辱”在语义上可能有距离但在具体案件里却紧密相关更麻烦的是向量检索对“法条编号 267 条”这种精确字符并不敏感。纯向量召回很容易漏掉关键文书。我的做法是升级为“多路召回 重排”。多路召回就是同时跑向量检索和 BM25 关键词检索然后通过 RRF 算法把两路结果融合# scripts/hybrid_retrieve.py from jieba import lcut def bm25_retrieve(query: str, corpus: list, top_k: int 10): # 这里省略 BM25 的具体实现可引入 rank_bm25 库 pass def rrf_fusion(list1: list, list2: list, k: int 60) - list: scores {} for rank, doc_id in enumerate(list1): scores[doc_id] scores.get(doc_id, 0) 1.0 / (k rank 1) for rank, doc_id in enumerate(list2): scores[doc_id] scores.get(doc_id, 0) 1.0 / (k rank 1) return sorted(scores.items(), keylambda x: x[1], reverseTrue)融合完的候选集仍然可能有噪声所以还需要一个重排模型做精排。我用的是BAAI/bge-reranker-large把查询和候选文档逐对输入输出相关度分数取分数最高的 5 条作为最终上下文。多路召回加 reranker 之后检索命中率提升非常明显。在项目自建的法律问答评测集上Top-5 命中率从单路向量的 62% 提升到了 84% 左右。这个数字不是通用结论但足以说明多路召回在垂直领域 RAG 里的价值。7. 凌晨报错复盘高频问题与排查清单训练和调优阶段报错是常态。这里把调试过程中遇到的高频问题按“现象—原因—解决”整理出来方便后来者快速定位。7.1 CUDA out of memory这是最常见的报错深夜训练时尤其容易遇到RuntimeError: CUDA out of memory. Tried to allocate 256.00 MiB (GPU 0; 23.65 GiB total capacity; 22.33 GiB already allocated; 1.13 GiB free; 5.02 GiB reserved in total by PyTorch)根本原因就是显存余量不够。排查顺序是先看 nvidia-smi 确认有没有其他进程占显存再调小per_device_train_batch_size从 2 降到 1然后开启gradient_checkpointing: true用计算换显存最后考虑把 lora_rank 从 32 降到 16。如果都不行就换更小的模型或者用多卡。7.2 数据集格式校验失败类似下面的报错通常发生在 LLaMA-Factory 初始化数据集时ValueError: Unknown formatting type for dataset criminal_law_qa.最常见的原因是 dataset_info.json 里写错了 formatting 的值或者 columns 字段映射错误。alpaca 格式必须写清 prompt、query、response 三列。解决方法打开 dataset_info.json 逐项核对字段名和 JSON 文件的实际键名注意大小写。7.3 中文编码报错UnicodeDecodeError: gbk codec cant decode byte 0x... in position ...Windows 环境下最容易出现因为默认编码可能是 gbk。解决方法是所有打开文件的地方都显式指定encodingutf-8读取数据前用脚本统一转换编码训练脚本所在目录也建议放一个.editorconfig强制 UTF-8。7.4 生成内容出现死循环重复微调后的模型在某些 query 下会反复输出同一句话无限重复消耗大量 token。Hot 词里也有“qwen 输出死循环”的讨论这确实是 Qwen 系模型在采样参数设置不当或过拟合时容易出现的现象。解决方案有三个层面推理时设置repetition_penalty1.15或更高设置no_repeat_ngram_size3禁止三元组重复在 API 层强制限制max_tokens例如 2048从源头防止无限生成。如果调整采样参数后仍然严重重复说明 LoRA 训练出现过拟合需要回调 epoch 或增大 dropout。7.5 检索命中率低检索不到相关资料模型只能胡编这是 RAG 最常见的质量问题。排查思路检查切片长度太长的切片里真正相关的信息可能只有一小段被向量平均后拉低了相似度检查 Embedding 模型法律领域切换为法律或中文优化的模型效果差异明显检查查询方式复杂问题建议先做 query 改写把“张三在公交车上抢手机怎么判”拆成“抢夺罪 公交车上 量刑”加入 BM25 多路召回可以覆盖精确关键词场景。7.6 高频问题汇总问题现象常见原因解决思路CUDA OOM单卡显存不足、batch size 过大调小 batch size、开梯度检查点、换小模型数据集格式校验失败dataset_info.json 字段配置错误核对文件路径、字段名、formatting 类型中文乱码或解码失败文件编码非 UTF-8统一使用 UTF-8 编码并显式指定生成内容不断重复采样参数不当或过拟合提高 repetition_penalty、限制 max_tokens检索结果与问题无关切片不合理或单路召回能力有限调整切片策略、换 Embedding 模型、多路召回vLLM 启动报“model not found”模型路径或 adapter 未正确合并确认模型目录结构、重新 export8. 法律领域模型上线的最佳实践8.1 数据合规与安全边界刑法领域模型天然涉及敏感数据上线前必须想清楚安全边界数据来源必须合法公开数据也要注意授权范围涉及个人信息的内容必须脱敏脱敏规则要有复核机制涉密卷宗一律不允许进入任何训练集、检索库和日志系统系统权限遵循最小权限原则不同角色只能访问对应范围的卷宗所有生成结果保留审计日志方便追溯责任。我这里特别强调一下“最小权限”。即使是内部系统也不需要每个用户都能检索全部卷宗。按角色和案件类型做权限隔离是法律行业项目的基本要求。8.2 输出免责与人工复核法律行业是强监管、强责任场景。模型输出必须强制附带免责声明并且不能在关键结论上替代专业判断。项目里的 Prompt 模板已经加入了“以上回答仅供参考不构成法律意见”建议在用户界面和 API 返回结构中也加入 disclaimer 字段。更稳妥的方案是人工复核闭环模型给出初步分析和引用来源法务人员审核后再对外输出。对于量刑建议、罪名定性这类高风险结果自动生成只能作为辅助参考不能直接作为正式法律文书。8.3 构建 RAG 评测体系RAG 效果好不好不能靠感觉。项目上线前一定要建立可量化的评测集。我的做法分三层检索能力构造一批“问题—应命中文档”的标注数据计算 Recall5、MRR生成质量人工对答案的正确性、忠实度、引用格式打分端到端指标法条引用准确率、关键实体正确率、拒绝回答率。评测集里的问题要覆盖法条问答、类案检索、术语解释、跨法条推理四类任务每类至少 100 条才能比较稳定地反映系统能力。8.4 工程化部署建议模型微调完成后工程化部署还有几个细节推理服务推荐 vLLM吞吐量明显优于原生 transformers单机向量库用 FAISS项目扩大后迁移 Milvus检索服务和推理服务建议分开部署避免互相影响API 层做限流和超时控制防止单条超长查询拖垮服务模型权重和向量库的更新流程要自动化建立版本管理。如果是部署到内网没有外网的环境需要提前把所有依赖打包可以用 conda-pack 或 Docker 镜像。Ollama 也可以用来快速部署经过转换的 GGUF 模型适合资源有限、要求部署简单的场景。9. 总结与后续学习路线这篇文章从项目背景讲到最终落地核心可以提炼成几句话第一通用大模型在法律领域不能直接用幻觉问题决定了它必须搭配微调和 RAG 才能扮演辅助角色第二LoRA 微调用很小的成本让模型学会了法律答问的表达方式但它解决不了知识的动态更新和精确记忆问题第三RAG 是法律知识外置的关键多路召回加重排在垂直领域带来的检索提升非常明显第四所有法律相关功能都必须围绕数据合规、免责声明和人工复核来设计。下一步建议按这个顺序继续深入完整跑通一次 Qwen LoRA 微调哪怕用 1.5B 模型先熟悉 LLaMA-Factory 的完整链路在单机上构建一个 1 万篇文书规模的 RAG 知识库尝试不同的切片策略和向量模型对比检索效果学习 DPO 偏好对齐用法条问答偏好集进一步优化模型的回答风格研究 Agentic RAG让模型自主决定什么时候检索、检索几轮、如何汇总多个来源这对复杂法律问题很有价值评估体系先于功能上线建议从第一天就维护评测集。法律 AI 目前还处在快速发展阶段没有哪个公开模型能做到完全可靠。但只要把微调、RAG 和人工复核的边界设计清楚这套方案就能在真实业务里发挥非常实际的作用。如果本文对你有帮助可以收藏备用也欢迎在实践中遇到问题后回来对照排查清单看看。