做招标文件合规审查这行,有个感触越来越深:通用Agent是个什么都懂一点的实习生,但真正让它去逐条核对招标文件里的资质门槛、技术偏离、废标条款时,它立刻露怯。不是模型能力不够,而是审查这件事本身需要极高的确定性——哪一条是实质性条款,哪一句是文字游戏,允许偏离的边界在哪,这些不能靠Agent自由发挥。
所以我把一个阶段性成果整理出来:基于openJiuwen + Skills,把通用问答Agent改造成专注招标文件合规审查的垂直引擎。这篇文章适合两类人:一类是天天跟招标文件打交道的业务方,想搞清AI到底能帮到什么程度;另一类是正在做RAG或智能体落地的开发者,想找一个从通用到垂直、从Demo到可上线的完整路线。
1. 从通用Agent到合规引擎:这一跳为什么非做不可
1.1 通用Agent在招标场景的三个水土不服
先说我踩过的坑。最早我用通用Agent直接读招标文件,让它回答“供应商需要具备哪些资质”,结果它能把一段商务需求描述得头头是道,但落实到具体条款编号、材料证明格式、递交截止时间,就开始含糊。这不是模型笨,是场景本身的要求和通用对话完全不一样。
第一个问题是事实性漂移。招标文件动辄几百页,通用Agent的上下文窗口再大也装不下全文细节,它只能靠片段记忆作答,而合规审查恰恰不允许“大概齐”——哪怕一个资质等级的“级以上”和“以下”,结果就是天壤之别。第二个问题是标准不统一。招标文件里的合规要求散落在招标公告、投标人须知、合同条款、技术规范书里,同一件事在三个地方可能有三种表述,通用Agent不会主动做交叉比对。第三个问题更麻烦:审查需要留痕。业务方要的不是“我觉得这条有问题”,而是“哪一份文件的哪一章哪一条,原文是什么,和哪条标准冲突”。通用Agent天生不擅长输出这种可追溯的结构化结论。
这个阶段我明白了一件事:在合规审查这种场景里,Agent最需要的不是更强的推理能力,而是更强的约束能力——约束它看什么、怎么比对、按什么格式输出。
1.2 “引擎化”的本质:把开放问题变成封闭问题
通用Agent适合的是开放问题:“帮我总结这份文件的要点”。但合规审查本质上是封闭问题:“这份文件是否存在第X类风险”。封闭问题的特征是:判定标准可以枚举、判定路径可以固定、输出格式可以模板化。
这就是我提出“合规引擎”这个说法的原因。引擎和Agent的区别不在于用了什么模型,而在于工作模式——引擎是把一套确定的审查流程跑完,Agent是在流程的缝隙里做弹性补充。openJiuwen在这里扮演的是底座,负责把非结构化的招标文件解析成可检索、可引用的结构化知识;Skills则扮演的是专业技能包,把“如何查资质条款”“如何比对偏离项”这些经验固化成一个个可插拔的模块。
这个架构最大的好处是:模型可以换,但流程和技能不会崩。今天用一个通用大模型,明天换一个更好的,Skills定义好的审查逻辑、比对规则、条款库可以原样迁移。招标文件的合规审查不是一次性的单点功能,它需要的是可持续迭代的工程架构。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. openJiuwen + Skills 的技术底座与选型逻辑
2.1 openJiuwen在整套链路中的角色
选openJiuwen而不是从零自研,是因为它的定位和这个场景高度契合:文档解析、知识切分、向量化检索、引用溯源这些脏活累活,它已经帮我们趟平了路。招标文件最让人头疼的就是格式——PDF里可能有扫描件、表格、页眉页脚、图片盖章,还有各种嵌套的章节结构。通用解析工具遇到扫描件就抓瞎,而openJiuwen的文档解析层可以保留文档结构信息,把“第三章 投标人须知”和“附件五 资格审查表”这样的层级关系原样提取出来,这一点在后续的条款定位中太关键了。
另一种考虑是纯RAG方案:把PDF切块、embedding、向量检索、灌给模型。听起来快,但实际用起来会发现一个问题——纯RAG只能回答“内容里有什么”,回答不了“内容违反了哪条规则”。因为你没有把审查逻辑独立出来,所有判断都靠模型临时理解。openJiuwen + Skills的做法是把知识检索和业务判定解耦:openJiuwen负责“找到相关段落”,Skills负责“按规则判定风险”,各司其职,结果就可控多了。
2.2 Skills机制:把审查经验变成可插拔的模块
Skills是这套架构里最值得展开的部分。简单理解,Skills就是一组可注册、可编排的“技能定义”,每个技能封装了三样东西:触发条件、执行逻辑、输出模板。
触发条件决定什么时候激活这个技能。比如“资质审查技能”,它在解析完投标人资质文件后自动触发;“偏离表核验技能”,则在技术参数比对阶段触发。执行逻辑内部可以包含多条子规则,比如资质等级判定规则、有效期校验规则、范围覆盖核对规则。输出模板则保证每次审查结果都按统一格式输出,包含结论、证据链、风险等级。
实际配置一个Skill时,我习惯把它定义成JSON或YAML结构,而不是完全靠代码硬编码。这样做的好处是业务同事可以直接改规则,不用等开发排期。比如最近某项目要求“售后响应时间不得超过4小时”,这是一条动态阈值规则,放在Skill配置里,业务方直接填“4小时”就行,引擎下次审查自动按这个标准执行。
2.3 为什么不用纯规则引擎或纯大模型
有人会问:合规审查这么规则化,直接用传统规则引擎不就行了?答案是可以,但前提是你愿意把每一条规则都写成代码,并且接受规则之间的组合爆炸。招标文件的表述千变万化,“具备有效的安全生产许可证”和“投标人应持有建设行政主管部门核发的安全生产许可证”,前者是通用说法,后者是具体领域,用正则去匹配两种写法会让你维护到崩溃。
大模型的价值在于理解语义变体,规则引擎的价值在于保证判定一致性,两者需要配合而不是二选一。openJiuwen + Skills恰好提供了这种分层:Skill的外层用大模型做语义理解,把不同表述归一化成标准字段;Skill的内层用确定性规则做精确判定,确保“资质过期”“等级不够”这种硬伤绝不会被模型糊弄过去。
我还踩过一个教训:一开始我把所有逻辑都塞进Prompt,让模型自己决定用哪条规则。结果出现了一次严重误判——模型在回答里把“制造商授权书”解读为“销售授权书”,差一个字,性质完全不一样。改成Skills结构化管理之后,这种模糊地带就交给规则层去强制校验,语义理解只负责抽取和分类,不再负责裁决。
3. 合规引擎的工程化落地实操:从搭建到上线
3.1 招标文件语料的预处理与知识库构建
搭建合规引擎第一步不是写代码,而是处理语料。招标文件通常是PDF,但PDF和PDF不一样。纯文本型PDF直接走文本解析,扫描型PDF需要走OCR,这决定了整个pipeline的路径选择。openJiuwen支持多格式文档解析,但我强烈建议在预处理阶段就把文档分类:按章节拆解、按条款编号建立索引、把表格转成结构化行列,这样后续检索才有的放矢。
语料处理有几个细节值得留意。第一,招标文件的附件和正文同等重要,只解析正文会让大量评审标准丢失。第二,表格里的信息密度极高,资质要求经常藏在“资格审查表”里,纯文本检索会把表格内容打散,导致召回不完整。我在预处理阶段会把表格单列成一个数据通道,单独做结构化入库,并在知识库里打上“表格来源”标签,方便后续技能优先命中。
知识库的索引结构我做了两层:一层是按文档结构索引,保留章节层级,方便按“第三章第二节”做定位式检索;另一层是按语义向量索引,用于模糊泛化召回。检索时先走结构索引,命中不了再走向量索引,这种混合检索能兼顾精确度和召回率。
3.2 合规Skill的编排实现:规则与模板的载体
Skill我一般用JSON Schema定义,下面是一个合规技能的精简示例,用来检查“企业资质证书的有效期覆盖项目工期”。这个技能名叫qualification_validity_check,输入是资质信息和项目工期,输出是一个带风险等级的结构化结论。
python复制{
"skill_name": "qualification_validity_check",
"trigger": "after_document_parsed",
"input": {
"qualification_certificates": "附件-资质证书列表",
"project_duration_start": "招标公告-计划开工日期",
"project_duration_end": "招标公告-计划竣工日期"
},
"rules": [
{
"rule_id": "R001",
"description": "资质证书有效期需覆盖项目工期",
"check": "certificate.valid_to >= project_duration_end",
"risk_level": "critical"
},
{
"rule_id": "R002",
"description": "资质证书资质等级不低于招标要求",
"check": "level_rank(certificate.level) >= level_rank(requirement.level)",
"risk_level": "high"
}
],
"output_template": {
"conclusion": "$conclusion",
"evidence": ["$certificate_info", "$requirement_clause"],
"risk_level": "$risk_level",
"suggestion": "$suggestion"
}
}
这里有个设计细节要强调:规则里的level_rank()不是简单的字符串比较,因为资质等级是有序的——“甲级”“乙级”“丙级”之间是高低关系,不能按字符串排序。我在实现时维护了一个等级映射表,把每家监管机构的标准映射到统一等级分,再比较数值。看起来是小事,但如果不做这层归一化,同一个等级在不同省份的表述不同,规则判定就会一团糟。
3.3 规则引擎与Agent的协同:先规矩后自由
落地过程中,我逐渐摸索出一套分工模式,叫“先规矩后自由”。整个审查流程被分成几个阶段,规则和Agent各管一段。
前期材料解析、条款抽取、资质信息入库,这类工作完全交给代码和规则,确定性高,不需要模型参与。中期风险判定,这是规则引擎的主场,所有硬性指标(资质等级、时间期限、金额大小)都由规则精确计算。后期才轮到Agent能力发挥:当规则判定“临界”或“语义模糊”时,比如资质证书名称和招标要求不完全一致但明显指向同一件事,Agent负责做语义层面的判断,并生成解释性的审查意见。
这种分工最直接的好处是可控。Agent永远拿不到最终裁决权,它只是在规则标记的“灰色地带”提供参考意见。有一次实际审查中,一个供应商的ISO9001证书名称写的是“质量管理体系认证证书”,招标文件要求的是“ISO9001质量管理体系认证”,严格按字符串匹配会直接判不合格,但规则的模糊匹配标记了“语义相似”,Agent介入后判定为合格,并给出了理由。这种场景如果纯靠规则会误杀,纯靠Agent会失控,协同才靠谱。
Agent和规则引擎之间的通信我设计成事件驱动:规则引擎每完成一个判定,就发出一个事件,事件携带上下文;Agent订阅需要它介入的事件类型,处理完再把结果写回事件总线,供规则引擎做最终汇总。这个模式下,两边互不阻塞,审查吞吐量完全由规则引擎决定,Agent只是异步加速器。
3.4 评测方案:没有评测集的合规引擎不值得上线
很多人在做这类系统时,最容易忽略的就是评测。但合规审查的容错率极低,没有评测就上线,等于拿甲方的项目当实验品。
我搭了一套双轨评测方案。第一轨是历史数据回归:从过往三年的招标文件和对应的审查报告中,人工标注出200条高风险条款作为基准集,每次改完规则或者换模型,都把基准集跑一遍,看检出率有没有下降。第二轨是模拟对抗:让业务同事构造一批“一眼合规但实际违规”的边缘案例,专门测试系统对模糊表述的识别能力,比如“生产厂家授权”和“原厂授权”的区别,能不能被正确归类。
评测指标不能只看准确率。我给每条风险定了三个指标:检出率、误报率、证据完整率。检出率是核心指标,但误报率过高会把业务方拖垮,证据完整率则决定了人工复核的效率。有一次我把一条LoRA模型的Prompt写错,检出的风险条款少了12%,就是因为没有跑历史回归,直接在线上用,还好上线前跑一轮评测发现了。
4. 上线阶段踩过的坑与排查实录
4.1 条款版本漂移:不同章节对同一要求表述不一致
上线后遇到的第一大类问题,是同一份招标文件里,不同章节对同一要求的表述存在差异。比如技术规范书里写“供应商需具备软件能力成熟度CMMI三级及以上认证”,而评标办法里却写“需提供CMMI认证材料”。系统在比对资质时,沿着技术规范书的条款标准去检索,结果找不到对应的证书材料,就报了一个“资质缺失”的风险,但实际上投标人提供了,只是提供的位置在评标资料附件里,名称也不是“CMMI认证材料”,而是“软件能力证明”。
排查后发现,这不是检索的问题,而是条款关联度不够。我的方案是在知识库里增加一层“条款同义关联”——通过语义向量把表述相近的条款聚成一个簇,当一条规则命中时,自动把同簇的其他条款也拉出来做交叉比对。这样即使原文表述有差异,系统也能意识到两处说的是同一件事,不再因为字面不匹配就误判。实测下来,调整后误报率下降了四成。
4.2 长文档召回缺失:关键条款只在docx里
另一个典型的坑是长文档的分段召回。招标文件里有一类风险条款藏在“合同专用条款”中,这部分内容经常是扫描后重新OCR出的docx,格式混乱,段落长且密集。我们最初用固定大小切块,每块1000字,结果一条完整的违约赔偿条款被切成了两块,检索时只召回前一半,后半部分的风险条件没被看到,系统判了个低风险。
解决思路是让切块沿语义边界走,而不是机械定长。我在openJiuwen的文档解析层里加了标题感知和段落聚合,每个块尽量保持一个完整小节,比如“违约金与赔偿”一整节,不再截断。针对特别长的条款,我会做重叠切分,保留前后文冗余,确保无论从哪边切入都能覆盖核心语义。这个改动对召回率的影响很大,但同样要注意,块变长了检索的效果可能变差,所以需要配合更精细的向量化策略,比如按条提取向量再合并,而不是对整块做单个向量。
4.3 幻觉审查意见:模型在“补充”它没看到的内容
这是AI系统最难防的问题之一。有一次测试系统,输入一段招标文件,其中关于投标保证金的条款只写了“投标保证金:金额见前附表”,并没有给出具体金额。模型居然自己“推理”说保证金金额为8万元。这完全是无中生有——它看到别处提到了保证金比例,自行推导了金额,还煞有介事地写进了审查意见。
我在Skill输出模板里强制加了一个字段叫“依据绑定”,要求任何涉及具体数字的结论,都必须引用至少一条来源条款,并且标签谷歌相似度达到阈值,否则输出“信息缺失,无法判定”,而不是硬造一个结论。更严格的做法是设置一个“数值一致性校验”:系统自动从整篇原文里聚合所有提及同一关键词的段落,发现“保证金”只有一个模糊表述时,直接把结论标记为“待人工确认”,禁止模型自行补全。这本质上是用流程约束把幻觉关在笼子里。
4.4 问题速查表:遇到问题第一时间的排除路径
| 症状 | 可能原因 | 排查路径 | 快速处置 |
|---|---|---|---|
| 高风险条款漏检 | Skill规则未命中或条款关联缺失 | 查看规则执行的case日志,确认哪一步没走到 | 补充同义关联,或调整触发条件 |
| 误报资质缺失 | 条款表述不一致导致检索失败 | 检查命中的条款来源是否和证书库匹配 | 运行条款同义聚类,合并近似描述 |
| 结论出现无来源数字 | 模型补充了上下文不存在的信息 | 检查输出模板的“依据绑定”字段是否为空 | 开启人工确认兜底,禁止自动生成数值 |
| 长文档关键片段召回不到 | 切块断在语义边界中间 | 查看切块ID和段落标题归属 | 调整切块策略为语义切分,开启重叠切分 |
| 规则Engine超时 | 大量子规则同时触发,计算冲突 | 检查规则依赖和优先级 | 按风险等级降级为异步执行 |
这张表我会让每个参与该系统的人贴在工位上。合规引擎上线之后,绝大部分问题都能在前15分钟定位到方向,剩下的只是细节调试。对工程化系统来说,可排查性比功能数量更值钱。
5. 实测效果与我的个人体会
这套引擎在模拟项目X的招标文件上做了完整压测:一份320页的招标文件,包含67项资质要求、22张评审表格和3份合同模板,从解析到输出全部审查意见,耗时约4分钟,检出率稳定在95%以上,证据完整率接近100%,误报率控制在可接受的个位数。最让我在意的一个指标是:一条误报都没有出现在“资质有效期”这类硬性规则上,因为这类判断已经完全交给了确定性计算。
如果你准备在自己的项目里复刻这条路线,我的核心建议是:不要先去追求更大的模型,先把规则引擎和Skill体系搭扎实。模型的替换成本很低,但规则和知识库的积累才是一砖一瓦堆出来的护城河。尤其是做招标文件这类强合规场景,业务方真正信任的不是AI的聪明,而是AI的稳定和留痕。
后面这个体系还可以扩展的方向,一是把审查结果自动生成偏离表草稿,二是把历史审查决策沉淀成规则建议,反向提示业务方哪些条款容易踩坑。但眼下我更想说的是动手之前先想清楚一件事:你做的到底是锦上添花的助手,还是风雨无阻的守门员。如果是后者,就用守门员的标准去设计它——确定性优先,证据第一。
