CTRAG实战:用RAG+大模型实现可追溯的自动化合规检查
2026/8/28 17:38:18 网站建设 项目流程

在许多工程与业务场景中,“合规检查”(Compliance Checking)都是一项高频率、高成本、高责任的工作。比如建筑项目中检查设计图纸是否满足消防规范,制造企业中核对设备维护记录是否符合安全标准,金融场景中审核合同条款是否违反监管要求。传统做法依赖人工逐条比对,效率低且容易遗漏;而直接让大语言模型(LLM)读文档、给结论,又常常出现“幻觉”、格式不稳定、条款引用错误等问题。

本文要讨论的CTRAG(In-Context Retrieval-based Framework for Automated Compliance Checking using LLMs),正是为了解决这一矛盾而提出的框架思路。它将检索增强生成(RAG)、上下文学习(In-Context Learning)与合规检查任务结合起来,用“先检索、再推理、后判断”的方式,让 LLM 在回答合规结论时能追溯规则来源,并输出相对稳定的结构化结果。

下面我会从概念原理讲起,再到代码级实现,逐步拆解这套框架的完整链路。无论你是算法工程师、后端开发,还是正在做文档智能化的技术选型,都能从中获得可落地的参考。

1. 背景与核心概念

1.1 合规检查到底难在哪里

合规检查的核心目标是:判断一条事实记录或一份文档,是否满足一组规则或标准。这里的“规则”可以是法定条文、企业制度、行业规范、合同条款等。传统做法可以抽象成两条路:

  • 规则引擎路线:把合规规则翻译为 if-else 逻辑、决策表或 Drools 等规则表达式,由程序自动执行。优点是结果可控,缺点是规则经常变化,维护成本极高。
  • 人工审核路线:由业务专家逐条比对。优点是理解能力强,能处理模糊语义,缺点是速度慢、人员培训周期长、标准不统一。

LLM 出现后,很多人尝试直接用大模型做合规判断。比如把规则和待检查文本一起放进 Prompt,让模型回答“是否合规”。这种方式简单粗暴,但存在三个致命问题:

  1. 规则文本过长:合规条款往往很多,全部塞进 Prompt 会超出上下文窗口,或导致模型注意力分散。
  2. 知识过时:合规规则会修订,模型训练数据中的旧规则可能产生误导。
  3. 幻觉与不可追溯:模型可能给出一个看似合理但没有依据的结论,用户无法核验它对在哪、错在哪。

这些痛点正是 CTRAG 框架要解决的。

1.2 什么是 CTRAG

CTRAG 的全称是Contextual/In-Context Retrieval-Augmented Generation,直译过来是“基于上下文检索增强生成”或“上下文检索增强框架”。它属于 RAG(Retrieval-Augmented Generation,检索增强生成)在特定垂直任务上的改进变体。

可以把 CTRAG 理解为:

在调用大模型进行合规判断之前,先从规则知识库中检索出与当前待检文本最相关的若干条规则,再将这些规则作为上下文注入 Prompt,让 LLM 在“知道该引用哪几条规则”的前提下给出结论。

与通用 RAG 相比,CTRAG 更强调In-Context的作用——即检索出来的内容不是简单地拼在 Prompt 后面,而是经过结构化组织,让模型能够把“待检查项”与“规则条款”一一对应,并明确输出推理过程。

1.3 CTRAG 的典型应用场景

  • 建筑工程合规:判断施工方案、设计图纸说明是否满足消防、结构安全等规范。
  • 企业制度审计:比对新发布的内部管理制度与国家法规、行业标准。
  • 合同风险审查:检查合同中的付款条件、违约责任条款是否符合公司模板要求。
  • 数据安全合规:判断数据处理流程描述是否满足《数据安全法》《个人信息保护法》等要求。
  • 供应链合规:审核供应商提交的资质文件是否满足准入规则。

凡是“事实记录 + 规则文本 + 人工判断”重复性高的场景,CTRAG 都有落地空间。

2. 相关技术原理拆解

2.1 从 RAG 到 CTRAG:两种思维的区别

RAG 的核心流程是:user query → retrieve → augment → generate。它把检索到的文档片段作为参考信息,帮助生成更准确的答案。CTRAG 在这个基础上,把目标从“生成答案”调整为“进行合规判断”,因此需要在 Prompt 设计和结果约束上做几层改造。

维度通用 RAGCTRAG
检索目标最大相似度的文档片段与合规判断相关的规则条款
上下文组织通常是“问题 + 文档片段”“待检对象 + 规则集 + 判断要求 + 输出格式”
输出结果自由文本答案结构化合规结论(通过/不通过/待确认)
核心指标答案准确率、相关性精确率、召回率、条款引用正确率
可解释性强,需要返回引用条款编号

2.2 In-Context Learning 为什么重要

In-Context Learning(上下文学习)指不更新模型参数,仅通过 Prompt 中的示例和提示文本,让大模型快速适应新任务的能力。CTRAG 利用这一点,把合规判断任务转换成一种“少样本分类+推理”问题。

举个例子,在 Prompt 中给出一个示例:

规则:建筑高度大于 27m 的住宅建筑属于高层民用建筑,应设置消防电梯。 待检信息:某住宅楼建筑高度 35m,设计文件中未设置消防电梯。 结论:不通过,违反规则编号 GB-2023-015。

模型看到这样的示例后,会模仿这种推理链路处理后续输入。这么做的好处是,不需要为每类合规规则重新训练模型,只要调整 Prompt 示例就能适配新业务。CTRAG 把“检索”当作启动上下文学习的触发条件——先检索出相关规则,再放入上下文,让示例和真实规则能对应起来。

2.3 合规检查的核心流程抽象

无论具体业务如何,自动化合规检查都可以抽象成下面五步:

  1. 输入解析:把待检文档转成结构化或半结构化文本段落。
  2. 规则匹配:从规则库中检索候选规则。
  3. 上下文构造:将候选规则与输入文本组织成 Prompt。
  4. 推理判断:由 LLM 输出判定结论和依据。
  5. 结果校验:检查输出格式,解析出结构化结果,必要时做人工复核。

CTRAG 就是对这个流程的系统化实现。

3. 环境准备与实验设计

3.1 运行环境

CTRAG 本身不是一个需要安装的第三方包,而是一种实现方法。实际开发时,建议环境如下:

  • 操作系统:Linux / macOS / Windows 均可,生产环境推荐 Linux。
  • Python:3.9 及以上。
  • LLM API:OpenAI 兼容接口、国内大模型 API,或通过 Ollama、vLLM 等部署的开源模型。
  • 向量数据库:Chroma、FAISS、Milvus、Elasticsearch 均可。
  • 嵌入模型:如 text-embedding-3-small、bge-large-zh、m3e-base 等。

需要强调的是,版本必须根据你的项目实际情况调整。本文示例采用常见的 OpenAI 兼容接口和 Chroma 作为向量库,重点演示框架思路,不绑定某一家的商业产品。

3.2 数据准备

为了让示例可以运行,需要准备两类数据:

  • 规则库:一组带编号的合规条款,每条记录包含“条款编号”“条款内容”“适用范围”。
  • 待检数据:若干条业务描述,例如“某住宅楼设计高度 35m,未设置消防电梯”。

在真实项目中,规则库往往以 Excel、Word、PDF、数据库表等形式存在。建议先统一清洗为 JSON 格式,再写入向量库。

3.3 评测指标

合规检查的结果不要只看“准确率”,建议至少关注以下指标:

指标含义
精确率(Precision)被判为不合规的样本中,真正不合规的比例
召回率(Recall)实际不合规的样本中,被模型找出的比例
条款引用正确率模型引用的规则编号与人工标注一致的比例
结构化解析成功率LLM 输出能被程序稳定解析为 JSON 的比例
延迟单条检查的平均耗时

4. CTRAG 框架设计与核心实现

4.1 框架整体流程

CTRAG 的整体流程可以画成如下阶段:

待检文本 │ ▼ 【输入解析】 → 如果文档是 PDF/Word,先转成纯文本,再切段 │ ▼ 【向量化】 → 将待检段落转成向量 │ ▼ 【规则检索】 → 在规则向量库中查询 Top-K 相似条款 │ ▼ 【上下文构造】 → 组装 Prompt:任务描述 + Few-shot 示例 + 检索到的规则 + 待检文本 │ ▼ 【LLM 推理】 → 输出 JSON 格式判定结果 │ ▼ 【结果解析】 → 提取判定结果、引用条款、风险说明

这个流程的意义在于:每一步都是可插拔的。检索可以换向量库,LLM 可以换模型,结果解析可以换规则表达式。

4.2 知识库构建

规则库构建要考虑两个层次:

  • 存储层:原始规则以结构化 JSON 保存,方便人工维护。
  • 向量层:将规则内容切分后向量化存入向量库,供相似度检索。

规则切分是重点。不要把一条规则拆得太碎,也不要把多条规则混在一起。建议以“一条完整约束”为一个最小检索单元。

4.3 检索模块

检索模块负责从规则库中找出与待检文本最相关的规则。常用方法是:

  1. 把待检文本 embedding 成向量。
  2. 在向量库中执行相似度检索。
  3. 返回 Top-K 条规则,K 一般取 3~8。

这里有个容易犯的错误:直接对整个大文档做向量检索,效果往往不好。更好的做法是:

  • 检索时使用“段落级”文本。
  • 如果规则存在版本差异,把版本号也作为过滤条件。

4.4 提示词构建

CTRAG 的 Prompt 是整个框架的灵魂,可以分成四层:

系统角色:你是一名合规审核专家... 任务描述:请根据给定规则,判断待检信息是否合规。 检索到的规则:以 JSON 数组形式列出。 Few-shot 示例:一个完整的“规则+待检信息+结论”示例。 待检输入:当前需要判断的文本。

注意,Few-shot 示例一定要和真实检索出来的规则“共格式”——示例中的规则编号、结论格式要和被检索规则的输出格式一致,否则模型可能模仿出错误格式。

4.5 合规判断与结果解析

LLM 输出不能直接入库,必须经过解析。建议强制模型输出 JSON:

{ "is_compliant": false, "violated_rules": ["GB-2023-015"], "reason": "建筑高度35m超过27m,应设置消防电梯但未设置", "suggestions": "补充消防电梯设计" }

后端再用json.loads解析。如果担心模型输出不规范,可以在 Prompt 中要求“只输出 JSON,不要其他内容”,并对异常输出做重试。

5. 完整 Python 示例

下面以一个最小可运行的 CTRAG 示例为例,演示从规则入库到合规判断的完整过程。

5.1 创建项目结构

项目结构如下:

ctrag-demo/ ├── data/ │ └── rules.json # 规则库 ├── src/ │ ├── __init__.py │ ├── vector_store.py # 向量库操作 │ ├── retriever.py # 检索模块 │ ├── prompt_builder.py # Prompt 构造 │ ├── llm_client.py # LLM 调用 │ └── compliance_checker.py # 主流程 └── main.py # 入口

5.2 准备规则数据

文件路径:data/rules.json。这里只列两条示例规则,实际项目中可以扩展到上千条。

[ { "rule_id": "GB-2023-015", "rule_text": "建筑高度大于27m的住宅建筑属于高层民用建筑,应设置消防电梯。", "scope": "住宅建筑" }, { "rule_id": "GB-2023-018", "rule_text": "高层民用建筑应设置火灾自动报警系统,且系统应具备远程启动功能。", "scope": "高层民用建筑" }, { "rule_id": "GB-2023-021", "rule_text": "消防车道净宽度和净空高度均不应小于4m。", "scope": "民用建筑" } ]

5.3 向量库操作

文件路径:src/vector_store.py。这里使用 Chroma 作为向量库,embedding 函数可以通过 OpenAI 兼容接口实现,也可以替换为本地模型。

import json from typing import List, Dict class RuleVectorStore: def __init__(self, collection_name: str = "rules"): try: import chromadb except ImportError: raise ImportError("请先安装 chromadb: pip install chromadb") self.client = chromadb.Client() self.collection = self.client.get_or_create_collection(collection_name) def load_rules_from_json(self, json_path: str, embed_func): with open(json_path, "r", encoding="utf-8") as f: rules: List[Dict] = json.load(f) ids = [rule["rule_id"] for rule in rules] documents = [rule["rule_text"] for rule in rules] metadatas = [{"scope": rule.get("scope", ""), "rule_id": rule["rule_id"]} for rule in rules] embeddings = [embed_func(doc) for doc in documents] self.collection.upsert( ids=ids, documents=documents, metadatas=metadatas, embeddings=embeddings ) return len(rules)

需要说明的是,embed_func是外部传入的向量化函数,这样可以灵活切换不同 embedding 模型。

5.4 检索模块

文件路径:src/retriever.py。检索模块根据待检文本返回 Top-K 条规则。

from typing import List, Dict class RuleRetriever: def __init__(self, store): self.store = store def retrieve(self, query: str, embed_func, top_k: int = 3) -> List[Dict]: query_embedding = embed_func(query) result = self.store.collection.query( query_embeddings=[query_embedding], n_results=top_k ) rules = [] # 遍历检索结果 for i in range(len(result["ids"][0])): rule_id = result["ids"][0][i] rule_text = result["documents"][0][i] metadata = result["metadatas"][0][i] distance = result["distances"][0][i] if "distances" in result else None rules.append({ "rule_id": rule_id, "rule_text": rule_text, "scope": metadata.get("scope", ""), "score": 1 - distance if distance is not None else 0.0 }) return rules

5.5 Prompt 构造模块

文件路径:src/prompt_builder.py。这里实现 CTRAG 最核心的 Prompt 组装逻辑。

from typing import List, Dict SYSTEM_PROMPT = "你是一名专业的合规审核专家,擅长根据给定规则对业务文本进行合规判断。" FEW_SHOT_EXAMPLE = """ 规则: 编号:GB-2023-015 内容:建筑高度大于27m的住宅建筑属于高层民用建筑,应设置消防电梯。 待检信息:某住宅楼建筑高度35m,设计文件中未设置消防电梯。 输出: { "is_compliant": false, "violated_rules": ["GB-2023-015"], "reason": "建筑高度35m超过27m,属于高层住宅建筑,应设置消防电梯但未设置。", "suggestions": "补充消防电梯设计并复核建筑平面布局。" } """ class PromptBuilder: def build_prompt(self, text: str, rules: List[Dict]) -> str: rules_block = "\n".join( [f"编号:{r['rule_id']}\n内容:{r['rule_text']}" for r in rules] ) prompt = f""" 请根据下列规则对待检信息进行合规判断。 ### 检索到的规则 ### {rules_block} ### 判断要求 ### 1. 如果待检信息违反一条或多条规则,is_compliant 为 false。 2. violated_rules 中列出所有被违反的规则编号。 3. reason 必须说明推理过程,并引用具体规则内容。 4. 只输出 JSON,不要输出其他文字。 ### 示例 ### {FEW_SHOT_EXAMPLE} ### 待检信息 ### {text} """ return prompt

5.6 LLM 调用模块

文件路径:src/llm_client.py。这里封装了大模型调用,兼容 OpenAI 接口,也可以替换成其他厂商的 SDK。

import json from typing import Dict import openai class LLMClient: def __init__(self, api_key: str, base_url: str, model: str = "gpt-4o-mini"): self.client = openai.OpenAI(api_key=api_key, base_url=base_url) self.model = model def complete_json(self, system_prompt: str, user_prompt: str, max_retries: int = 2) -> Dict: for attempt in range(max_retries): try: response = self.client.chat.completions.create( model=self.model, messages=[ {"role": "system", "content": system_prompt}, {"role": "user", "content": user_prompt} ], temperature=0.1, response_format={"type": "json_object"} # 如果模型支持 ) content = response.choices[0].message.content return json.loads(content) except json.JSONDecodeError: if attempt == max_retries - 1: raise raise RuntimeError("LLM 输出不是合法 JSON")

注意,response_format={"type": "json_object"}并不是所有模型都支持。如果不支持,可以去掉该参数,并在 Prompt 中反复强调 JSON 输出。

5.7 主流程

文件路径:src/compliance_checker.py,负责串联整个 CTRAG 流程。

import os from .vector_store import RuleVectorStore from .retriever import RuleRetriever from .prompt_builder import PromptBuilder, SYSTEM_PROMPT from .llm_client import LLMClient class ComplianceChecker: def __init__(self, rules_json_path: str, embed_func, api_key: str, base_url: str, model: str): self.store = RuleVectorStore() self.store.load_rules_from_json(rules_json_path, embed_func) self.retriever = RuleRetriever(self.store) self.prompt_builder = PromptBuilder() self.llm_client = LLMClient(api_key=api_key, base_url=base_url, model=model) self.embed_func = embed_func def check(self, text: str, top_k: int = 3) -> dict: rules = self.retriever.retrieve(text, self.embed_func, top_k=top_k) user_prompt = self.prompt_builder.build_prompt(text, rules) result = self.llm_client.complete_json(SYSTEM_PROMPT, user_prompt) return { "input_text": text, "retrieved_rules": rules, "result": result }

5.8 入口与运行

文件路径:main.py

import os from src.compliance_checker import ComplianceChecker # 将文本向量化,这里以 OpenAI 兼容接口为例 def embed_func(text: str): import openai client = openai.OpenAI(api_key=os.getenv("OPENAI_API_KEY"), base_url=os.getenv("OPENAI_BASE_URL")) resp = client.embeddings.create( model="text-embedding-3-small", input=text ) return resp.data[0].embedding if __name__ == "__main__": checker = ComplianceChecker( rules_json_path="data/rules.json", embed_func=embed_func, api_key=os.getenv("OPENAI_API_KEY"), base_url=os.getenv("OPENAI_BASE_URL"), model="gpt-4o-mini" ) text = "某住宅楼建筑高度35m,设计文件中未设置消防电梯。" output = checker.check(text) print(output["result"])

运行命令:

export OPENAI_API_KEY=你的密钥 export OPENAI_BASE_URL=https://api.openai.com/v1 python main.py

5.9 预期输出

如果一切正常,控制台会输出类似下面的 JSON:

{ "is_compliant": false, "violated_rules": ["GB-2023-015"], "reason": "建筑高度35m超过27m,属于高层住宅建筑,应设置消防电梯但未设置。", "suggestions": "补充消防电梯设计并复核建筑平面布局。" }

同时,output["retrieved_rules"]中可以看到检索命中了哪些规则,方便排查问题。

6. 实验与对比分析

6.1 对比基线设置

要验证 CTRAG 的效果,建议设置三组对比:

  • Baseline 1:直接让 LLM 回答,不提供任何外部规则,只看模型内部知识。
  • Baseline 2:把全部规则拼进 Prompt 的标准 RAG 方式,不做检索(适合规则库很小的情况)。
  • CTRAG:本文框架,先检索 Top-K 规则,再注入 Prompt。

在规则库较大、单条 Prompt 无法容纳全部规则时,Baseline 2 会直接失效,而 CTRAG 可以稳定工作。

6.2 对比维度

建议从以下维度记录结果:

对比项直接 LLM全量规则注入CTRAG
能否处理大规模规则库否(受上下文限制)
结论是否可追溯
条款引用正确率
推理延迟高(输入过长)
对规则更新的适应性

6.3 实验注意事项

在实验时,需要确保所有对比使用相同的测试集和相同的 LLM 模型,否则结果不可比。另外,建议把测试集按照“是否违反规则”分层抽样,保证正负样本均衡,否则精确率和召回率会出现虚高。

7. 常见问题与排查思路

7.1 检索不到相关规则

现象:待检文本明明违反了某条规则,但检索返回的 Top-K 中没有这条规则。

可能原因

  • 规则文本与待检文本用词差异太大,比如规则写“消防电梯”,业务描述写“疏散用梯”。
  • Embedding 模型对专业术语理解不足。
  • 规则切分过碎,导致语义不完整。

解决思路

  1. 检查规则库质量,补充同义词和近义改写。
  2. 换用领域相关的 embedding 模型。
  3. 增大 top_k 值,从 3 调整到 5 或 8。
  4. 增加“关键词召回”作为 BM25 融合检索,与向量检索双路召回。

7.2 LLM 输出格式不稳定

现象:模型偶尔输出 JSON 以外的内容,导致json.loads失败。

解决思路

  1. 在 Prompt 中明确“只输出 JSON,不要输出 markdown 代码块”。
  2. 开启模型的 JSON mode。
  3. 增加重试机制,解析失败后让模型重新输出。
  4. 用正则提取第一个{到最后一个}之间的内容,作为兜底。

7.3 规则之间存在冲突

现象:两条规则都命中待检文本,但结论相反。

解决思路

  1. 在规则维护时增加规则优先级字段。
  2. 在 Retriever 返回结果时按优先级排序。
  3. 在 Prompt 中说明“如果规则冲突,以优先级高的规则为准”。

7.4 LLM 幻觉导致引用不存在的规则

现象violated_rules中出现规则库中不存在的编号。

解决思路

  • 在结果解析阶段,对规则编号做白名单校验。
  • 如果发现规则编号不在当前规则库内,标记为“疑似幻觉”,转入人工复核。

7.5 常见问题速查表

问题现象常见原因解决思路
检索结果相关度低embedding 模型不匹配更换模型或融合关键词检索
LLM 输出解析失败Prompt 约束不足或模型不支持 JSON mode增加重试、正则兜底
违规结论不一致规则冲突或 top_k 过大设定优先级、调整 top_k
Prompt 超过上下文窗口规则检索太多或待检文本太长缩小 top_k、对待检文本切段
线上效果与测试不一致测试集和真实数据分布不同扩大真实样本验证集

8. 工程化最佳实践

8.1 规则库要版本化管理

合规规则一定会变化。建议在规则表中增加versioneffective_date字段,每次修订都新增一条记录,而不是覆盖原记录。这样既能实现“按时间点回放”,也能支持不同版本的合规判断需求。

例如:

{ "rule_id": "GB-2023-015", "version": "2024-06", "effective_date": "2024-06-01", "rule_text": "...", "scope": "住宅建筑" }

在检索时,可以根据待检文本对应的时间点过滤启用版本,避免“拿新规则审旧项目”的乌龙。

8.2 重视人工复核闭环

LLM 合规判断不能 100% 自动化,至少要在初次上线阶段保留人工复核环节。建议在结果中标记置信度:

  • 检索到的规则数量为 0 → 低置信度。
  • LLM 引用了白名单外的规则 → 低置信度。
  • 规则冲突且无法消解 → 低置信度。

低置信度结果进入人工复核队列。

8.3 日志与审计

合规检查是强审计场景,必须记录:

  • 输入文本。
  • 检索返回的规则列表。
  • 完整 Prompt。
  • LLM 输出原始内容。
  • 解析后的结构化结果。
  • 人工复核结果。

记录完整 Prompt 很重要,否则后期无法复现模型当时为什么会给出这个结论。

8.4 安全与权限边界

如果合规数据涉及业务敏感信息,需要注意三点:

  • 最小权限原则:只有授权人员能查看完整 Prompt 与原始文本。
  • 数据脱敏:进入向量库和 LLM 请求前,对敏感字段做脱敏。
  • API 密钥管理:不要硬编码在代码中,使用环境变量或 Secrets Manager。

8.5 性能优化方向

  • 缓存:如果多条待检文本的内容高度相似,可以启用检索结果缓存,避免多次调用向量库。
  • 批处理:LLM 调用支持 batch,可以把合规判断任务批量下发。
  • 模型分层:简单规则用轻量模型判断,复杂模糊场景才调用大模型,控制成本。

9. 总结与进一步学习

CTRAG 是一个把 LLM 与检索增强、上下文学习结合起来的合规检查框架。它的核心价值在于:让大模型不是凭空“想”出一个合规结论,而是在给定规则上下文中“推理”出可追溯的合规结论

从实现角度,你需要掌握五样东西:

  1. 规则知识库的结构化建模。
  2. 向量检索的基本用法。
  3. Prompt 中 Few-shot 与规则上下文的组织方式。
  4. LLM 结构化输出的解析与容错。
  5. 人工复核与审计日志的闭环设计。

如果你准备在真实项目中落地 CTRAG,我建议按下面的路线继续深入:

  • 先做一个小规模 PoC,比如 20~50 条规则、100 条测试样本,验证框架可行。
  • 再扩展规则库,引入版本管理和检索过滤。
  • 接着做评测集,把精确率、召回率和条款引用正确率跑出来。
  • 最后再接人工复核流程和监控告警。

合规检查本质上是一项责任重大的工作,任何时候都不要把模型的输出直接当作最终裁决。让 LLM 做“初筛者”,让人工做“复核者”,这套框架才能发挥最大价值。

如果你对 RAG 的工程落地、向量数据库选型或 Prompt 调优有兴趣,可以在下一篇文章中继续深入。希望这篇 CTRAG 的拆解能帮你在自动化合规方向上少走一些弯路。

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

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

立即咨询