自动合规检查这个方向,最近经常能看到一个框架名字:CTRAG。它的全称是 In-Context Retrieval-based Framework for Automated Compliance Checking using LLMs,简单说就是“用大模型做自动合规检查之前,先做上下文检索,再让模型基于检索到的条款做判断”。我先说结论:CTRAG 不是某一个具体模型,也不是一个装好就能用的现成工具,它更像一套解决“法规很长、条文很多、结果必须可引用”这类场景的工程方案。如果你正在做图纸合规、合同条款审查、安全检查表核对、规章制度比对,或者你已经在用 LLM 做规则判断但发现模型经常答非所问、引用条文不准,那这篇文章值得看完。
传统人工检查是拿法规一条一条比对,效率低、容易漏,而且不同人理解还不一样。后来有人做规则引擎,把法规翻译成 if-else,维护成本又特别高。到了 LLM 时代,很多人以为直接让模型“读懂”法规就能判断,真跑一轮才会发现:法规太长模型记不住,条款太多模型会混,没有条文原文兜底模型会编。CTRAG 的核心思路是先把法规做成可检索的条款库,等真正检查某一条内容时,只把最相关的几条条款放进模型上下文,再用提示词要求模型必须基于这些条款给结论、给引用。思路听起来不复杂,落地时却有不少细节值得拆开讲。
1. 先搞清楚 CTRAG 要解决的是哪一类合规检查
1.1 传统合规检查为什么麻烦
合规检查有一个共同特点:判断依据是外部规则,不是检查员自己的经验。建筑图纸要对照设计规范,合同要对照公司标准条款,安全方案要对照行业检查表,医保报销要对照报销目录。这些规则通常有几个特点:
- 篇幅长。一本规范少则几十页,多则几百页,根本塞不进模型上下文。
- 条文多且互相引用。经常出现“按第 X 章第 X 条执行”,只截取某一条会丢掉关键前置条件。
- 更新频繁。规范修订后,旧结论可能直接失效。
- 判断结果必须可追溯。你说不合格,要能指出是哪一条不合格,而不是“模型觉得不行”。
传统规则引擎把法规转成代码,最致命的问题是维护成本。法规改一句话,代码逻辑、测试用例、输出文案可能都要动。人工检查又受制于投入。所以这个场景一直需要一种“规则变化时只改数据,不改主流程”的自动化方案。
1.2 为什么不能直接让 LLM 背法规
LLM 本身并不适合直接做合规判断。原因不是模型笨,而是任务形态不匹配。
第一,模型上下文窗口有限。你把一整本规范塞进提示词,还没开始判断,可能已经超出限制;就算窗口够大,有效注意力也会被无关条文稀释。第二,模型训练时见过的法规和当前版本不一定一致。规范更新之后,模型记忆里还是旧版本,判断结果就会错。第三,生成式模型有“补全倾向”,面对不确定的条款,容易生成看似合理但实际不存在的引用。对合规检查来说,幻觉一个不存在的条款编号,比判断错误更危险。
所以在工程上,合规检查不能用“记忆型”方案,要用“检索支撑型”方案:检查哪一条内容,就临时去找对应的规则原文,把规则原文放进上下文里让模型现场判断。这就是 CTRAG 里 In-Context Retrieval 的含义。检索到的内容作为“现场证据”进入上下文,模型的任务从“回忆规则”变成“根据给定的规则做判断”,幻觉空间被大幅压缩。
1.3 CTRAG 的定位:检索、上下文、生成三件事
从框架设计角度看,CTRAG 把任务拆成三个相对独立的环节:
- 离线阶段:把法规文档加工成带编号、带层级、可检索的条款片段,建立索引。
- 在线检索:拿到待检内容后,找出最相关的若干条条款。
- 生成判断:把待检内容和检索到的条款拼进提示词,让 LLM 输出结构化结论,比如“合规 / 不合规 / 不适用”,并附上引用条款编号和说明。
这种拆法最大的好处是职责分离。法规变了,只更新条款库和索引,不用改判断逻辑;检索不准,只调检索参数,不用重新设计提示词;模型效果不行,可以换模型,主流程基本不动。这也是这类框架在工程上比“单次大提示词”方案更稳的原因。
2. 理解框架核心:条款库、检索器和生成器怎么协作
2.1 条款库构建:把法规变成可检索的结构化片段
很多人做 RAG 时习惯按固定字数切文本,比如每 500 字一刀。但合规场景不建议这么干。法规有天然结构:章、节、条、款、项。最理想的切分单位是“条”,必要时把同一条中的多个款项合并成一个完整片段,因为一条法规往往包含了完整判断条件,切开之后判断语义就断了。
条款库里的每一条记录,至少应该包含这些字段:
- 条款编号,例如 3.2.1,或者 GB 50016-8.3.4 这类带标准号的格式。
- 条款原文。
- 法规来源,例如标准名称、版本号。
- 章节路径,例如“第三章 第二节”,方便追溯。
- 可选的标签,例如“防火”“疏散”“荷载”,用于过滤。
构建索引时,除了把条款原文做向量化,还要把编号、来源、标签作为元数据单独保存。这样检索回来之后,不仅能看到相似度,还能把条款上下文拼接出来,生成时可以附带完整章节路径,避免模型只看到孤立一句话而产生误判。
2.2 检索器:从“搜到相关”到“搜到可引用”
检索器在这个框架里的作用,不只是给模型找“参考材料”,而是给最终结论提供“引用来源”。所以检索质量直接决定判断质量,甚至比模型选型更关键。
常见的检索策略是混合检索:向量检索负责语义相似,关键词检索负责精确命中。比如检查“疏散门宽度”,向量检索能找到“门的净宽不应小于 1.2 米”这种语义相近的条文,关键词检索能直接命中“疏散门”“宽度”这些精确词。两个结果合并去重后,再按相关度取前 top_k 条。
实际工程中我一般会再补两个细节:
- 相似度阈值。检索结果不是越多越好。低于阈值的条款宁可不要,硬塞进上下文只会干扰判断。阈值通常靠一小批人工标注样例来调,不要凭感觉定。
- 重排序。如果条款库很大,第一步检索可以先取 top_k 的 2 到 3 倍,比如取 20 条,再用一个轻量重排序模型或基于关键词命中率打分,最后压缩到 5 到 8 条进上下文。重排序能明显减少“语义相近但不该引用”的噪声。
2.3 生成器:让 LLM 只基于给定条款下结论
检索做得再好,提示词设计不行,模型还是会乱答。合规检查的提示词有两条硬性要求。
第一,角色和任务要写死。比如“你是合规检查助手,只根据用户提供的法规条款判断待检内容是否合规”。这里的“只”字很重要,它会显著降低模型引入外部知识或者自己脑补的概率。
第二,输出格式要结构化。建议强制输出 JSON,至少包含结论、引用条款列表、说明三个字段。如果某条结论找不到对应条款,要允许模型输出“不适用”或者“未找到明确条款”,而不是强行判定。这个“允许不确定”的设计,比逼模型必须给结论更符合合规场景。
提示词模板示意如下:
你是一个合规检查助手。请只根据下面提供的法规条款判断待检内容是否合规。 待检内容: {target_text} 法规条款: {clause_list} 要求: 1. 只能引用上面给出的条款,不得引用其他条文。 2. 输出 JSON,格式为: {"结论": "合规|不合规|不适用", "引用条款": ["条款编号"], "说明": "判断理由"} 请输出:注意,条款列表里要同时有条款编号和原文。如果没有编号,模型就算引用了,后处理也无法核对。
3. 落地前需要准备的输入数据与运行环境
3.1 数据准备:法规文件、待检文件、验收样例
CTRAG 落地前,数据要分成三类准备。
第一类是法规数据,也叫规则源。它决定系统能查什么、能判什么。法规文件常见格式是 PDF、Word、网页。PDF 是最麻烦的,很多扫描版 PDF 需要 OCR,而 OCR 对条文编号的识别经常出错,比如把“1.2”识别成“12”。我建议在进入切片前先做一个清洗环节:人工抽检 PDF 转换后的文本中条款编号是否完整,特别是“条”“款”“项”这类关键结构词。这一步做得越干净,后面检索和引用的坑越少。
第二类是待检数据。待检内容可能是图纸导出的文本、合同条款、检查表、申请材料。它不需要预先入库,检查时才临时输入。但需要注意输入清洗:表格转文本后列关系会丢,PDF 里多栏排版转出来顺序会乱。输入乱,检索就不准,判断自然不对。
第三类是验收样例,也是很多人最容易忽略的。至少准备 30 到 50 条已经人工标注好结论、且注明了对应条款的检查样例,作为评估集。这个评估集用来回答两个问题:检索有没有召回正确条款,判断结论是否与人工一致。没有评估集,你根本不知道参数调得好还是坏。
3.2 运行环境:本地模型还是 API,资源怎么评估
环境选择主要看三个条件:数据敏感程度、调用频率、可用预算。
如果法规和待检数据都不能出内网,就要用本地模型。本地方案至少需要一张能跑推理的 GPU,具体显存取决于模型大小。实践中,7B 到 14B 级别的量化模型,在合规判断这种“不需要太强创作能力”的任务上是常见起点;显存不够就跑更小的模型或多卡分载。嵌入模型相对轻量,很多 300M 到 1B 级别的嵌入模型在 CPU 上也能跑,但批量建立索引时还是建议上 GPU 加速。
如果数据允许走 API,开发速度会快很多。API 方案优先看三点:上下文长度是否覆盖“目标文本 + top_k 条款 + 提示词”的总长;输出是否稳定支持 JSON 格式;单位测试成本是否在可接受范围。不要一开始就追求超大上下文模型,因为 CTRAG 的核心是“少而准的上下文”,把 top_k 从 5 提到 20 并不代表判断更准,反而可能引入更多噪声和更高成本。
原始材料没有给出具体模型和版本,落地时建议先确认你选用的嵌入模型、LLM 以及向量数据库之间的依赖兼容性,尤其是 Python 版本和底层库的对应关系。
3.3 初始参数:先按一套保守配置起步
第一次跑通链路时,参数先保守一点。给一套我常用的初始配置,后续再根据评估集调整:
| 参数 | 初始建议 | 说明 |
|---|---|---|
| 切片单位 | 按条款编号切 | 保持判断条件完整 |
| 检索方式 | 向量 + 关键词混合 | 语义和精确命中互补 |
| 初始 top_k | 8 | 上下文够用且成本可控 |
| 相似度阈值 | 0.2 到 0.3 | 太低会引入噪声,太高会漏召回 |
| LLM 温度 | 0 | 合规判断不要随机性 |
| 输出格式 | JSON | 便于后处理和归档 |
| 单次最大重试 | 3 次 | 处理输出解析失败 |
这套配置不是为了拿最优效果,而是为了让第一次跑通可复现、结果可检查。先看流程通不通,再谈优化。
4. 单条合规检查流程怎么跑通
4.1 流程拆解:切片、索引、检索、拼装、生成、校验
单条检查看起来简单,实际一条完整链路至少包含六个环节:
- 法规切片。按条款切分,保留编号和来源。
- 建立索引。对每条切片生成向量,写入向量库,同时保留元数据。
- 检索条款。输入待检内容,得到关联度最高的条款集合。
- 拼装上下文。把条款编号、原文、来源按固定格式拼进提示词。
- 调用模型生成。得到 JSON 结构的判断结果。
- 校验输出。检查模型引用的条款编号是否真的存在于本轮的检索结果里,不存在就拒绝该输出并重试。
第六步是很多实现里缺失的。模型可以在 JSON 里写一个看起来很合理的条款编号,但这个编号可能根本没进过上下文,也可能根本不存在。后处理必须做引用存在性校验。校验不通过,就让模型重新生成,或者把问题标记为“待人工复核”。
4.2 一个最小链路示例(伪代码)
下面给一个示意性的 Python 伪代码,只用来表达主流程,不代表某个具体库的精确调用方式:
# 示意代码:CTRAG 单条检查主流程 def check_item(target_text, top_k=8, threshold=0.25): # 1. 检索相关条款,返回带 score 的结构化记录 candidate_clauses = retrieve_clauses(target_text, top_k=top_k * 2) # 2. 按相似度阈值过滤,并压缩到 top_k selected_clauses = [c for c in candidate_clauses if c.score >= threshold][:top_k] end # 3. 拼装提示词 clause_list = format_clauses(selected_clauses) prompt = build_prompt(target_text, clause_list) # 4. 调用 LLM,并设置低温度 raw_output = llm_generate(prompt, temperature=0) # 5. 解析和校验输出 result = parse_json(raw_output) assert_references_exist(result["引用条款"], selected_clauses) return result这段伪代码省略了很多细节,比如索引怎么建、检索具体用什么库、超时如何设置,但它把最核心的执行顺序表达清楚了:检索、过滤、拼装、生成、校验。
4.3 输出格式和验证标准
单条检查跑通后,先不要急着接更多输入,先看输出是不是满足这几点:
- 结论字段只能是预设枚举值,比如“合规 / 不合规 / 不适用”,不能出现其他自由文本。
- 引用条款字段里的每个编号,都能在本次检索结果里找到。
- 说明字段能描述“哪个条件不满足导致不合规”,而不是泛泛而谈。
- 同一输入重复跑两次,结果一致率足够高。合规判断不该依赖随机性。
第一次跑通时,我一般会拿 5 条人工标注样例做一次完整测试,逐条看检索结果和最终判断。重点关注检索阶段有没有把真正依据的条款召回到 top_k 里。如果条款压根没被召回,后面模型再强也没用。
5. 从单条到批量:并发、重试和结果归档
5.1 批量任务和单条任务的差异
单条跑通后,很多人直接把单条逻辑塞进 for 循环,然后发现一堆问题:有些请求失败,有些输出解析失败,有些结果写进同一个文件覆盖了,有些跑到一半断了不知道从哪继续。这不是模型问题,而是批量化设计缺失。
批量和单条最大的区别在于:
- 单条失败不影响整体,每条任务要有独立状态。
- 批量必须考虑并发限制,尤其是 API 方案的速率限制和本地方案的显存占用。
- 输出必须按任务隔离,避免并发写同一文件。
- 大批量需要断点续跑,处理到第 500 条中断时,不能从头再来。
这些都属于工程问题,但直接影响合规检查的可靠性。合规检查结果要留痕,任务中断重跑如果产生不同结果,归档就会混乱。
5.2 输出命名、失败重试和断点续跑
我的建议是每个待检对象生成一个唯一任务 ID,可以用原始文件名加哈希后缀。输出目录结构按“任务 ID / 输入副本、结果 JSON、日志”来组织。这样即使同一批任务跑多轮,也不会互相覆盖。
批量循环里要区分三类失败:
- 网络或服务超时,可以重试,一般重试 2 到 3 次。
- 输出 JSON 解析失败,可以带错误信息重新生成一次。
- 引用校验不通过,不要盲目重试,先记录下来,归到“待复核”集合。
断点续跑最简单的做法是:任务开始前先写一个“已完成”标记文件,或者把任务状态记录在 SQLite 里。重启时扫描标记,跳过已完成任务。这类逻辑不复杂,但能省下大量重复调用成本。
5.3 批量的验收指标
批量不是“跑完没报错”就算完成。合规检查至少要统计这几个指标:
- 成功率:成功输出且通过引用校验的任务占比。
- 条款召回率:人工标注的正确条款,是否出现在每条任务的检索结果里。
- 判定一致率:模型结论与人工结论一致的比例。
- 平均耗时和成本:单条平均耗时、总 token 消耗,评估是否可接受。
- 待复核比例:引用校验失败或结论不明确的占比,这个比例不能太高。
如果某个指标明显异常,先看是不是输入数据有问题,再看检索参数,最后才怀疑模型。不要一上来就换模型。
6. 常见问题与排查顺序
6.1 检索不到正确条款
这是 CTRAG 类系统最常遇到的问题。现象是:模型给出的结论看着合理,但引用的条款和人工标注的不一样,或者上下文里压根没有正确条款。
排查顺序我建议这样走:
- 先确认法规切片是否完整。检查目标条款是否真的被切出来,编号有没有被 OCR 弄丢。
- 再确认待检文本是否被清洗干净。表格、多栏、乱码都会影响检索。
- 看检索结果列表。把 top_k 调大到 20,看目标条款是否出现在候选里。如果出现但最终没被选中,可能是重排序或阈值把它挤掉了;如果没出现,问题在切分、向量化或者检索方式。
- 调整嵌入模型。中英文法规混合场景,有些嵌入模型对法律文本的语义理解明显偏弱,换一个领域更匹配的模型往往比调参数更有效。
6.2 模型不按条款回答
模型引用了上下文里不存在的条款,或者明明没有对应条款也要强行判“不合规”,这类问题多半出在提示词和后处理。
先检查提示词里的“只依据给定条款”约束是否写清楚。再用后处理拦截:引用编号不在给定集合内,直接判定该次生成无效。最后看模型本身。小模型在强约束任务上表现往往不稳定,如果提示词和后处理都做对了还经常违规,就要考虑换更大模型或者换一个对齐更好的模型。
6.3 结果不稳定或速度慢
结果不稳定,先看温度。合规判断场景温度应该设为 0,如果设为默认值,结果抖动是必然的。温度已经是 0 还不稳定,可能是提示词里条款顺序每次不同,可以固定条款排序方式,比如按条款编号排序。
速度慢,先看瓶颈在哪里。检索慢,优化向量索引和候选集;生成慢,检查是不是 top_k 太大导致上下文过长;单条请求超时,把超时时间调长,或者拆解目标文本。不要盲目并行,本地 GPU 显存不够时,并行反而会导致 OOM 和卡死。
6.4 通用排查链路
如果整套流程跑出异常,我的习惯是按下面这个顺序排查:
- 先看现象:是报错、卡住、无输出,还是输出格式错误。
- 再看输入:待检文本、法规文本、路径、编码、格式是否正常。
- 再看检索:目标条款是否被召回,相似度分数是否异常。
- 再看参数:top_k、阈值、温度、超时是否合理。
- 最后看依赖:嵌入模型、LLM、向量库、Python 版本之间是否兼容。
这个顺序的核心原则是:先解决数据问题,再解决流程问题,最后才换模型。很多看起来像模型能力不足的问题,最后都查出来是输入没清洗干净或者条款没被切出来。
7. 哪些场景适合 CTRAG,哪些不适合
7.1 适合的场景
CTRAG 最适合的判断依据是“显式条文”的场景。典型特征包括:
- 规则以条款形式存在,有编号、有明确表述。
- 待检对象是具体内容,可以翻译成文本,比如一段描述、一张属性表、一段合同文字。
- 判断结果需要引用具体条款,不能只给结论。
- 法规会更新,需要快速切换到新版本。
- 人工检查量大,希望先自动筛掉明显不合规或明显合格的部分,剩下小部分交给人工复核。
在这些场景里,CTRAG 能把“人工逐条翻规范”变成“机器先检索、再判断、人工复核结果”,价值非常直接。
7.2 不适合的场景
不是所有“规则判断”都适合 CTRAG。如果遇到下面这些情况,建议谨慎:
- 规则本身模糊,依赖行业经验。比如“做法是否合理”“设计是否美观”,没有明确条文可引用,检索检索不出来,模型也只能猜。
- 判断需要综合几十条跨章节条款做权衡。一次性把几十条塞进上下文,效果不一定好,可能需要拆成多步推理。
- 没有标注数据。连人工验收样例都没有,就无法评估检索和判断质量,上线风险太高。
- 法规文本质量极差。扫描件、缺编号、排版混乱,先花时间清洗数据,否则整套框架跑起来都是垃圾进垃圾出。
另外要明确一点:CTRAG 是辅助工具,不是“无人值守审批系统”。合规检查的最终责任通常还是要落在专业人员身上。把系统定位成“自动预筛 + 人工复核”,比定位成“全自动判定”稳妥得多。
7.3 我的建议:先跑小样本,再谈上线
这套框架真正落地时,最该盯住的不是功能列表,而是输入格式、检索召回和失败重试。我更建议把第一次测试拆成三步:先用 30 到 50 条标注样例把检索调通,再用 100 条混合数据把批量流程和结果归档跑顺,最后才考虑接入实时接口或者嵌入业务系统。
踩过几次之后我发现,很多问题不是框架能力不够,而是前置环境和输入材料没有处理干净。法规文本清洗、条款编号规范化、待检文本结构保留,这些看起来不起眼的环节,恰恰决定了 CTRAG 在真实项目里能不能稳定工作。如果你正在评估要不要用这类方案,先把数据样例拿过来,按上面的链路跑一遍小样本,你会比看任何功能列表都更快知道它适不适合你的场景。