☰
把 1242 条法规做成离线可核查的知识库:一个基层执法场景的落地过程
2026/10/20 2:24:58 网站建设 项目流程

把 1242 条法规做成离线可核查的知识库:一个基层执法场景的落地过程

我手上有件真事。2026 年 9 月的一份现场检查记录只有两行字:厂区东侧空地露天堆放表面处理污泥约 3.6 吨(HW17),未采取防扬散、防流失、防渗漏措施;危险废物管理台账 1 月至 8 月缺失。

就这两行。同一个堆放行为,能落到三条不同的条款上,罚款幅度从几万到几十万。差别不在法条背得熟不熟——法条可以回来查——在于那天从现场带回来什么。带回来的东西不够,后面写多少份文书都补不上。

这篇不谈法律适用(那是另一回事),只谈工程:怎么把一套会修订、会打架、会失效的条文,做成离线可用、每条可溯源、冲突能报警的本地知识库。

一、为什么不能用通用大模型直接问

先说清楚不要做什么。

直接把问题丢给在线大模型,得到的答案有三个致命问题:

  1. 不知道依据的施行日期。法律文件修订频繁,模型训练语料里的版本可能已经作废,但它说得一样自信。
  2. 不知道冲突时以谁为准。同一事项,法律、行政法规、部门规章、地方性法规、规范性文件可能都有规定,效力层级和适用规则模型不会自动判断。
  3. 无法溯源。它给出的条款,你没法一键回到原文核对。

我在实际使用里遇到过最危险的情况不是"答不上来",而是答得很像真的——措辞规范、条款编号完整、逻辑自洽,但那个编号对应的内容是另一回事。这种错误在执法场景里是要出事的。

所以知识库的设计目标必须是:每一条结论都能点回原文,并且标明核对日期。

二、数据怎么组织:以"条文"为最小单位

不要把整部法律当一个文档存。检索粒度太粗,引用也不精确。

最小单位应该是条,字段这样设计:

CREATETABLEarticle(idINTEGERPRIMARYKEY,law_nameTEXTNOTNULL,-- 所属文件全称law_levelTEXTNOTNULL,-- 效力层级:法律/行政法规/部门规章/地方性法规/规范性文件article_noTEXTNOTNULL,-- 条号,如 "第三十八条"contentTEXTNOTNULL,-- 条文正文effective_fromTEXT,-- 施行日期effective_toTEXT,-- 失效日期(NULL 表示现行有效)source_urlTEXT,-- 来源链接source_nameTEXT,-- 来源名称(用于双源核对)checked_atTEXTNOTNULL,-- 最后一次人工核对日期version_tagTEXT-- 版本标记,如 "2026-08-15施行版");

几个字段看着普通,但缺了就出问题:

  • effective_to:法规不会主动告诉你它失效了,只会有新文件宣布"某某文件同时废止"。没有这个字段,检索结果会混进废止条款。
  • checked_at:这条最关键。它把"机器里存的"和"人核过的"区分开。检索结果里凡是checked_at距今超过 N 天的,界面上要打标提示复核。
  • law_level:冲突判断的基础。上位法优于下位法、特别规定优于一般规定、新规定优于旧规定,这些规则要靠层级字段才能自动提示。

三、检索:先别上向量库

一个反直觉的建议:一开始不要用向量检索。

法规检索的核心诉求是准确命中条款编号和关键词,不是语义模糊匹配。模糊匹配在这里是缺点——你可能检索"未采取防护措施",结果召回一条语义相近但完全不适用的条款,而且它还排在前面。

先用 SQLite 的 FTS5 做全文索引就够了:

CREATEVIRTUALTABLEarticle_ftsUSINGfts5(law_name,article_no,content,content='article',content_rowid='id');

查询时按"精确条号 > 关键词 > 语义"三级降级:

defsearch(query):# 1) 精确条号:如 "固体废物污染环境防治法 第三十八条"rows=exact_article(query)ifrows:returnrows,'exact'# 2) 关键词命中(FTS5,带 BM25 排序)rows=fts_search(query,limit=10)ifrows:returnrows,'keyword'# 3) 实在没有才做语义召回,且必须标注"语义匹配,需人工核对"rows=semantic_search(query,limit=5)returnrows,'semantic'

语义召回的结果必须打标,不能和精确命中混在一起展示。这是防止"像真的"错误的第一道闸。

四、双源核对:一条条文至少查两个来源

这是整个库里最费人工、也最不能省的一步。

规则很简单:同一条条文,必须从两个独立来源核对一致后才能入库。来源可以是官方发布平台、正式出版的汇编、上级机关下发的文本。两个来源不一致时,不入库,标记冲突,人工介入。

defcross_check(law_name,article_no):a=fetch_source('official',law_name,article_no)b=fetch_source('compiled',law_name,article_no)ifnotaornotb:return{'status':'MISSING','detail':'至少一个来源缺失'}ifnormalize(a)!=normalize(b):return{'status':'CONFLICT','a':a[:200],'b':b[:200]}return{'status':'OK','content':a}

normalize要做的事:去掉全半角差异、统一空格、统一引号、去掉页码和版式符号。不做归一化,两个来源会因为排版差异永远不一致。

我在实际整理中,第一轮核对的冲突率不低——主要是修订版和旧版混在一起。这些冲突本身就是最有价值的产出:它告诉你哪些条文最近动过。

五、冲突检测:让库自己报警

入库之后要有一个定时任务扫全库,找出"同一事项、不同条文、结论不同"的情况:

deffind_conflicts():"""找出可能适用同一行为、但规定不一致的条文组"""groups=group_by_keyword(load_all())# 按行为关键词聚类out=[]forgingroups:levels={r['law_level']forring}iflen(levels)>1:# 存在不同效力层级 → 提示按层级适用out.append({'group':g[0]['keyword'],'items':g})returnout

输出不是"答案",是需要人工确认的清单。系统会提示:这一组里存在上位法和下位法,适用时优先看哪一条,但不替你做决定。

这个边界很重要。执法判断的责任主体是人,工具只能保证"你看到的是全的、是最新的、是可溯源的"。

六、离线:为什么不能依赖网络

两个现实原因:

  1. 现场没网,或者信号很差。检查现场在厂区、在山区,这是常态。
  2. 联网查询不可复现。今天查到的结果,明天网页改了就变了。办案卷宗里引用一个网页链接,半年后链接失效,这份卷宗的证明力就有瑕疵。

所以整个库是本地 SQLite 文件,检索、比对、导出全在本地完成。导出引用时写的是文件全称 + 条号 + 版本标记 + 核对日期,不写网址。

defcitation(row):returnf"《{row['law_name']}》{row['article_no']}({row['version_tag']},核对日期{row['checked_at']})"

七、复盘:最有价值的不是检索,是"带回来什么"

回到开头那个只有两行字的检查记录。

知识库能解决的问题是:这两行字对应的行为,可能适用哪些条款、条款现行是否有效、条款之间效力如何排序。

知识库不能解决的问题是:那天现场有没有拍照、有没有测量、有没有固定证据。这些没做,后面写多少份文书都补不回来。

所以这套东西真正的用法,是在去现场之前先看一遍:这个类型的违法行为,通常需要固定哪几类证据、对应哪些要件、常见争议点在哪。把这张清单打印出来带在身上,比回来再查有用得多。

工具的价值不在"事后能查到",在"事前知道该带什么回来"。

八、一句话总结

  • 最小单位存到条,不是存到部
  • 每条必须有effective_to和checked_at
  • 检索先精确后语义,语义结果必须打标
  • 入库要双源核对,冲突不自动合并
  • 冲突只报警、不裁决
  • 全程离线,引用写版本和日期、不写网址

这些规则看着繁琐,但每一条都对应一次真实的翻车。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询