几个月前我用一个通用Agent去检查一份招标文件结果它在“投标人资格要求是否合规”这个核心问题上给了我一段措辞严谨、逻辑通顺但完全没有任何条文支撑的“疑似违规”结论。我追问依据它开始编造条款编号。那一刻我就明白通用Agent在泛化问答上确实很强但在“必须给结论、必须能给证据、错了要负责任”的合规审查场景里它撑不住。这个项目就是这么开始的。我要把“什么都懂一点”的通用Agent改造成一个“只懂招标合规、但每个判断都落在原文上”的垂直引擎。最终的架构选择是 openJiuwen 做Agent编排再加上一套自定义的 Skills 技能栈把招标文件的合规要点拆成一个个独立又协同的判定模块。这篇文章把我从选型、设计、实现到踩坑的整个过程完整写出来重点落在工程化落地上——不是给你一个Demo而是给你一条可以照着搭的路径。1. 通用Agent做不了招标合规被“什么都懂”绑架的日常先说清楚不是通用Agent不行而是用错了场景。招标文件合规审查这个任务有几个特征是通用对话式Agent天然不适配的。1.1 它给出的结论“看起来都对”但没法为结论负责我给通用Agent输入了一份模拟的100多页招标文件问“资格要求里有没有设置不合理的门槛”。它的回答非常漂亮提到了“注册资金要求可能与项目规模不匹配”还建议“应结合项目实际情况综合评估”。这话对不对对。能不能用来出审查报告不能。因为合规审查要的不是“建议式意见”而是“判定式结论”——这个条款违反了哪一条合规要求、具体是哪个章节哪一行、应当怎么改。通用Agent在生成这些话时并没有锚定到文档的具体片段它的回答是对整个训练语料的“概率性总结”一旦追问细节它就靠上下文补全硬接。这在别的场景无所谓但在招标审查里不行。招标文件的每一句话都涉及投标人的真金白银判断错了整个项目可能被质疑。1.2 合规知识是“有边界的规则”不是“开放式的常识”通用Agent强在开放式知识弱在边界明确的规则执行。招标合规恰恰是一个高度规则化的领域什么时间节点该发什么公告、投标人资格怎么设定算合理、评分标准怎么量化、废标条款怎么表述、付款条款有没有异常倾向。这些规则的数量其实不大核心合规要点可能就几百条但它们必须是“确定性”的——不是“大概差不多”而是“满足条件A且不满足条件B则判定为C”。通用Agent天生不适合做这种精确匹配它的回答天然带有模糊性。1.3 判断过程不可追踪审查结果没法复现如果一个人工审核员给出了意见他的依据是白纸黑字写在审查表上的。但通用Agent的推理过程在权重里你没法把“为什么判定这条违规”展开成一个线性流程给客户看。在合规场景里结论是否可解释比结论本身更重要。没有可解释性准确性再高也没人敢用。这就是我决定放弃直接调用通用Agent做端到端判断转而自己搭一套编排框架的原因。而这个框架最终选了openJiuwen做底座。2. openJiuwen Skills把合规知识拆成可插拔的技能栈在选型之前我其实犹豫过一阵子要不要直接用现成的大模型API套提示词要不要干脆全部用规则引擎写死这两个方案我都试过原型然后各自遇到了瓶颈。2.1 openJiuwen解决的核心问题让Agent知道“该调用谁”openJiuwen本质上是一个面向确定性任务的Agent编排层它做的事情是把“大模型的意图理解能力”和“预设功能的确定性执行”接在一起。你给它一个任务它不是直接凭记忆回答而是先判断这个任务属于哪个技能然后把技能触发的执行逻辑跑一遍再把结构化的执行结果交给大模型去组织语言。这里的关键转变是大模型从“答题者”变成了“调度员”。它不再负责输出合规判断它只负责听懂用户要我查什么然后调用对应Skills。而真正的判断是由Skill里面的规则引擎、数据库查询、文档定位这些确定机制来完成的。这听起来很绕但用生活类比一下就通了。通用Agent像是一个“什么都懂一点的多面手”你问他招标文件的事他全靠脑子里的印象回答。而openJiuwen Skills 像是一个“科室齐全的办事大厅”前台大模型负责帮你取号真正办事的是后面各个窗口技能。窗口办事员按流程走不看关系、不发挥、不给模棱两可的话。2.2 Skills机制我把合规检查拆成了十几个独立模块整个引擎里最重要的设计决策就是把“招标合规”这个大问题切成了若干个小技能。现在线上跑的Skill清单大概是这样的Skill名称检查维度典型判定逻辑文件完整性检查必备章节是否齐全对比配置好的章节清单缺失即报资格要求合规资格门槛是否合理注册资金、资质等级、业绩年限等字段的合理性校验评分办法检查分值设置是否量化、是否有倾向性主观分值占比、评审因素权重偏离度废标条款检查废标条件表述是否清晰、无歧义关键词匹配加语义复核合同条款检查付款条件、违约条款是否公平异常账期、单方免责条款识别时间节点检查公告期、投标截止期是否满足法定时限日期计算与强规则比对每个Skill都是独立的一个Python模块有输入Schema、触发条件、执行逻辑和输出结构。它们之间不共享内部状态唯一交流方式是通过openJiuwen的上下文总线传递结果。这样设计有几个好处。第一出错时可以单独调试某个技能不用整条链路跑第二合规规则更新时只需要改对应Skill的参数不影响其他模块第三每个技能都能单独做单元测试准确率可度量。2.3 为什么不直接用规则引擎全写死其实最开始的版本就是用纯规则引擎做的——正则表达式加关键词黑名单。但很快发现两个问题一是招标文件的表述千变万化同一个意思能有十几种写法规则写不完二是规则引擎缺乏“意图理解”经常把“不要求提供财务报表”误判成“要求提供财务报表”。之所以用openJiuwen来编排是因为它正好补上了这两个缺口。大模型负责把自然语言翻译成结构化意图Skills负责用规则去执行判定。一个管理解一个管准确各干各擅长的事。3. 合规引擎的判定链路解析、匹配、复核三段式整个引擎跑起来之后一条审查任务的完整链路是这样的。我把它分成三段每一段都有明确的输入输出边界。3.1 第一步文档解析与要素抽取把PDF/Word变成结构化切片招标文件通常是一份几十上百页的PDF或者Word直接丢给Agent处理不现实。我们做的第一件事是把文档拆解成“可寻址”的单元。文档入库后会经过这几个步骤格式转换PDF、Word统一转成HTML或者纯文本结构保留标题层级章节识别按照招标公告、投标人须知、合同条款、技术需求、评标办法等标签切分段落表格抽取把商务条款表、评分明细表里的二维数据提取成字典列表生成文档ID每一个段落、每一个表格行都有唯一的定位标识比如SEC-3.2-P-15后续所有判定结果都挂在这些ID下面。这一步是整个链路的地基。如果文档解析错位后面所有判定都是空中楼阁。我建议在这个环节投入足够的精力因为这个环节的准确率直接决定了后面的上限。解析完成后的数据结构大致长这样{ doc_id: TENDER-2025-001, sections: [ { section_id: SEC-3.2, title: 投标人资格要求, content: 投标人须具备XXX资质..., table_rows: [...] } ] }3.2 第二步Skill匹配与条款级判定每个技能只回答一个小问题文档结构化之后openJiuwen开始介入。它会先拟定一个审查计划根据文档类型和章节内容决定要激活哪些Skill。比如当识别到“资格要求”章节时调度层会激活资格要求合规这个技能并把该章节的文本内容作为输入传进去。Skill内部做的事情并不复杂但每一步都很确定用规则模板抽取关键字段注册资金、资质、业绩年限、财务状况、信用要求等把这些字段的具体值与预设的合理性阈值比对超出阈值的字段自动标记为“待确认问题”对于模糊表述Skill会生成一个待补证任务调用大模型做一次“仅基于给定文本”的复核判断。这个设计的核心是每个Skill不试图一次性解决所有问题它只对自己负责的那一小块给出结论。切割得越细判定越准也越容易排查问题。3.3 第三步复核与证据留痕让每个结论都能溯源到原文Skill跑完不是直接出报告还需要经过一个复核环节。这一步我用了一个比较笨但很有效的办法——把每个判定结果和它依据的文档片段一起打包。每条问题记录的数据结构里至少要有这几个字段{ issue_id: ISS-2025-0042, skill_name: qualification_check, severity: high, summary: 注册资金要求5000万元与项目预算明显不匹配, evidence_refs: [SEC-3.2-P-15, TBL-C-12-ROW-3], suggest_action: 建议将注册资金要求调整为与项目规模相适应的额度 }其中evidence_refs是核心。它记录了这条结论是看了文档里哪些位置才得出来的。审查报告输出时每个问题点都会附上原文引用客户可以一键跳转原文核对。这一步之前系统还只是个“智能检查工具”做完这一步它才真正变成“合规引擎”——因为每一句话都有据可查是可以被追责的。4. 工程化落地召回率、误报率、并发与可维护性怎么平衡原型和工程化之间隔着一条巨大的鸿沟。原型只要能跑通就行工程化要考虑的是“跑得稳”“跑得快”“出了问题知道跑错在哪”。我把这个阶段遇到的主要矛盾和解决方法展开讲讲。4.1 准确率不能只看端到端要拆成四个维度单独评估很多做Agent类项目的人一上来就盯着端到端的通过率。但我建议把评估维度拆开否则你根本定位不了问题出在哪一环。我在当前项目里沿用了一套“四率”评估表指标含义我的观察解析准确率文档章节、表格是否被正确切分最容易出现系统性偏差但往往被人忽略召回率已知问题能不能都被发现低召回比高误报更致命因为漏检没人知道精准率报出来的问题是不是真问题误报太多会快速消耗审核员的信任可解释率每条结论能否追溯到原文低于99%就无法用于正式报告当前项目跑下来的一个经验如果你发现召回率不达标多半不是模型不行而是前面的解析切错了如果你发现精准率不达标多半是Skill的规则阈值设置太激进。4.2 上下文管理长文档怎么切成不打架的窗口一份100页的招标文件全部塞进上下文窗口既不现实也不明智。但在做切片时有个陷阱如果把完整的一个章节的上下文切碎了Agent看到的就不是“资格要求”这段完整内容而是几段没有逻辑关联的碎片判定准确率会明显下降。我采用的办法是“语义分段优先字数限制兜底”。先按照章节标题来切切完之后长度依然超限的再用段落边界二次分割。这样既保证语义完整性又控制token消耗。另外每个Skill在运行时只接收它需要的那部分输入。比如评分办法检查就只给它评分章节的内容不塞合同付款条款进来。这样做的好处是上下文纯净模型不会被无关内容干扰坏处是Skill之间缺乏全局关联信息碰到跨章节的问题会漏检——这个问题我在第5章会单独讲。4.3 Skill的版本管理与灰度发布合规规则是会变的Policy一更新对应的Skill就要改。我们给每个Skill都设置了版本号并在调用链路上做了灰度开关。举例来说资格要求的合理性阈值从“注册资本不超过项目预算的5%”调整为“不超过8%”时不是直接全量上线而是先在低流量环境跑一段时间对比新旧版本在同样一批样本上的召回率和误报率差异确认没有异常波动后再逐步扩大流量。这样即使规则调整有误判影响面也可控。版本管理还解决了一个实际问题当客服反馈某个case结论不对时我们能立刻知道是哪个版本的哪个Skill产生的判断从而快速回滚或针对性修复。5. 踩坑实录解析失真、上下文溢出、规则冲突这个章节写的全是真实遇到过的问题。每个问题背后都有一段调试到深夜的经历我把链路和解决方法写出来希望能帮你少走弯路。5.1 表格解析错位招标文件的表格比想象中更“不讲武德”第一个大坑出现在表格解析上。招标文件里的表格经常有合并单元格一个“资格条件”里可能嵌着“注册资本”和“业绩要求”两列数据程序提取后经常错位——把A列的值塞到B列里。这个问题的排查链路花了我很长一段时间最初以为是解析库的参数问题调试后才发现是提取后没有做行级校验。最后加了一道校验机制提取完表格后自动做一次“行列头语义一致性检查”如果第一列的字段名和第一行的字段名跟预设模板对不上就触发重解析或人工标注。从那以后我养成了一个习惯所有自动化解析环节后面必须跟一道自动化的正确性自检。解析不可能100%不出错但至少要保证出错时能被发现。5.2 上下文溢出导致的“幻觉回升”在Skill还没完全独立之前我有过一段把全部文本都放进上下文的版本。结果很讽刺上下文越长模型越容易在回答里“发挥”出一些原文里根本不存在的条款。后来分析这不是模型变笨了而是长文本里的注意力被稀释关键信息湮没在大量无关内容里。解决方式就是我前面提到的“按Skill切分输入每个技能只看自己负责的那一小块”。改动上线后误报率明显下降。5.3 技能间规则冲突废标条件的“优先级黑洞”当Skills数量超过10个之后新的问题出现了同一个条款可能触发两个技能给出矛盾结论。比如“资格要求合规”判断某公司的注册资本不符合要求而“废标条款检查”认为该条款本身表述不当属于需要修改的废标条件——两个结论都对但同时报出来审查人员就糊涂了。解决思路是给每个Skill配置一个“优先级”和“边界声明”。一个条款只能由优先级最高的主技能做最终判定其他技能认为自己能覆盖时只能输出辅助性参考不能输出最终结论。相当于给技能之间的协作定了一个“管辖范围”。这个设计踩了不少坑才理顺但好处是让整个引擎的行为变得可预期。任何一条输出你都知道它是来自哪个技能、基于什么优先级得出的。6. 把这段实践沉淀成一句话如果你问我这个项目做下来最重要的是什么我的答案是别让大模型去做它不擅长的事也别让规则引擎去做它不擅长的事。大模型不擅长精确的合规判定规则引擎不擅长理解千变万化的自然语言。openJiuwen Skills 这套架构真正解决的问题就是把这两类能力放到各自合适的位置上。现在这套引擎已经跑在内部的审查流程里每天处理十几份文件每一份都能生成带原文引用的合规报告。它没有取代审核员但把审核员从“逐条翻文件找问题”里解放了出来让他们把精力放在最需要专业判断的环节。对我来说这就是一个通用Agent走向垂直领域落地时最值得参考的一种工程化路径。