ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

用RAG为《生态环境法典》搭建可溯源知识库:从切分到防幻觉

2026/10/5 5:01:19 拓冰建站 浏览量
用RAG为《生态环境法典》搭建可溯源知识库:从切分到防幻觉 在法规整合项目里做过几年信息化支撑我对这类“旧法失效、新法上位”的切换再熟悉不过。之前一直维护的那套法条问答系统依赖的全是老的单行法一夜之间失效之后旧模型开始一本正经地背错条款错误还很隐蔽。正好那批单行法整合进了新的《生态环境法典》我花了两周时间把1242条正文全部重新切分、向量化、接进RAG检索链路做成了一个至少目前实测不会记错条文号的知识库。这篇就把整个过程拆开讲包括数据清洗里那些没人提醒你的坑、文本切分的粒度选择、向量检索和重排的参数设置、防止AI编造法条的一套组合拳以及上线之后面对新修条文怎么维护。1. 项目拆解为什么AI搞不定一部法典以及我为什么选RAG而不是微调1.1 大模型背法条是个“看起来很行、一考就露馅”的事很多人以为大模型学了那么多语料背个法条不成问题。实测下来恰恰相反法条这种文本有两个天然缺陷是模型最容易翻车的地方。一是条文之间的引用关系极重。比如某条写着“依照前款规定执行”“参照本法第XX条”这类内部交叉引用对法律人来说是常识对模型来说却是上下文陷阱。它未必知道“前款”到底指哪一款更可能把“第XX条”的内容张冠李戴。二是精确性要求太高。法条不是散文不能“意思对就行”。条款项号错了、时限写错了、适用对象漏了在法律问答里就是严重事故。而大模型的解码机制天生倾向于生成“流畅的、概率最高的”文本不是“准确的、可以逐字对上的”文本。让模型凭记忆输出等于让一个记性不错但偶尔脑补的实习生直接答复正式咨询。所以我从一开始就排除了“把法条喂进模型做微调”这个方向。微调适合学习某种风格、指令遵循能力、特定格式输出不适合作为事实数据库。模型参数里存不了1242条文号的精确映射就算强行塞进去也会出现灾难性遗忘——学会了新法典忘了旧法怎么废止衔接或者几部法内容互相污染。1.2 RAG才是正解带着“教材”开卷考试而不是闭卷背RAGRetrieval-Augmented Generation检索增强生成的思路很直白先根据用户问题把最相关的若干条法律条文检索出来再把这些条文拼进提示词让模型基于给定材料作答。打个比方微调是让学生把整本《生态环境法典》背下来参加闭卷考RAG是允许他带着一本可检索的教材进场翻到哪页抄哪页。只要教材本身是对的、翻页功能检索是准的哪怕模型本身不懂环保法也能给出有依据的答案。这正好对治法条文本“精度要求高、交叉引用多”的痛点。整个项目选型也就清晰了分成四层数据层把法典正文清洗成结构化数据保留编、章、条、款、项、目的层级信息索引层把条文切成适合检索的块做向量化同时保留关键词索引检索层混合检索向量关键词 重排把最相关的条文捞出来生成层用受控提示词让模型只依据给定条文回答强制输出引用出处后面的所有操作都是在往这四层里填细节。2. 1242条法规的数据清洗与条文切分决定成败的地基2.1 从PDF/Word到干净文本那些没人提醒你的隐藏陷阱数据清洗是最枯燥、也最影响最终效果的环节。我从官方渠道拿到的文本主要是PDF和Word版本表面看没问题一处理就冒出各种坑。第一个坑是全角半角字符混用。法规正式文本里经常出现全角括号、全角数字、不间断空格甚至OCR识别后把“条”识别成“倏”。我直接用了一个批量规则统一转半角中文标点除外、去掉零宽字符、合并连续空白符、修复常见的OCR错字映射表。别小看这一步如果字符不干净后面无论关键词检索还是向量召回都会出现奇怪偏差。第二个坑是条款编号的各种写法。文本里有“第一条”“第四十二条”也有“第一百二十三条”还有“第一项”“第二目”。正则表达式必须兼容中英文数字混排比如“第100条”和“第一百条”。我在切分时统一把它们归一化成标准形式作为元数据单独存储方便后面对条文号做精确匹配。第三个坑是附则、附表和过渡条款。法典末尾往往有“本法自X年X月X日起施行”“本法施行前……”这类内容它们也是合法条必须保留但不能和正文混在一起。我在清洗阶段就按编/章结构把正文、附则、附表的边界标记出来。2.2 切分粒度我为什么不用“固定300字切片”而是按条切RAG项目里最常见的坑就是无脑按字符数切文本比如每300字切一块、重叠50字。这种切法在通用文档上还能用在法规领域就是灾难。一条法条往往是完整的法律规范包含“适用条件行为模式法律后果”如果你把它拦腰切成两半检索回来的半截条文根本没法用模型只能看着残缺材料硬答。我在这个项目里坚持了最小单元一整条的原则每条法条单独作为一个chunk保留完整的“第X条”标题。条文特别长的比如超过1000字再按款拆分但每款会带上所属条号同时保留原始整条用于上下文拼接。这样切之后检索单元和用户关心的法律规范单元是一致的检索命中即答对。代价是chunk长度差异很大——短的三四十字长的上千字。实测下来这种不均匀粒度在混合检索里反而效果很好因为短条文语义更聚焦长条文本身自带完整上下文。2.3 扩充“兄弟条文”把交叉引用变成检索优势法条间大量“依照前款规定”“适用本法相关规定”这类交叉引用孤立检索是处理不好的。用户问“违反大气污染防治法相关规定的法律责任”只召回一条“第X条”是远远不够的。我做的处理是给每个条文chunk构建一个“扩展上下文”字段解析条文里的“前款”“第X条”“依照本法”等引用短语把被引用的条文内容追加进去。这样当这个chunk被检中时它自带关联条文模型在生成阶段不需要再靠记忆去脑补“前款规定”到底是什么。这一步我也踩了坑解析引用用简单的正则和规则判断覆盖面大约七成足够实用。不要试图做到100%精确那是一个自然语言理解项目本身规则能做到的不要让模型来做避免引入额外的不确定性。3. 向量化、检索与重排让AI“找对法条”的核心链路3.1 Embedding模型怎么选我试了通用模型和领域模型之后向量化是把文本变成一串数字向量让语义相近的文本在向量空间里距离更近。选Embedding模型时有几个考量维度中文效果、对法律文本的理解度、本地部署还是API调用、向量维度。我陆续试了BGE-M3、通用开源Embedding以及几个专用的法律语义模型。综合排序下来BGE-M3在中文法律文本上的表现最均衡——它支持中英双语对长文本最长8K token支持友好并且自带稀疏向量和稠密向量配合可以实现混合检索而且它支持本地部署1242条法典规模完全不需要上大规模集群。另一个很多人忽略的点是Embedding模型的更新。如果你的知识库用了老版本Embedding模型生成了向量后来换了一个新模型之前所有向量都要重新生成否则新旧向量在同一空间里比对没有意义。我一开始吃过这个亏后来把embedding模型版本固化了并且写进了索引元数据里。3.2 向量检索里两个差点让我翻车的参数TopK和Score阈值单独用向量检索看似简单调参数才是真功夫。我主要调三个参数TopK召回多少条候选条文我设为20。太少了容易漏太多了生成阶段上下文塞不下模型也会被无关信息干扰。Score阈值相似度分数低于多少直接不召回。BGE-M3的相似度分数在不同场景分布差别很大我的经验是先跑一批真实问题看分布再划阈值不要想当然设0.5或者0.7。每条最多引用数控制最终进入提示词的条文数。我限制为6条超过就按重排后的分数截断。这里尤其想说一下Score阈值用了一段时间我发现用户提法言法语时分数往往很高但用户用大白话提问时哪怕意思完全一样分数也会明显偏低。后来我在代码里加了一个“宽松模式当低于阈值时”的兜底如果向量检索结果分数全部偏低就放宽TopK重新检索一次宁可多召回也不要空手而归。3.3 关键词检索为什么必须留着专有名词的胜负手向量检索擅长语义匹配但不擅长精确匹配专有名词。比如“排污许可证”“环境影响评价”“生态保护红线”这些术语在向量空间里可能被语义泛化成别的概念尤其是用户输入时加了个字或者用了简写向量检索很容易漂走。所以我做了混合检索向量召回BM25关键词召回两路结果合并后去重再进入重排。BM25对精确的关键词命中是强项语义泛化是弱项恰好和向量检索互补。这也是为什么我在清洗阶段强调字符干净——BM25对字符的依赖很高OCR错一个字关键词就匹配不上了。3.4 重排为什么要单独用CoT这一步的收益超乎想象混合检索之后会有一堆候选直接拼给模型效果一般。我加了一个重排模型把候选条文重新打分排序把真正相关度高的条文顶上去。重排模型我还是选了和Embedding同一条链路的产品保持生态一致。重排的收益有多大简单说我做过对比不加重排时正确答案出现在前6条的概率约75%加重排后提升到90%以上。这是因为向量召回和BM25看的是“文本相似”而重排模型用的是交叉编码器能把用户问题逐字和每条候选文本做深度交互对语义细节的捕捉好得多。数据库加大之后这个差距还会继续拉大。4. 生成策略与防幻觉机制让AI只说法条里的话4.1 预设一个“受控生成”的System提示词模型生成阶段我用了严格受控的方式不靠模型自由发挥。System提示词把几件事讲死你是生态环境法典的法规问答助手只能依据用户提供的检索片段回答回答时必须逐个引用所依据的条文号不引用就不能下结论如果检索片段不足以回答明确说“未检索到相关法律条文”并建议咨询专业法律人士禁止加入自己的推测、常识、解释除非用户要求引用格式统一为“《生态环境法典》第X条”多个条文用顿号连接别小看这段提示词它是在“告诉模型不许编”和“告诉模型怎么算不编”之间画了一条线。实测中换掉这段提示词之后幻觉率明显提升模型开始出现“创设条文”的情况——它会把刑法、民法的习惯性表达混进环境法答案里。4.2 问答时的两条链路一步检索 vs 多步追问很多RAG项目只做一步检索用户提问检索生成。在这个项目里我发现一步检索有时隔靴搔痒。比如用户问“擅自倾倒危险废物的由哪个部门处罚”先检出来的条文可能是总则里的定义条款罪名罚则反而排在后面。我加了一个判断逻辑如果用了一轮检索后重排第一名的分数仍然低于阈值或者模型判断信息不足这个可以靠提示词让模型自己标记“信息不足”就触发第二轮检索。第二轮检索会引入两路输入一路是用户的原始问题加上第一轮已找到的线索另一路是模型从第一轮结果中提取出来的关键实体关键词。这个“提问改写”的思路让检索系统有了一个粗磨细磨的过程最终命中率明显提升。4.3 每个答案都带“条文号溯源”让模型不敢乱编我在最终答案的格式上做了一个硬性要求每个结论段落后面必须跟上引用条文号比如“〔依据《生态环境法典》第1242条〕”。条文号不是模型自己写的而是从检索链路带过来的真实元数据在提示词里明确告诉模型只能用列表中出现的条文号做引用。这一招在工程上彻底解决了“模型自己编一个法条号”的问题。因为即使模型在正文里编了内容引用的条文号必须来自给定的候选列表而候选列表是真实存在的。当答案需要跨多个条文时模型只能从候选列表的6个条文里组合引用。这个约束把幻觉从根源上压低了。4.4 兜底策略阈值太低就“拒答”绝不硬答人问问题很多时候没有直接对应的唯一法条如果检索出来的东西驴唇不对马嘴生成策略必须能说“不知道”。我在生成阶段加了一个前置条件如果重排后最高分低于设定阈值直接返回“未检索到与该问题直接对应的条文”同时给出用户可选的发送相关关键词建议。这个兜底看起来简单但它保住了整个系统的可信度。法律场景里用户往往拿AI的回答去做判断或决策一次错误答案带来的信任损伤需要十次正确答案才补得回来。宁可让用户重问一次也不能让他拿着一个编造的法条去办事。5. 评估与测试实录我自己建的40题测试集和翻车案例5.1 测试集必须覆盖“条款项、跨章、大白话、近似罪名”四类做RAG项目很多人上线前只测十几个例子感觉“还行”就发版。我这次做了40道题的测试集而且刻意分成四类每类10题条款项精确题比如“第X条第X款规定的处罚幅度是多少”这类题考察对条文层级结构的精确匹配跨章节综合题需要同时引用两个以上不同编/章条文才能回答大白话改写题用户不用法言法语而是用口语、俗称提问近似罪名辨析题两个问题涉及的概念高度相似需要模型区辨适用情况这四类题分别能暴露链路的不同短板。我在第一版上线前第2类和第3类问题的正确率只有六成多原因分别是跨章节时TopK不够、大白话时向量召回分数低。针对这两点我调整了TopK到20并增加二轮检索效果立刻提升到八成以上。5.2 跑分结果和3个典型错误案例复盘改进一轮后我整理了一份粗略的评估表测试类型首版正确率优化后正确率主要优化手段条款项精确题85%95%强化“第X条第X款”精确匹配逻辑跨章节综合题62%82%TopK调大二轮检索大白话改写题60%80%提问改写放宽阈值兜底近似罪名辨析题75%90%引入重排引用约束以下是我重点复盘过的三个翻车案例各有代表性第一个案例用户问“未批先建怎么罚”我首版答案引用了“环评未依法报批”的条文但漏掉了“未经验收擅自投产”的条文。原因是两句话在用户口语里都是“没批就开工”语义接近但适用条件不同。优化后我在数据层把相近但不同适用条件的条文之间增加了一个“易混淆提示”字段当重排模型发现两个条文都进入候选且彼此相似度过高时会强制两个条文都出现在提示词中让模型去辨析。这个做法有效避免了选错条文。第二个案例用户问“跨省转移危险废物需要什么手续”检索链路第一轮命中的是“危险废物转移的管理部门”完全没命中具体的跨省审批程序条文。原因很简单用户问的是“手续”检索的是“审批”“联单”这类词向量空间里差太远。后来给问题加了“从法律义务角度改写”的指令比如自动转换为“跨省转移危险废物的法律义务及批准程序”就顺利锁定了正确条文。第三个案例用户问“什么样的固废算危险废物”系统回答时把“危险废物名录”和“鉴别标准”两条混为一条直接报错了条号。这个属于跨章节内容相近导致的错引。我为此加了一个强制校验从候选列表进提示词时每一条都保留独立的条号占位符并禁止模型合并两个条目的内容到同一个引用中。从那之后这种错引情况大幅减少。5.3 评估后的发现搜索词和法条之间的“翻译层”比模型能力更关键复盘完这四个错误我最大的感受是问题和法条之间不是一个直接的匹配关系中间隔着一层专业翻译。用户用自己朴素的语言描述事实而法条用精确的构成要件表达同样的法律事实这两者之间需要“翻译”。我后来专门做了一版“翻译增强”在检索前先用一轮小模型把用户问题改写成一个更贴近法条词汇的描述。这一步不是让模型回答而是让它把“没批就开工”翻译成“未依法报批建设项目环境影响评价文件擅自开工建设”。这个翻译层带来的准确率提升比换一个更大的生成模型还明显。这也是为什么我一直强调RAG项目80%的精力应该花在检索前的数据加工和查询改写上而不是纠结用多大的参数量模型。6. 上线后的运维、动态更新与经验沉淀6.1 法规不是死的动态更新和版本管理怎么做知识库上线之后最容易被忽略的是法规本身会继续更新。《生态环境法典》施行后配套的行政法规、部门规章、地方性法规还在陆续出台后续一定会有修改决定和修正案。这决定了知识库不能是“一次性工程”。我做了一套简单的更新版本机制每个条目都带一个version字段记录它来自哪个版本的文本更新时按新版本重新清洗、切分、向量化替换旧的chunk被替换的旧chunk不直接删除而是进入“历史版本”索引保持可追溯。用户问的是现行有效规定就只检索“现行”标记的chunk如果要做立法沿革研究就切到历史版本查询。这个操作里有一个我特别想提醒新手的点更新Embedding模型之后不要只更新新chunk的向量所有旧chunk的向量也要同步重算否则新旧向量分布不一致检索结果会漂移。我专门写了一个一次性任务跑完整库全量向量重建才敢切流量。6.2 和传统信息系统的衔接接口设计、权限和审计知识库不能孤立存在。我设计了三个层级的接入方式一是OpenAPI给自研业务系统调用二是Web端检索页面供内部法律顾问和业务人员检索引用三是批量导出适配汇报材料场景。权限设计上环境法业务涉密性不高但仍要做分级匿名用户仅看到条文名称和引用号登录用户可看条文全文和解答结果管理员可进入后台查看数据更新日志。审计要求上每次问答都会记录用户问题、命中的chunk ID列表、召回模型和生成模型的版本号、最终回答、引用条文号。如果有纠纷找过来一条一条可以对账。有一个细节是接口的QPS控制。很多人低估了知识库的查询量一个几十人的业务团队一天就能发起几千次检索调用有时候前端一个页面会触发三四次。做好缓存和限流否则大模型API费用和延迟都会失控。6.3 我把这个项目里最值得复用的经验浓缩成几条第一个经验数据质量永远是第一位的。你给检索系统一个错半个字的法条比不给还糟糕因为它会以极高的置信度给出一个不存在的条文号。清洗占了我整个项目一半以上的工作量回头看完全值得。第二个经验不要过度调优生成模型的提示词先把检索做扎实。我见过很多团队在System提示词里反复修“请认真回答”“请基于事实”但检索链路本身漏得一塌糊涂。提示词只能约束模型的表达救不了检索的盲区。第三个经验用测试集驱动迭代而不是凭感觉上线。每个版本的改动都跑一遍那40道题的测试集看哪些题目从对变错。很多时候一次改动会在别的地方引入回归没有测试集你根本不知道。第四个经验预留“人机协同”的出口。知识库再准也不能完全替代专业律师判断尤其是行政处罚幅度、自由裁量这类涉及个案事实的问题。我在答案底部统一加了一句话“本法条内容仅供参考具体适用请以官方文本和执业律师意见为准。”这句话看似多余但它在合规和信任层面上是最后一道保险。顺着这个方向我接下来准备做两件事一是把提问改写模块改成可配置的查询模板不同业务团队可以传入自己的专业词典二是给知识库增加一个“条文沿革追踪”视图把每次修正案的变动高亮出来。这套在环境法典上验证过的方案应该很快就能复用到其他部门法领域。