ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

合规引擎零LLM调用:确定性规则引擎与CI门禁实践

合规引擎零LLM调用:确定性规则引擎与CI门禁实践 1. 为什么要在合规引擎里彻底封杀LLM调用第一次听到“合规引擎里零LLM调用”这个说法很多同行的第一反应是都什么年代了还不用大模型但如果你真正做过欧盟AI法案EU AI Act相关的合规产品就会明白这个决定背后不是保守而是被现实反复教育之后的结果。我参与过一个面向出海企业的AI合规评估系统核心功能是判断一个AI系统属于法案里的哪个风险等级然后生成对应的义务清单、技术文档模板和整改建议。项目早期我们试过用LLM做条款匹配和风险分类效果确实惊艳demo阶段准确率能到九成以上。但一进入客户现场就崩了同一份输入今天判定为“高风险”明天变成“有限风险”客户问为什么我们自己也说不清楚。合规这件事最怕的就是“说不清楚”监管审计要的是可追溯、可复现、可举证而不是一个概率性的黑盒。所以这个项目的核心思路非常明确把EU AI Act的条款、附件、风险分类规则、义务映射关系全部结构化用确定性代码实现判定逻辑整个合规引擎不发起任何LLM调用并且在CI流水线里加一道硬门禁任何人提交的代码只要引入LLM依赖或运行时调用直接阻断合并。这套方案解决的不是“能不能做”的问题而是“敢不敢交给客户”的问题。它适合正在做合规SaaS、企业内部AI治理平台、或者需要向监管提交AI系统合规证明的团队参考。哪怕你不做EU AI Act只要你的业务涉及规则判定、审计留痕、结果可复现这套“零LLMCI门禁”的思路都能直接抄作业。2. 合规引擎的整体架构与零LLM设计思路2.1 核心判定逻辑为什么必须确定性EU AI Act的合规判定本质上是一个规则树遍历问题。法案把AI系统分为不可接受风险、高风险、有限风险、最小风险四个层级每个层级对应不同的义务。高风险系统还要进一步看它属于哪个附件类别是生物识别、关键基础设施、教育评分、 employment决策还是执法用途。这些分类依据全部写在法案正文和附件里是明确的、有限的、可枚举的。我试过用LLM做这件事最大的问题是条款引用不可控。你问它“招聘筛选系统属于哪一类”它可能引用第6条也可能引用附件三甚至编造一个不存在的条款号。合规文档里出现一个假条款号客户拿去提交监管后果是灾难性的。确定性代码不会有这个问题规则表里写死了“招聘筛选 - 附件三第4项 - 高风险”输出永远一致条款号永远准确。另一个关键点是版本管理。EU AI Act从提案到正式生效经历过多轮修订不同时间点的义务要求不一样。确定性引擎可以把每个版本的规则集做成独立配置客户问“2025年8月之后有什么变化”直接切换规则版本重新跑一遍就行。LLM做不到这一点它的知识是混在一起的你没法让它精确地只使用某个时间点的法案文本。2.2 规则引擎的选型与数据结构设计我们最终选的是JSON规则表Python判定函数的组合没有用Drools、没有用CLIPS也没有用任何重型规则引擎。原因很简单合规规则的数量是有限的EU AI Act核心判定规则大概两百多条用JSON描述完全够用而且JSON对人类可读、对Git友好、对CI友好。规则表的结构大概长这样{ rule_id: EUAIA-ART6-HIGH-RISK-001, version: 2024-07, condition: { system_purpose: [employment, recruitment], technique: [machine_learning, statistical], role: [provider, deployer] }, classification: high_risk, annex_reference: Annex III, point 4(a), obligations: [ risk_management_system, data_governance, technical_documentation, record_keeping, transparency, human_oversight, accuracy_robustness_cybersecurity ] }判定函数就是遍历规则表匹配输入的系统描述返回分类结果和义务清单。整个过程没有任何网络请求没有任何模型推理纯本地计算毫秒级返回。注意规则表一定要和法案原文做双向追溯。每条规则都要标注它对应法案的哪一条、哪一款、哪一项。审计的时候客户可以拿着你的输出直接翻到法案对应位置核对。2.3 为什么不用“LLM规则”的混合方案有人会问能不能用LLM做初步分类然后用规则引擎校验我们试过结论是混合方案比纯LLM更危险。因为LLM的输出是不确定的规则引擎的校验逻辑就变成了“在不确定的输出上做确定判断”这本身就是一个逻辑矛盾。而且一旦LLM给出了错误分类规则引擎可能因为匹配到了某个边缘条件而放行最终输出一个看似合理但实际错误的结果。更现实的问题是审计成本。纯规则引擎的审计很简单看规则表看判定函数跑测试用例。混合方案的审计要复杂得多你得证明LLM在什么情况下会出错规则引擎在什么情况下能兜住兜不住的时候怎么办。这套论证写出来比规则表本身还长客户根本不想看。所以我们的选择是彻底的要么全规则要么全LLM不做混合。既然选了合规这个场景那就全规则。3. CI门禁如何用流水线彻底阻断LLM依赖3.1 依赖层面的静态扫描CI门禁的第一道防线是依赖扫描。任何LLM相关的包不管是openai、anthropic、cohere、huggingface、transformers、langchain、llama-index还是国内的各类大模型SDK只要出现在依赖文件里直接失败。具体实现是在CI里加一个脚本扫描以下文件requirements.txt/requirements-dev.txtpyproject.toml里的[project.dependencies]和[project.optional-dependencies]package.json里的dependencies和devDependenciesPipfile/Pipfile.lockpoetry.lock扫描逻辑不是简单的字符串匹配而是维护一个禁止包名列表同时用正则匹配包名的变体。比如openai要匹配openai-python也要匹配open-ai这种变体也要覆盖。我们踩过的坑是有人把包名写成openai但放在optional-dependencies里以为CI不会查结果被我们的脚本抓出来了。# ci/check_llm_deps.py import re import sys from pathlib import Path FORBIDDEN_PATTERNS [ ropenai, ranthropic, rcohere, rhuggingface, rtransformers, rlangchain, rllama[-_]?index, rgoogle[-_]?generativeai, rmistralai, rollama, rvllm, rtext[-_]?generation[-_]?inference, ] def scan_file(path: Path) - list[str]: hits [] content path.read_text(encodingutf-8, errorsignore).lower() for pattern in FORBIDDEN_PATTERNS: if re.search(pattern, content): hits.append(f{path}: matched {pattern}) return hits if __name__ __main__: targets [ requirements.txt, requirements-dev.txt, pyproject.toml, package.json, Pipfile, poetry.lock, ] all_hits [] for t in targets: p Path(t) if p.exists(): all_hits.extend(scan_file(p)) if all_hits: print(LLM dependency detected, blocking merge:) for h in all_hits: print( h) sys.exit(1) print(No LLM dependency found.)这个脚本在GitLab CI里就是一个独立的job放在test阶段之前失败就直接终止流水线。3.2 运行时调用的动态拦截依赖扫描只能挡住“引入包”这条路挡不住有人用requests直接调API。所以第二道防线是运行时拦截。我们的做法是在合规引擎的入口处加一个网络调用守卫。具体来说引擎启动时会patch掉requests、httpx、urllib等HTTP客户端的send方法任何试图发起外部网络请求的操作都会抛出异常并记录堆栈。# compliance_engine/network_guard.py import requests import httpx import traceback class NetworkAccessBlocked(Exception): pass def _blocked_send(self, request, **kwargs): stack .join(traceback.format_stack()) raise NetworkAccessBlocked( fNetwork access attempted in compliance engine.\n fURL: {request.url}\n fStack:\n{stack} ) def install_guard(): requests.Session.send _blocked_send httpx.Client.send _blocked_send这个守卫只在合规引擎进程里安装不影响其他服务。CI里跑集成测试的时候任何触发网络调用的测试用例都会失败这样就能确保引擎在真实运行时也不会偷偷调LLM。提示这个守卫要放在引擎初始化的最前面早于任何业务代码加载。我们曾经因为导入顺序问题导致某个模块在守卫安装前就创建了requests.Session实例绕过了拦截。后来改成在__init__.py第一行就调用install_guard()才解决。3.3 CI配置的完整写法GitLab CI的配置大概是这样stages: - guard - test - build llm-dependency-check: stage: guard script: - python ci/check_llm_deps.py rules: - if: $CI_PIPELINE_SOURCE merge_request_event - if: $CI_COMMIT_BRANCH main llm-runtime-check: stage: test script: - python -m pytest tests/test_no_network.py -v rules: - if: $CI_PIPELINE_SOURCE merge_request_event - if: $CI_COMMIT_BRANCH maintest_no_network.py里会跑一遍完整的合规判定流程同时监控是否有网络调用。如果有测试失败MR被阻断。这套CI门禁上线之后我们统计过三个月内拦截了7次LLM依赖引入其中3次是开发同学不小心在pyproject.toml里加了openai做实验2次是复制粘贴了其他项目的依赖还有2次是有人想用transformers做本地推理但没意识到这也算LLM调用。4. 规则表维护与版本管理的实操细节4.1 规则表的拆分与组织两百多条规则放在一个JSON文件里维护起来是灾难。我们的做法是按法案章节拆分rules/ article_5_prohibited.json article_6_high_risk.json article_50_transparency.json annex_iii_high_risk_categories.json annex_viii_registration.json每个文件里是一个规则数组规则之间用rule_id做唯一标识。判定引擎启动时把所有文件加载进来合并成一个内存中的规则索引。这样做的好处是Git diff清晰。法案修订的时候只需要改对应的文件review的时候一眼就能看出哪条规则变了。如果全放在一个文件里diff会淹没在几百行JSON里根本没法审。4.2 规则版本与法案版本的映射EU AI Act的正式生效时间是2024年8月1日但不同条款的适用时间不一样。比如禁止性条款2025年2月2日适用高风险系统义务2026年8月2日适用部分条款2027年才适用。所以规则表必须带生效日期字段。{ rule_id: EUAIA-ART6-HIGH-RISK-001, effective_from: 2026-08-02, effective_until: null, version: 2024-07 }判定引擎在跑的时候会根据当前日期过滤规则。客户如果问“2025年3月我的系统需要满足什么义务”引擎就只加载effective_from 2025-03且effective_until 2025-03的规则。这个设计让我们在客户现场回答“什么时候要做什么”这类问题时非常从容直接切日期重新跑一遍就行不需要人工翻法案。4.3 规则变更的CI校验规则表变更也要过CI而且校验比代码变更更严格。我们加了几个检查JSON schema校验每条规则必须包含rule_id、condition、classification、annex_reference、obligations字段缺一不可。rule_id唯一性校验不能有重复的rule_id。条款引用格式校验annex_reference必须匹配Annex [IVX], point \d\([a-z]\)这种格式。义务清单白名单校验obligations里的每一项必须在预定义的白名单里防止有人写错别字。这些校验全部在CI的guard阶段跑规则表改错了直接阻断合并。实操心得规则表的review一定要拉上法务或合规专家。我们曾经因为一个condition的匹配逻辑写得太宽把“教育评分系统”和“教育招生系统”混在一起导致义务清单多列了两项。后来改成每个condition字段都要有对应的法案原文引用review的时候逐条核对。5. 常见问题与排查技巧实录5.1 CI报错“preparing metadata (pyproject.toml) did not run successfully”这个报错在引入CI门禁之后变得很常见但原因往往和LLM无关。最常见的情况是pyproject.toml里的build-system配置有问题或者requires里的包版本冲突。排查步骤先看CI日志里pip install的具体报错行通常是某个依赖的setup.py执行失败。检查pyproject.toml的[build-system]段确认requires里的setuptools、wheel版本是否兼容。如果最近改过依赖用pip install --dry-run在本地复现一下看是哪个包的问题。确认不是LLM依赖导致的之后再按普通依赖冲突处理。我们踩过的坑是有人在pyproject.toml里加了openaiCI的LLM检查先失败了但日志里同时出现了preparing metadata的报错开发同学以为是构建问题折腾了半天才发现是LLM依赖被拦截了。所以CI日志里要把LLM检查的失败信息放在最前面避免误导。5.2 规则匹配结果与预期不符这是合规引擎最常被问到的问题。排查思路是把判定过程打出来。我们在引擎里加了一个--explain模式跑判定的时候会输出每一步匹配了哪条规则、为什么匹配、为什么没匹配其他规则。python -m compliance_engine classify --input system.json --explain输出大概是这样Input: {system_purpose: recruitment, technique: machine_learning} Matched rule: EUAIA-ART6-HIGH-RISK-001 condition.system_purpose: [employment, recruitment] - matched recruitment condition.technique: [machine_learning, statistical] - matched machine_learning classification: high_risk annex_reference: Annex III, point 4(a) Skipped rule: EUAIA-ART6-LIMITED-RISK-003 reason: condition.system_purpose did not match有了这个输出客户问“为什么我是高风险”的时候直接把explain结果发过去一目了然。5.3 网络守卫误伤正常功能网络守卫刚上线的时候误伤过几个正常功能。比如某个模块用了requests做本地文件下载其实是读本地文件但用了requests的file://适配器被守卫拦截了。还有一次是某个日志库在初始化时尝试发送遥测数据也被拦截了。解决办法是白名单机制守卫允许访问localhost和127.0.0.1允许file://协议其他一律阻断。同时把遥测类的库在依赖层面就移除掉不让它们进项目。ALLOWED_HOSTS {localhost, 127.0.0.1, ::1} def _blocked_send(self, request, **kwargs): host request.url.host if host in ALLOWED_HOSTS or request.url.scheme file: return original_send(self, request, **kwargs) raise NetworkAccessBlocked(...)5.4 常见问题速查表问题现象可能原因排查方法解决方式CI在guard阶段失败依赖文件里有LLM包看CI日志的LLM检查输出移除对应依赖或确认是否误报CI在test阶段失败运行时触发了网络调用看NetworkAccessBlocked堆栈定位调用点改为本地实现或加白名单规则匹配结果不对condition字段写错或规则顺序问题用--explain模式跑一遍修正规则表加测试用例pyproject.toml构建失败build-system配置问题本地pip install --dry-run修正build-system或依赖版本规则表JSON解析失败格式错误或编码问题用jq或python -m json.tool校验修正JSON格式统一UTF-8编码义务清单多列/少列obligations白名单没更新对比法案原文和规则表更新白名单加CI校验避坑技巧规则表的测试用例要覆盖边界条件。比如“系统用途是招聘但只用于内部员工晋升”和“系统用途是招聘用于外部候选人筛选”这两个在法案里的分类可能不一样。我们专门建了一个tests/rule_cases/目录每个规则至少配一个正向用例和一个反向用例CI里全跑一遍。6. 这套方案的实际效果与适用边界上线半年多这套零LLMCI门禁的方案在客户现场的表现非常稳定。最直接的反馈是审计通过率之前用LLM方案的时候客户拿我们的输出去给监管看经常被问“这个结论怎么来的”现在直接给规则表和explain日志监管一看就懂。有个客户的法务说这是他们见过的最“不像AI产品”的AI合规产品但恰恰因为这样他们才敢用。性能上单次合规判定平均耗时12毫秒比LLM方案的3到5秒快了三个数量级。客户可以批量跑几千个系统做组合评估完全不需要考虑API配额和成本。但这套方案也有明确的适用边界。它不适合处理开放式问题比如“帮我写一份AI伦理政策模板”这种需要自然语言生成的任务。我们的做法是把这类需求拆出去做成独立的文档生成服务那个服务可以用LLM但它不参与合规判定只负责把判定结果填充到模板里。这样既保证了核心判定的确定性又保留了文档生成的灵活性。另一个边界是规则覆盖度。EU AI Act的规则是有限的我们能做到全覆盖。但如果你的合规场景涉及多个司法辖区规则数量会指数级增长规则表的维护成本会变得很高。这种情况下可能需要考虑用更专业的规则引擎但核心原则不变判定逻辑必须确定性LLM只能做辅助不能做决策。最后分享一个我在实际维护中总结的小技巧规则表的变更一定要和法案原文的变更同步做。我们建了一个docs/act_changelog.md每次法案有修订先更新这个文档再改规则表最后跑CI。这样即使过了半年回头看也能清楚地知道每条规则是什么时候、因为什么原因加进去的。合规这件事追溯性比什么都重要。
返回列表