这个仓库能给你一套中文法规语料、2,475 条带条款级引用的问答,以及一套现成的召回/生成评测脚本。但它不是下载即用:数据有版本差、偏移量失效、章节结构大量丢失,脚本也跑不起来。这篇按“背景 → 数据 → 脚本 → 能做什么实验”四块拆开,重点说清楚哪些数据可信、哪些只能当参考。

1. 仓库背景:它是什么,和论文什么关系

仓库地址:legal-dc/Legal-DC。

git clone --depth 1 https://github.com/legal-dc/Legal-DC.git

克隆下来约 51 MB,其中原始 docx 占 18 MB。仓库本身很轻:

项 值
提交历史 3 个 commit,都在 2024-11-01
README 两行占位
LICENSE 没有
目录 仓库根下再套一层同名 Legal-DC/
代码 混合检索主流程、5 个模型调用脚本、若干评测脚本
数据 539 篇 docx、结构化语料 JSON、问答全集与 5 个分片

它对应论文《Legal-DC: Benchmarking Retrieval-Augmented Generation for Legal Documents》(arXiv:2603.11772,2026-03)。两者的关系要分清:

论文 仓库
提出 Legal-DC 基准、LegRAG 框架、自动化评测方法 提供数据集、常规混合检索基线、评测脚本
LegRAG 的条款边界索引、双路自反思 不在仓库里
2026-03 挂出 代码停在 2024-11-01

所以结论是:数据集和评测口径可以用;论文里的 SOTA 不能从这个仓库复现。 仓库快照还早于论文,论文表格里的数字未必对应当前 commit。

另外,没有 LICENSE。内部研究没问题,商用或再分发前先确认授权。

还有一点容易被名字误导:它不是合同数据集。标题里带“合同”的只有 4 篇,主体是市场监管、食品/药品/医疗器械、质量安全、广告网络、知识产权等行政法规和规章。要做商业合同条款抽取,CUAD 那类数据更合适;这里评的是“法规咨询”。

2. 数据:有什么,怎么理解,哪些可信

2.1 三个文件口径

文件 文档 问答 说明
struct_document_539_final.json 539 — 语料池,比 QA 覆盖多 59 篇
pro_LawQA.json 480 2,474 单文件全集,无空引用
pro_LawQA-1..5.json 480 2,475 5 个分片,含 1 条空引用

记住三句话:2,475 是分片口径;单文件全集少 1 条;语料池比 QA 覆盖多 59 篇。

2.2 语料:539 篇,27,176 个 chunk

语料文件是一个列表,每篇文档三个字段:编号、标题、正文。正文 不是字符串,而是“段落序号 → 文本”的字典:

{
"编号": 0,
"标题": "《对外劳务合作经营资格管理办法》补充规定",
"正文": {
"0": "《对外劳务合作经营资格管理办法》补充规定",
"1": "(2005年8月15日商务部、国家工商行政管理总局令2005年第14号公布)",
"2": "为适应企业改制的需要……"
}
}

这 27,176 个值,就是仓库默认的检索单元。它们不是按句子或固定长度切的,而是以 docx 段落为基础、按“第X条”做了聚合:

  • 521 篇有条文结构的文档共 26,323 个 chunk,其中 26,175 个(99.4%)以“第X条”开头。
  • 18 篇没有条文结构的文档(补充规定、代码标准、抽查细则等)按非空段落逐段保留。
  • 文首标题、公布令、章/节标题大多被丢掉;超长条文会在句号或段落边界处再切一刀。

这套切法有两个明显后果:

指标 实测
chunk 能在 docx 原文里逐字找到 26,942 / 27,176 = 99.14%
章/节标题保留 3,295 个只剩 5 个(0.15%)
表格单元格保留 2,029 / 4,567 = 44.4%
含 ≥2 个完整条文头的 chunk 237 个(0.87%),分布在 66 篇
整篇只有 1 个 chunk 的文档 3 篇

具体例子:

  • 《合同违法行为监督处理办法》16 条被合成 1 个 1,706 字的 chunk。
  • 《公司法》《保险法》《企业破产法》出现“10 条一块”。
  • 《专利法》第十九条又被切成 187 字 + 43 字两段。
  • 《专利代理条例》的“第二章 专利代理机构和专利代理师”被删,前后条文直接粘连。

怎么理解:可以把每个 正文 值大致当成“一条法规”,但不能假设它严格等于一条。 要做严格的条款级评测,最好回到 docx 重建。

2.3 问答:2,475 条,四种题型

pro_LawQA.json 每条七个字段:query、answer、document、title、document_start、document_end、class。

  • query 是问题;
  • answer 是自然语言参考答案;
  • document 是条款级标准答案列表,不是文档名;
  • class 是题型。

实测统计:

指标 实测值
条数 全集 2,474;分片 2,475
覆盖文档 480
引用总数 2,812
每题引用数 1 条 2,278;2 条 143;3 条 23;4 条 15;5 条 8;最多 24 条
问题长度 中位数 20 字,P90 34 字,最长 79 字
答案长度 中位数 75 字,P90 201 字,最长 672 字
每篇 QA 数 2–8 条,中位数 5 条

题型分布:

题型 条数 占比 特点
概括归纳型 1,614 65.2% 从长条款里提炼要点
逻辑推理型 527 21.3% 需要多步判断
概念解释型 268 10.8% 解释术语或法律概念
其他 65 2.6% 施行时间、文号等事实型

注意两个代码级陷阱:

  • class 字段里“逻辑推理型”前面有一个空格,527 条全是 ' 逻辑推理型'。用 class == '逻辑推理型' 精确过滤会一条都选不到,先 .strip()。
  • 9 组问题文本重复出现(每组 2 次)。做评测时可以去重,也可以保留;保留更接近真实流量,去重则避免同一问题被重复计分。

2.4 哪些可信,哪些不可信

对象 可信度 依据 怎么用
539 份 docx 高 原始文档层,包含标题、公布令、章节、表格 作为语料源头,重建 chunk
正文 chunk 中 99.1% 原文可核,但章节/表格大量丢失,边界不一致 做第一版检索基线可以;严格评测需修复
query / answer 中高 问题像真实咨询,答案贴着法条;但仓库没交代构建流程 当评测集,别当法律标准答案库
document gold 高(归一化后) 2,812 条非空引用(全集口径),docx 可达 99.89%,JSON 可达 98.76% 归一化后映射到段落/条
document_start / document_end 不可信 2,812 条引用逐条切正文,0 条命中;抽 44 条对 docx 也是 0 条 别用
论文里的 SOTA 数字 不可复现 LegRAG 代码不在仓库,仓库快照早于论文 只当背景,不当复现目标

2.5 五个必须先处理的坑

坑 1:全集和分片不是同一份数据。 以分片为基准:分片多 1 条(引用是空字符串,全集里没有);2,294 条完全相同;180 条在引用切分或格式上不同,其中 176 条只是字面量 \n 的差异,2 条是引号/合并切分(定量包装、肉制品),2 条内容真的不一样(茶叶包装、野生药材)。全集还有 191 条引用带字面量 \n(反斜杠 + n,不是换行),分片里没有。

坑 2:偏移量是摆设。 document_start / document_end 和仓库里的 JSON、docx 都对不上,别用。定位 gold 只能靠归一化后的子串搜索。

坑 3:直接匹配只有 66%,归一化后能到 98.8%。 把 gold 引用直接到“该文档所有段落拼起来的正文”里做子串匹配,只有 1,855 / 2,812 = 66.0% 命中;去掉空白、标点、全半角括号差异后到 92.0%;再去掉字面量 \n 后到 98.8%。剩下的 35 条才是真正对不上的,主要是标题、公布令、章节标题、目录、表格和跨段拼接。

坑 4:2.0% 的引用跨段,仓库的 Recall 会绕过 chunk 边界。 归一化后,2,722 / 2,812 条引用(96.8%)完整落在某一个段落内;55 条(2.0%)跨段;35 条不可达。仓库的 RetrievalRecall 是把检索到的所有片段拼成一个大字符串,再检查每条 gold 是否出现——跨段引用也能算命中。做严格 chunk 级评测时,要自己实现更严格的口径。

坑 5:标题、章节、表格缺得比想象中多。 356 篇有章/节标题,共 3,295 个,JSON 只留下 5 个;539 篇里 450 篇的首行标题、379 篇的公布令在 JSON 里找不到;18 篇表格文档的 4,567 个非空单元格只有 2,029 个(44.4%)进入 JSON。35 条不可达 gold 里,7 条指向章/节标题、9 条指向标题、8 条指向公布令,其余是目录、表格和跨段拼接。

一个最小的 gold 对齐代码:

import json, re

PUNCT = r"[\s\u3000,。;:、!?()()《》〈〉“”‘’\"'·—\-–_//\\,.;:!?\[\]【】<>]"

def norm(s: str) -> str:
# 全集里有字面量 "\n",不清洗会让匹配率凭空掉 30 多个点
s = s.replace("\\n", "").replace("\\r", "")
return re.sub(PUNCT, "", s)

corpus = json.load(open("data/struct_document_539_final.json"))
qa = []
for i in range(1, 6):
qa += json.load(open(f"data/pro_LawQA-{i}.json"))
qa = [x for x in qa if any(r.strip() for r in x["document"])]

paras = {d["标题"]: sorted((int(k), v) for k, v in d["正文"].items()) for d in corpus}
body = {t: "".join(v for _, v in ps) for t, ps in paras.items()}

def locate(title, ref):
nref = norm(ref)
return [k for k, v in paras[title] if nref and nref in norm(v)]

bad = [(x["title"], r) for x in qa for r in x["document"]
if norm(r) and norm(r) not in norm(body[x["title"]])]
print(len(qa), len(bad)) # 2474 36(分片口径;全集是 35)

这段代码用分片跑是 2474 36,用全集跑是 2474 35。分片多出的 1 条,是肉制品那条引用在分片里的拼法和正文对不上。

3. 脚本:能做什么实验

3.1 脚本地图

文件 作用 状态 / 注意
hybrid_rag.py 主流程:Chroma 向量检索 + ES BM25 + bce-reranker 重排 + 生成 默认跑 pro_LawQA-2.json;输出到 data/result/,目录不在仓库
rag_ERNIE.py 用文心 ERNIE 基于已有检索结果生成 只跑前 5 条;路径和 API Key 要改
rag_qwen.py 通义 qwen1.5-110b-chat 生成 只跑前 5 条
rag_Qwen2-7B-Instruct.py 本地 Qwen2-7B(OpenAI 兼容端点)生成 端点写死在代码里
rag_baichuan.py 智谱 Baichuan3-Turbo 生成 API Key 硬编码在 chat_model.py
rag_wenxin.py 百度“文心插件”自带知识库 作为产品基线,不是用本仓库语料
src/bm_25.py Elasticsearch BM25 检索(match on content) 仓库没给建索引脚本
src/chat_model.py 5 个模型客户端封装 含硬编码凭据和内网地址,只读不抄
src/prompt.py RAG 提示词:无关拒答 + 强制输出引用编号 值得借鉴的设计
src/evaluate.py Recall / MRR / BLEU-2 / ROUGE-L 一体评估 主评测脚本
evaluate/evaluate_for_retrivel.py 只算检索 Recall / MRR 可用
evaluate/evaluate_for_class.py 分题型评估 用 == '逻辑推理型' 会漏掉 527 条
evaluate/llm_retrieval_eval.py 解析“参考检索结果:1,2,3”算引用准确率 只统计 isRecall 为 1 的题,口径偏乐观
src/retrieval_recall.py / retrieval_mrr.py 独立检索脚本 对当前数据跑不通:document 是 list,却调用了 .split('\n')
src/qa_Accuracy.py 只算回答质量(BLEU / ROUGE) 不看检索

3.2 仓库自带的评测口径

指标 实现 注意
Recall 把检索片段拼成一个大字符串,去掉空格和字面量 \n,要求每条 gold 都出现 绕过 chunk 边界,跨段引用也能命中
MRR 只看前 5 个片段,按 Σ(1/(rank+1)×命中数)/gold 数 计算 分母是 gold 条数,不是题目数
BLEU-2 jieba 分词,weights=(0.6, 0.4),> 0.4 记准确 只测字面相似
ROUGE-L rouge-chinese,> 0.6 记准确 同上
引用准确率 解析模型输出的“参考检索结果:1,2,3”,映射回片段后与 gold 双向包含匹配 法律场景最该看的一项
分题型 概念解释 / 概括归纳 / 逻辑推理 分开算 注意 class 前导空格

src/prompt.py 里有三个设计值得直接抄:与问题无关就别参考、没有相关内容就拒答、强制输出引用编号。最后一条是引用准确率能自动化的前提。

3.3 跑之前要知道的坑

  • requirements.txt 是 UTF-16 LE + CRLF,pip install -r 会解析失败;chromadb、BCEmbedding、jieba、rouge-chinese、dashscope 等实际用到的包还没写全。
  • 没有 ES 建索引脚本,没有 docker-compose,data/result/ 和 data/evaluate/ 目录也不在仓库里。
  • hybrid_rag.py 的异常分支只 print 不赋值,模型调用失败后仍会用 rag_answer['result'],直接 NameError。
  • reranker 写死 device='cuda:0';没有 GPU 要改。
  • chat_model.py 里有明文凭据和内网地址,抄代码时必须删掉。
  • evaluate_for_class.py 用精确匹配 '逻辑推理型',而数据里是 ' 逻辑推理型',逻辑推理型会被整类漏掉。
  • retrieval_recall.py / retrieval_mrr.py 对当前数据跑不通,能用的是 src/evaluate.py 里的版本。

建议:数据可直接用,脚本只读不抄。 它更像一份“评测口径说明书”,不是能一键复现的工程。

3.4 用这些脚本能做什么实验

实验 怎么做 产出
检索策略消融 BM25 / dense / hybrid / hybrid + rerank 各跑一遍 Recall@k、MRR@k、Doc Recall@k
Embedding / reranker 选型 换 bce 之外的模型,固定生成模型和 prompt 检索增益、显存/延迟成本
生成模型对比 同一份检索结果喂 5 家模型 BLEU / ROUGE / 引用准确率
Prompt 实验 加/减拒答约束、引用编号、无关过滤 拒答率、引用准确率、答案长度
分题型分析 概念 / 概括 / 逻辑分开报告 看推理题短板
语料范围实验 480 篇 vs 539 篇(59 篇当干扰项) 检索难度和召回变化
背题检测 不给检索,直接 closed-book 回答 判断分数来自记忆还是检索

4. 六个值得做的 RAG 评测方向

4.1 检索策略消融:先把“检索”和“生成”拆开

问题:BM25、dense、hybrid、rerank 各带来多少增益?

设计:固定 chunk(先用 正文 的 27,176 块)、固定生成模型,只换检索器;语料分别用 480 篇和 539 篇跑两组。

指标:Recall@5/10/20、MRR@5、Doc Recall@k;再补一个严格 chunk 级 Recall(多引用题分 any / all 两种口径)。

价值:这是最便宜、最能说明问题的基线。仓库自带的 Recall 是拼接口径,容易虚高;自己实现严格口径后,才能看出 rerank 到底有没有用。

4.2 Chunking 对比:直接针对这份数据的最大短板

问题:按条切、固定窗口、small-to-big、条款边界切,哪种更适合法律 RAG?

设计:从 539 份 docx 重建四种语料:

  1. 按条切(保留“第X条”作为 metadata);
  2. 固定 512 字窗口;
  3. 按段切 + 命中后带邻居段;
  4. 章 → 节 → 条层级切分(条款边界)。

指标:Recall@k、MRR、上下文总字数、跨段 gold 命中率、答案引用准确率。

价值:这份数据里 2.0% 的 gold 跨段、3,295 个章节标题只剩 5 个、237 个 chunk 含多条——正好用来证明“切分策略会直接改变召回上限”。这也是最有可能做出差异化结论的方向。

4.3 中文法律域的 Embedding / Reranker 选型

问题:通用 embedding 在法律术语上够不够?reranker 值不值这个延迟?

设计:固定 chunk 和生成模型,比较 3–5 个 embedding(含 bce)和 2–3 个 reranker;分题型看增益。

指标:Recall@k、MRR、nDCG@k;再加每次查询的延迟和显存。

价值:法律 query 很短(中位数 20 字),条款又长又专业,通用模型容易在“相似但不相关”的条款上翻车。分题型报告能看出 reranker 对逻辑推理题是否更有效。

4.4 引用忠实度与幻觉:别只看答案像不像

问题:模型答对了,是真的用了检索结果,还是在背法条?

设计:

  • 用 src/prompt.py 的引用编号约束,算 Citation Accuracy;
  • 加一组 closed-book(不给检索)对照;
  • 把“答案正确但引用对不上”的样本单独挑出来。

指标:引用准确率、答案-BLEU/ROUGE、closed-book 与 RAG 的分差、无依据回答比例。

价值:法律场景最怕“答案看着对、依据是编的”。公开法条很可能在预训练数据里,closed-book 分数不会低;如果 RAG 和 closed-book 差不多,说明你测到的是记忆,不是检索。

4.5 分题型能力评估,尤其是逻辑推理

问题:65% 的概括归纳题会不会掩盖逻辑推理题的短板?

设计:按 class 分三组报告;对逻辑推理题做错误分类(没召回到、召回到了但判断错、引用错)。

指标:分题型 Recall、答案准确率、引用准确率;逻辑推理题单列。

价值:这份数据里逻辑推理只占 21.3%,但它是合同/合规场景最接近真实工作的部分。总分好看不代表推理题能用。

4.6 长文档与上下文预算:RAG 还是整篇喂?

问题:法条很长(docx 最长 10 万字,单 chunk 最长 3,136 字),top-k 拼接和整篇长上下文,哪个更划算?

设计:同一批题跑三种上下文策略:

  1. top-5 chunk;
  2. top-5 + 邻居段;
  3. 整篇法规(或按章截断)直接喂长上下文模型。

指标:答案质量、引用准确率、输入 token、延迟、成本。

价值:这份数据的 chunk 长度分布很极端(中位 95 字,P99 557 字,最长 3,136 字),按“块数”算预算会严重失真。这个实验能给出“法律 RAG 的上下文到底该怎么花”的实测答案。

4.7 如果只做一件事

先做4.1 检索消融 + 4.2 Chunking 对比:用 docx 重建一版条级语料,跑 BM25 / dense / hybrid / rerank 四组基线,同时报严格 chunk Recall 和可达子集分数。这两步做完,你才知道后面换模型、换 prompt 的收益是不是真的。

最小流程:

步骤 做什么 产出
0 冻结版本:分片去空引用,或全集 + 清洗 2,474 条基准
1 从 539 份 docx 重建语料:章 → 节 → 条,补标题/表格 检索库
2 对齐 gold:归一化 + 映射到条/段 id 可判定的 gold
3 跑基线:BM25 / dense / hybrid / rerank Recall@k、MRR@k
4 评生成:ROUGE-L / BLEU-2 / 引用准确率 分题型报告
5 防背题:closed-book 对照 泄漏评估

5. 收尾

Legal-DC 的价值,不是“2,475 条”这个数字,而是它把“中文法规语料 + 条款级引用 + 题型分层 + 一套评测口径”放在了一起。但它是一份需要先修的数据:两个版本、偏移失效、章节和表格大量丢失、chunk 边界不一致。

如果只带走三件事:

  1. docx 是源头,JSON 是便利层;严格评测从 docx 重建,别直接信 正文 的边界。
  2. 先归一化再匹配,别用 document_start / document_end;把 32 条在 docx 可达、在 JSON 不可达的引用单独列出来。
  3. 先跑检索基线,再谈生成;同时报严格 chunk Recall 和可达子集分数,别让数据噪声冒充模型差距。

6. 参考资料

  • 仓库:legal-dc/Legal-DC
  • 论文:arXiv:2603.11772
  • 2026-10-28 内部调研:仓库源码与数据通读
  • 2026-11-02 本地复测:539 篇 docx / 27,176 个 chunk / 2,474(全集)与 2,475(分片)/ 2,812 条引用;逐字覆盖率、章节/表格保留率、chunk 边界、gold 可达率均为逐条核对结果

本站由 卡卡龙 使用 Stellar 1.33.1主题创建

本站访问量 次. 本文阅读量 次.