ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

CTRAG框架:用检索增强实现可追溯的法规合规检查

CTRAG框架:用检索增强实现可追溯的法规合规检查 CTRAGIn-Context Retrieval-based Framework是一类面向自动化合规检查的检索增强框架核心思路是让大语言模型不靠记忆去判断文档是否合规而是先从外部法规知识库中检索相关条款再把条款和待查内容一起放入提示词上下文最后输出带依据的合规结论。合规检查在建筑审查、合同审核、安全报告评审、技术规范核对等场景中非常常见过去主要靠人工逐条比对成本高、漏检率不低直接用大模型判断又容易出现幻觉和依据不透明的问题。这篇文章从一个可复现的实现视角切入拆解 CTRAG 的架构、检索与上下文构建、推理验证、评估指标和生产落地注意点并给出一套最小可运行的代码示例。阅读本文需要了解基本的 Python、向量检索概念并接触过 LLM API 调用如果你正在做“给大模型接法规库、让系统输出可追溯判断”的项目下面内容可以直接作为起点。1. 理解 CTRAG 之前先看合规检查为什么难做1.1 合规检查到底在查什么合规检查在工程领域的定义很直接把一份待检文档设计说明、合同条款、安全报告、技术方案与一组规则国家标准、行业规范、企业内部制度逐条比对找出违反规则的位置、说明违反了什么规则并给出整改建议。这套流程有几个无法回避的特征规则数量大。一本规范动辄几百条一个项目可能同时适用十几本规范总条款数很容易超过几万条。规则结构化程度低。法规文本是长段落加编号层级不是现成的结构化标签很难直接塞进数据库按字段检索。规则会更新。新版本发布后旧条款作废、新条款生效检查依据必须跟着换。结论必须可追溯。合规检查不是写作文结论需要能指出依据哪一条、哪个版本、哪一版的表述。这些特征决定了单纯靠大模型“读一遍就判断”的方式走不通需要一套能把规则管理、检索、推理、追溯串起来的框架。CTRAG 的定位正是这个。1.2 直接让大模型判断的三个典型问题第一次尝试用 LLM 做合规检查时多数人会直接把规范文档粘进提示词然后让模型输出“是否违规”。这种方式在 demo 阶段看起来能跑深入验证后会暴露三个问题。第一上下文窗口装不下完整规则集。一套中等规模的法规库可能有几十万字即使使用大窗口模型把全部规则放入提示词也不现实而且大部分规则与当前待检片段无关放进去只会稀释注意力。第二幻觉导致依据不可信。大模型在模糊记忆下会“创造”条款号比如把 GB 50016 的内容安到 GB 50974 上甚至凭空造出一个条款编号。合规检查场景对错误依据的容忍度极低。第三结论不稳定。同样的输入在不同温度、不同上下文顺序下可能给出不同结论。没有证据约束时模型会基于“看起来合理”而不是“条款确实这么写”来做判断。这三个问题指向同一个解决方案把判断所需要的证据从模型记忆里搬出来变成可检索、可核对、可展示的外部上下文。这正是 RAG 思想的用武之地也是 CTRAG 的核心出发点。1.3 CTRAG 的核心思路检索拉近规则上下文约束推理CTRAG 的名字里有两个关键限定词分别对应两个设计决策。In-Context Retrieval 表示检索是以“构造当前判断所需的上下文”为目标的。它不是一次性把法规库全部灌入模型而是针对每个待检片段先召回最相关的若干条款再把这些条款作为上下文交给模型。这样模型看到的是“当前片段 相关证据”而不是“全部规则”。Retrieval-based 表示结论并非模型凭空生成而是建立在被检索到的证据之上。框架在后处理阶段还会校验模型引用的条款是否存在、是否在知识库中命中从机制上阻止幻觉条款进入最终报告。把两个限定词合在一起就得到 CTRAG 的完整工作闭环对待检文档分段对每个片段检索相关法规条款将条款作为上下文交给 LLM 做受控推理模型输出 JSON 格式的结论和引用编号系统再对引用编号做二次校验。后面的章节会围绕这个闭环逐步展开。2. CTRAG 的整体架构与模块划分2.1 从传统 RAG 到 CTRAG多出来的“上下文工程”RAGRetrieval-Augmented Generation通常指“检索 生成”即先从知识库检索文档片段再把片段拼进提示词让模型作答。CTRAG 沿用了这个底层结构但把重点放在合规场景特有的约束上检索对象不是普通文章而是带编号、带版本、带章节层级的法规条款提示词里必须明确告诉模型“只依据给定条款判断不能补充条款之外的常识”模型输出必须结构化且必须包含引用条款编号供系统校验校验环节不能省略因为合规报告的每个“依据”都要经得起复核。所以CTRAG 实现时最花精力的往往不是模型本身而是“检索结果怎么组织成上下文”“上下文怎么约束模型输出”“输出怎么验证回写”。这三个环节合起来可以叫上下文工程。2.2 五个核心模块的职责与数据流在工程实现上一个 CTRAG 系统通常拆成五个模块。模块主要职责关键产物法规知识库存储法规条款、元数据、版本关系JSON/数据库中的条款记录文档解析与分块把待检文档切成可独立判断的片段带段号、页码的片段列表检索器对每个片段召回相关法规条款候选条款列表及相似度分数上下文构建器把片段和条款组装成规范提示词完整 prompt 文本推理与校验器调用 LLM 并校验输出格式和引用结构化检查结论数据流如下待检文档进入解析模块按段落或条款切分为片段每个片段交给检索器检索器在法规知识库中找到 Top-K 条款上下文构建器把片段、条款、输出格式说明拼成提示词LLM 返回 JSON校验器检查 JSON 结构、裁决字段是否合法、引用条款是否存在于知识库最后汇总成检查报告。这一段数据流里最容易出问题的是第三步到第五步之间的衔接尤其是提示词拼接和 JSON 解析后面会专门讲。2.3 三个阶段的取舍原则分阶段设计之后每个阶段可以有自己独立的优化目标这一点对工程落地很重要。检索阶段的目标是召回优先。宁可多召回一些弱相关条款也不能漏掉真正约束当前片段的条款。召回不足时模型没有证据可用只能凭常识输出这比召回过多更危险。上下文构建阶段的目标是精准优先。进入提示词的条款越少越好只要够用就可以。因为条款越多token 成本越高模型也越容易被不相关条款带偏。推理与校验阶段的目标是可审计优先。输出必须结构化、引用必须可校验不能接受“模型说违规但说不清依据”的结果。如果模型引用了一个知识库中不存在的条款编号系统应该直接标记为校验失败并重新推理而不是把它写进报告。这三个目标彼此有冲突检索希望召回多上下文希望越精越好。解决冲突的方法不是靠感觉调参而是靠一组带标注的评估集用数据决定 Top-K 取多少、要不要重排、温度设多少。评估集的构建方法在第 5 章给出。3. 环境准备与最小可运行实现3.1 依赖选型这里选择一套轻量、容易在普通开发机上跑起来的技术栈用于演示 CTRAG 的核心流程。Python 3.10 或更高版本sentence-transformers加载嵌入模型生成法规条款和待检片段的向量FAISS本地向量索引方便检索 Top-Kopenai 或其他兼容 OpenAI 协议的 SDK调用 LLMpydantic解析和校验模型输出。安装命令python -m venv .venv source .venv/bin/activate pip install sentence-transformers faiss-cpu openai pydantic注意如果原始项目没有锁定嵌入模型版本落地前要先确认所选模型在本机可下载、与 Python 版本兼容。sentence-transformers 首次加载模型时会从模型仓库下载权重建议在稳定网络环境下执行。3.2 法规知识库的数据结构法规条款作为检索与校验的共同基础结构设计必须同时满足两个需求能被向量化检索也能被精确编号校验。推荐每个条款记录包含以下字段。[ { id: GB50016-2014-8.3.1, doc_id: GB50016-2014, doc_name: 建筑设计防火规范, chapter: 第8章, article: 8.3.1, content: 除本规范另有规定外下列建筑应设置自动喷水灭火系统……, keywords: [自动喷水灭火系统, 建筑, 设置要求], version: 2014, effective_date: 2015-05-01, status: active } ]id 字段是最关键的设计。建议写成“规范编号-版本-条款号”的组合格式这样后续校验引用时可以直接用 id 做精确匹配不需要解析长文本。status 字段用于标记条款是否仍然有效失效条款在检索时应当被过滤否则模型会引用过期的依据。3.3 最小可运行代码从加载条款到输出结论先实现法规知识库的索引构建与检索。import json import faiss import numpy as np from sentence_transformers import SentenceTransformer class RegulationKB: def __init__(self, model_name: str BAAI/bge-small-zh-v1.5): self.encoder SentenceTransformer(model_name) self.index None self.clauses [] def build_index(self, clause_file: str): with open(clause_file, r, encodingutf-8) as f: all_clauses json.load(f) self.clauses [c for c in all_clauses if c.get(status) active] texts [c[content] for c in self.clauses] embeddings self.encoder.encode(texts, normalize_embeddingsTrue) dimension embeddings.shape[1] self.index faiss.IndexFlatIP(dimension) self.index.add(np.asarray(embeddings, dtypenp.float32)) def retrieve(self, query: str, top_k: int 5): q_vec self.encoder.encode([query], normalize_embeddingsTrue) scores, indices self.index.search( np.asarray(q_vec, dtypenp.float32), top_k ) results [] for score, idx in zip(scores[0], indices[0]): if idx 0: continue results.append((self.clauses[idx], float(score))) return results这段代码里有两个容易出错的地方。第一build_index 时只处理状态为 active 的条款避免过期条款参与检索同时 self.clauses 与索引保持一一对应后续按 idx 取条款不会错位。第二使用归一化向量和 IndexFlatIP计算的是余弦相似度适合中文法规文本这类没有固定长度的语料。接着实现提示词构建和 LLM 调用。import json from openai import OpenAI PROMPT_TEMPLATE 你是合规审查助手。下面给出了若干法规条款以及一段待审查的文档片段。 请只依据给出的条款内容进行判断不得补充条款之外的规则。 法规条款 {clauses} 待审查片段 {segment} 请输出 JSON格式如下 {{ verdict: pass 或 fail 或 review, violations: [违规点描述], cited_clauses: [引用条款id], reason: 判断依据 }} def check_segment( client: OpenAI, segment: str, clauses: list, model: str gpt-4o-mini, temperature: float 0.1, ) - dict: clause_text \n.join( f[{c[id]}] {c[content]} for c in clauses ) prompt PROMPT_TEMPLATE.format(clausesclause_text, segmentsegment) response client.chat.completions.create( modelmodel, temperaturetemperature, messages[{role: user, content: prompt}], ) content response.choices[0].message.content return json.loads(content)这段实现体现了 CTRAG 的几个关键决策temperature 设得很低保证同一个片段重复检查时结论尽量一致提示词明确限定“只依据条款判断”压低模型自由发挥的空间输出格式用 JSON 约束方便后续校验。最后是引用校验。def verify_verdict(verdict: dict, kb: RegulationKB) - tuple: if verdict.get(verdict) not in {pass, fail, review}: return False, verdict 字段不合法 known_ids {c[id] for c in kb.clauses} for cid in verdict.get(cited_clauses, []): if cid not in known_ids: return False, f引用了知识库中不存在的条款: {cid} return True, ok三个函数合起来就是一个最小闭环build_index 建库retrieve 取证据check_segment 推理verify_verdict 校验。这份代码的作用是演示主链路真正生产使用还需要补充日志、重试、超时、并发控制和评估逻辑这些在第 5 章和第 7 章展开。4. 三个关键设计分块、检索、上下文预算4.1 法规文本怎么分块法规文本的分块策略直接决定检索质量。常见的做法有两种按条款分块和按固定长度分块。按条款分块是指以条款编号为切分边界一个条款对应一个检索单元。这种方式的优点是语义完整、编号天然存在、引用校验方便缺点是部分条款很长可能超过嵌入模型的 max_seq_length。按固定长度分块是先把文本切成固定大小的 chunk再设置重叠区域。优点是不受条款长度限制缺点是会切断条款语义而且一个条款可能被切成多段检索时容易遗漏或重复。策略优点缺点适用场景按条款分块语义完整便于引用校验长条款需二次切分法规条文结构清晰时优先固定长度重叠实现简单长度可控语义断裂编号关联麻烦原始法规未结构化时兜底推荐顺序是优先走结构化解析尽量拿到条款级粒度只有条款过长时才对该条款内部做固定长度切分并在切分后的片段中保留条款 id避免失去追溯能力。4.2 检索策略向量、BM25 还是混合检索器负责在法规知识库中找到与当前片段相关的条款。三种常用方案各有取舍。方案原理优势局限向量检索嵌入模型将文本映射为向量按余弦相似度召回语义召回强能处理同义改写对精确编号匹配较弱依赖模型质量BM25基于词频和逆文档频率计算关键词匹配分精确词汇匹配稳定可解释同义词、改写词可能漏召混合检索向量与 BM25 分数加权融合兼顾语义与关键词召回更稳需要调权重工程稍复杂在合规场景中单纯向量检索有一个典型问题法规表述和待检文档的写法常常差异巨大。规范里写“应设置自动喷水灭火系统”设计文档里可能写“配置喷淋系统”语义相近但词汇不同向量检索能处理反过来如果文档里出现了规范编号“GB50016-2014”BM25 对精确编号的匹配能力就更直接。因此混合检索是更稳妥的默认选择特别是在项目初期还没有足够数据做向量模型调优时。4.3 上下文窗口与 token 预算CTRAG 中每一轮的 token 消耗主要由三部分构成提示词模板、召回的条款、待检片段。上下文构建器需要控制的是“召回条款总长度 待检片段长度”不要超过模型上下文窗口的安全比例。推荐的做法不是等到超限再截断而是在构建前先估算。可以给每条召回条款的 content 设置一个上限长度超限时截断并保留 id待检片段也可以按长度切分保证单次检查的输入稳定。参数含义常见取值调大的影响调小的影响top_k每个片段召回条款数3 到 8证据更全token 成本上升模型可能被无关条款干扰成本降低但可能漏掉关键依据条款最大长度单条条款截断阈值500 到 1000 字保留更多原文上下文变大上下文变小但可能丢失条款尾部限制条件temperatureLLM 采样温度0 到 0.2输出更多样但合规判断不稳定输出更稳定更适合规则判断top_k 是最需要调优的参数。k 太小最关键条款进不来k 太大模型注意力被稀释。不要凭经验固定一个值应该在一个小规模评估集上对比不同 k 值下的端到端准确率再决定最终取值。5. 运行验证与结果评估5.1 先构造一个小型评估集CTRAG 系统上线前必须回答一个问题变化 retriever 或 prompt 之后合规判断到底变好还是变坏。这个问题只能靠带标注的评估集回答。最小评估集可以是一组 JSONL 数据每一行包含三个字段{segment: 地下二层车库未设置自动喷水灭火系统。, expected_verdict: fail, expected_clauses: [GB50016-2014-8.3.1]} {segment: 项目采用耐火等级一级的建筑构件。, expected_verdict: pass, expected_clauses: []} {segment: 部分疏散走道两侧墙体装修材料为B1级需要进一步核实具体位置。, expected_verdict: review, expected_clauses: []}评估集不需要一开始就很大但必须覆盖三类情况明确违规、明确合规、边界模糊。边界模糊样本用于测试模型是否会输出 review而不是硬判 pass 或 fail。5.2 从三个维度看评估结果只盯“判断对不对”不够CTRAG 的评估至少要从三个维度进行。第一判断准确率。把模型输出的 verdict 与 expected_verdict 对比计算精确率、召回率和 F1。其中 fail 判成 pass 属于高风险漏检评估时要单独统计。第二引用正确率。检查模型输出的 cited_clauses 是否包含 expected_clauses以及是否引用了知识库之外的编号。引用正确率越高报告越能经得起复核。第三格式合规率。JSON 能否解析、字段是否齐全、verdict 是否在合法枚举内。格式错误会直接中断后续流程在批量处理场景中尤其需要关注。python eval_ctrag.py --data eval_set.jsonl --kb regulations.json --top-k 5评估脚本应该输出每一条样本的 verdict、引用、格式状态并汇总指标。这些指标应该成为后续每次改动 prompt、检索权重、top_k 时的统一对比基准。5.3 日志与追踪每个结论都要能复盘生产环境中合规检查结论会被下游流程使用因此系统必须具备追溯能力。每次检查至少记录以下信息待检片段来源和段号检索到的条款 id 和相似度分数最终进入提示词的条款 id 列表模型返回的原始输出校验器是否通过最终结论和时间戳。推荐把日志写成结构化 JSON 行方便后续检索和分析。日志不只是排错工具也是评估集扩充的来源当人工复核发现模型误判时把该样本加入评估集持续回归能有效防止同类问题再次出现。6. 常见问题与排查路径6.1 问题现象、原因与处理对照CTRAG 的报错和异常通常集中在 JSON 解析、检索召回、引用校验三个环节。下面的表格整理了实际开发中高频遇到的问题。问题现象常见原因检查方式处理建议LLM 输出 JSON 解析失败输出中混入了 markdown 代码块或解释文本打印原始 response.choices[0].message.content在解析前剥离 json 标记或要求模型只输出 JSON检索结果明显不相关法规文本未分好块或嵌入模型与领域不匹配单条打印 retrieve 结果看相似度分数改为按条款分块尝试更换嵌入模型或加 BM25 混合模型引用了不存在的条款校验环节缺失或条款
返回列表