这个项目上线那天,我们在会议室里等第一个真实工单。演示环境里模型表现得像个十年老员工,能总结、能推断、能把完整执行计划列得清清楚楚。但生产环境里,它要做的第一件事,是把 OA 里一张审批单读进来,再对着 ERP 里的科目去改一条凭证记录。就这一小步,我们磨了整整两天。
所以标题那句话不是段子,是复盘完整个项目之后,我们几个人一致认可的结论:Agent 上生产,最难的从来不是模型本身,而是把 OA、ERP 这类企业存量系统接进来。模型再聪明,拿不到业务数据就是空手道高手。这篇就是把我们这段经历中"系统接入"这部分完整拆开讲——包括我们为什么选择了 MCP 协议、为什么在 MCP 之上又加了一层 FDE MCP Blade 适配层、以及生产环境里那些文档永远不告诉你的坑。
如果你正在做一个要连接企业系统的 Agent 项目,或者你只是好奇"AI Agent 落地时到底卡在哪",这篇文章应该能帮你少走不少弯路。
1. 模型从来不是短板,系统接入才是拦路虎
1.1 demo 的错觉:模型会聊天,不代表能连接
做 Agent 项目,几乎所有人都会先被模型的表现征服。我们当时的演示场景是:让 Agent 扮演财务助理,用户说一句"帮我把这周报销单统计一下,超预算的标出来",模型回答得滴水不漏,还能自己写 SQL、做汇总。客户当场拍板要上生产。
但生产环境的内网里,数据不是一张干净的 CSV,而是一个个独立运行了几年的业务系统。OA 里有审批流、待办、表单;ERP 里有供应商、科目、凭证、预算。Agent 第一步要做的不是"思考",而是"连接"——它得知道 OA 的登录地址、拿到会话票据、找到那张审批单的接口、解析返回参数,再把结果翻译成 ERP 能认的科目编码。这一整套链路,和模型聪明不聪明没有半点关系。
说白了,模型的能力是"想清楚之后怎么做",系统接入解决的是"能不能够得着做这件事的原料"。演示时我们喂给模型的是整理好的数据,生产时模型要自己去一锅乱炖里捞食材,难度完全不在一个量级。
1.2 集成成本为什么总被严重低估
很多人以为企业系统都有 API,调一下就行。真实的情况是:几十个系统、几百个接口,文档七零八落,字段命名各有一套方言,接口返回的数据格式和数据库里的还不一样。我们当时做了个粗略盘点,一个中等规模企业的核心业务链路,涉及的接口调用串起来至少有七八层,每一层都可能断。
集成成本被低估主要在三个地方:
- 接口盘点阶段就要喝一壶。很多老系统的接口文档是 Word 版,和线上版本已经对不上了,只能靠抓包和翻旧代码一点点还原。
- 语义对齐比技术对接更耗时。OA 里的"报销项目"和 ERP 里的"费用科目"明明是一件事,字段名完全不一样,枚举值也对不上,Agent 传参时稍微模糊一点就出错。
- 历史包袱躲不掉。字符集、时区、金额精度、分页上限,这些脏活累活没有技术含量,但每一个都可能让一次看似简单的调用直接失败。
这些坑在项目规划和 demo 阶段几乎看不见,只有真的把 Agent 挂到生产环境,让它在真实数据上跑一遍,才会集中爆发。我们正是在这个阶段慢慢意识到,需要一套专门处理"系统接入"的中间层,而不是靠模型自己去歪打正着。
2. OA 与 ERP 原生集成差在哪:三个最让人头疼的真实问题
2.1 协议方言:一套系统一套登录与调用标准
OA 和 ERP 是两类完全不同的"物种",它们当初被设计出来时,压根没想过有一天会让 AI Agent 来调用。
拿 OA 来说,以我们项目对接的泛微、致远这类产品为例,最常见的是 SOAP WebService 加一套自定义的登录票据机制。你要先拿着账号密码换一个 session ticket,后续每个请求都得带着它,而且票据有过期时间,可能在一次长任务跑到一半时就失效了。有些老版本 OA 还混合着 cookie、token、甚至 IP 白名单多种鉴权方式,各自的过期策略还不一样。
ERP 又是另一套脾气。金蝶、用友这类系统通常有企业服务网关,接口风格五花八门,有同步返回的,也有提交后返回一个 job_id、真正结果要异步轮询的。也就是说,Agent 调用一个"创建凭证"接口,拿到的不一定是成功或失败,而是一个"我收到了,你等会儿再来问"的中间状态。模型对这种异步语义天然不敏感,如果中间层不做封装,它会把"任务已提交"误判成"操作已完成"。
再加上字符集、时间格式、金额精度这些细节,你会发现每个系统都在说自己的方言,Agent 不可能靠通用能力去猜,必须有人替它做翻译。这正是接入层必须存在的最直接理由。
2.2 对象网络:给 Agent 的不能只是接口,得是"领域答案"
更隐蔽的坑是业务对象之间的关联。一张 OA 里的报销单,背后牵扯的不是一条记录,而是一张关系网:发起人属于哪个部门、部门挂在哪个成本中心、费用走哪个预算科目、审批流当前到哪一层、附件里有没有发票影像。Agent 要正确理解"这张单能不能报销、超没超预算",需要一次性拿到整张关系网,而不是孤立地查一两个字段。
我们最早犯的错,就是把 OA 的"查询表单详情"接口直接暴露给 Agent。结果模型拿到一坨字段 ID 和部门编码,完全不知道这些数字是什么意思,只能瞎猜。后来才明白:接入层不应该把原始接口抛出去,而应该把系统翻译成模型能理解的领域答案。比如查完报销单后,直接返回"申请人:王总监,部门:销售部,费用类型:差旅,金额:12800,预算剩余:3200,当前审批人:李总"这种结构化描述,模型才能做出后续判断。
这也是为什么不能只做"接口转发",而必须做"语义适配"。中老年系统里字段命名混乱、冗余字段多、空值不空值也没个统一规范,这些脏活如果不在一层里处理干净,模型的错误率会居高不下。
2.3 权限矩阵:接口能调通,不代表你有权调
系统接入里最容易被忽视、也是讨论起来最容易起争执的,是权限问题。接口层能连通,不代表以 Agent 的身份调用就一定合法。企业系统的权限是分层的:数据权限管"你能看哪些部门/哪些金额范围";功能权限管"你能执行哪些操作";审批权限管"你能批到哪一级"。
更麻烦的是,很多老系统的接口自身不做权限校验,校验逻辑写在业务代码里。比如新增一张凭证,接口可能只校验字段完整性,真正判断"这个人有没有权限往这个成本中心挂账"的逻辑在后面的业务校验里。Agent 调用接口时如果带的是系统管理员的高权限 token,它确实能调通,但生产环境里这种"能调通"恰恰是事故的开始。
我们后来定的原则是:Agent 永远不要用管理员身份去调用业务系统,每个工具调用都要映射一个最小权限的真实用户身份,每一次写操作都要留下审计痕迹。这一块留到第 5 章详细讲,但它绝对是上生产之前必须想清楚的问题。
3. MCP 帮忙完成协议统一,Blade 做的才是"适配层"的功夫
3.1 MCP 到底是什么:把接口爆炸变成工具列表
MCP(Model Context Protocol)解决的是一个很朴素的问题:如果每个业务系统都有一套自己的接入方式,Agent 框架没法为每一个都写适配代码。MCP 把"Agent 调用外部工具"这件事标准化了:Agent 端跑一个 MCP Client,业务系统侧可以部署一个个 MCP Server,Server 把能力描述成工具列表,每个工具包含名字、描述、参数 Schema,Agent 看到这些描述就知道该怎么调。
这个抽象非常有用。我们先后对比过 Function Calling 直连、自研调度网关、MCP 三套路线。Function Calling 直连的问题是每个系统都得写一遍胶水代码,系统一多就爆炸;自研调度网关灵活,但要自己维护一整套协议和客户端,成本高。MCP 是现成的开源标准生态,社区里已经有大量现成 Server,而且主流 Agent 框架基本都原生支持。
MCP 还有一个关键价值:它把"工具暴露"和"工具实现"解耦了。Agent 框架只看 MCP Server 抛出来的工具描述,完全不关心背后连的是 SOAP 还是 REST,是同步还是异步。这相当于给各种系统的"方言"装了一层统一翻译机,Agent 终于不用关心对面是谁了。
3.2 为什么光有 MCP 还不够:Blade 把"适配层"做薄做干净
不过,用上 MCP 只是第一步。我们很快发现,如果只是把 OA 和 ERP 的原始接口原封不动注册成 MCP 工具,模型面对几百个零散接口,照样会迷路。你给模型一万个工具,它连选哪个都费劲,更别说每个接口的参数还那么不友好。
于是就有了 FDE MCP Blade 这层设计。它本质上是一个运行在企业内网的轻量级 MCP 网关,做三件事:
- 接口收敛:把 OA、ERP 的几十上百个原始接口,收敛成十几个有业务含义的"领域动作",比如发起审批、查询预算、创建凭证。
- 语义翻译:把系统返回的原始数据结构,处理成 Agent 能直接理解的领域答案,同时把脏数据、空值、枚举码这些问题在这一层消化掉。
- 安全收敛:统一在这里做身份映射、权限校验、操作审计、幂等控制,不让模型直接碰到业务系统的原始凭证和 token。
"Blade" 这个词在我们内部其实就是"薄薄的一层刀片"的意思——它不该变成巨石应用,也不该承载复杂业务逻辑,它只负责把系统世界和模型世界之间的边界切干净。模型越自由,适配层越要克制,越要稳。
3.3 工具 Schema 设计的核心心得:让模型"少猜多做"
工具 Schema 写得好不好,直接决定 Agent 的调用准确率。我们的经验是三个字:少、明、稳。
少,指的是参数必填项尽量少。模型每次调用都要自己推断参数,必填项越多,错得越多。默认值能在适配器里处理的,就绝不让模型填。明,指的是每个参数描述的颗粒度要到"给实习生看也能看懂"的程度。比如"date_range"要写成"查询的起止日期范围,格式 YYYY-MM-DD,默认近30天",而不是简单写"时间"。稳,指的是返回结构永远稳定,status、data、message 三段式,哪怕出错也要返回结构化错误信息,绝不允许模型面对一段裸报错自己猜。
这套设计听起来不复杂,但实际效果非常明显。改完 Schema 之后,Agent 的首次调用成功率从不到六成提到了九成以上,很多无谓的重试和幻觉直接消失了。
4. 一条真实接入链路复盘:从 OA 里查单到 ERP 里改单
4.1 场景设定:财务助理完成"差旅报销过账"
拿一个我们生产环境里真实跑过的场景举例:用户说"查一下王总监上个月的差旅报销批了没,批了的话在 ERP 里做一张凭证"。这个需求本质上要跨 OA 和 ERP 两个系统完成一段完整的业务闭环:
第一跳,Agent 调用 OA 的模糊查询工具,按申请人、时间段找到对应审批单;第二跳,调用 OA 的单据详情工具,拿到审批状态、金额、部门、费用类型;第三跳,Agent 判断状态为"已审批通过"后,调用 ERP 的凭证创建工具,把报销单信息映射成科目和金额;第四跳,回写状态,把 ERP 生成的凭证号写回 OA 的自定义字段,方便业务人员核对。
这四个跳看起来简单,但我们第一次跑通花了快两周。问题不发生在任何一步的"单点能力"上,而是每一步之间的衔接——比如 OA 返回的审批状态枚举值是数字,模型不知道 3 到底代表"通过"还是"驳回";再比如 ERP 的科目编码和 OA 的费用类型不是一一对应的,中间需要一个映射表。
4.2 工具的 Schema 与适配器细节
下面是我们实际注册到 FDE MCP Blade 里的三个核心工具(简化版):
{ "name": "oa_search_approval", "description": "按申请人和时间范围查询OA审批单列表,返回审批单ID、标题、状态、金额。状态枚举:0草稿/1审批中/2已通过/3已驳回", "inputSchema": { "type": "object", "properties": { "applicant": {"type": "string", "description": "申请人姓名,必填,示例:王总监"}, "start_date": {"type": "string", "description": "开始日期,格式YYYY-MM-DD,默认当月1日"}, "end_date": {"type": "string", "description": "结束日期,格式YYYY-MM-DD,默认今天"} }, "required": ["applicant"] } }{ "name": "erp_create_voucher", "description": "创建ERP记账凭证。调用前必须确认审批单状态为已通过(2)。凭证号为异步返回,需要调用erp_query_voucher确认最终结果。该操作不可重复提交,重复调用需携带相同request_id", "inputSchema": { "type": "object", "properties": { "request_id": {"type": "string", "description": "幂等键,建议用OA审批单号"}, "voucher_date": {"type": "string", "description": "凭证日期YYYY-MM-DD"}, "amount": {"type": "number", "description": "金额,单位元,保留两位小数"}, "cost_center": {"type": "string", "description": "成本中心编码,来自OA部门映射"}, "subject_code": {"type": "string", "description": "会计科目编码,差旅费默认550101"} }, "required": ["request_id", "amount", "subject_code"] } }这里有个心得:工具描述里要把"隐含业务规则"显式写出来。比如 erp_create_voucher 里明确写了"调用前必须确认审批状态为已通过",模型看到这句话就会先做前置校验,而不是盲目创建凭证。这就是文档不写但生产必需的细节。
4.3 三个绕不过去的坑:会话过期、重复提交、异步回执
生产联调阶段我们踩了三个最有代表性的坑。
第一个是 OA 会话过期。OA 的登录票据有效期一般是 30 分钟,而 Agent 处理一条复杂链路可能要反复调用十几次,中途票据就失效了。我们在适配层做了一个透明处理:工具调用前自动检测票据有效期,剩五分钟时先刷新再执行业务动作。这个逻辑如果放在 Agent 层,模型是感知不到"票据只剩两分钟"这种系统细节的。
第二个是 ERP 重复提交。Agent 在等待异步返回时可能超时重试,如果重试时再调一次创建凭证,ERP 里就会出现两张一模一样的凭证。解决办法就是我们工具定义里的 request_id 幂等键——用 OA 审批单号做幂等键,适配层在请求创建前先查一下这个 request_id 是否已存在,存在就把旧结果返回,而不是重复创建。
第三个是异步回执的"中间状态"误导。ERP 创建凭证接口通常立即返回一个 job_id,Agent 看到返回成功就以为完事了,实际上凭证可能还在排队,甚至最终失败。我们的解法是把工具返回结构统一改成"已提交但未确认"状态,并强制要求 Agent 调用查询接口确认最终结果后,才允许向下执行。
这三个坑有一个共性:它们都是"系统世界"与"模型世界"信息不对称造成的。模型习惯了一步到位的交互方式,而企业系统充满了中间态、过期态和不确定态。适配层存在的意义,就是替模型把这些不确定性消化掉。
5. 敢上生产的底线设计:权限收敛、审计与可控性
5.1 最小权限与身份映射:Agent 绝不拿着管理员钥匙跑
系统接入跑通之后,团队内部最容易松的那根弦是"反正都连上了,给 Agent 配个高权限账号最省事"。这个想法非常危险,因为模型的行为不是百分百可预测的,你无法保证它在哪次调用里会做出超范围的写操作。
我们采用的方案是"双身份映射":每一次工具调用,都以"当前对话的发起人"作为业务系统里的操作身份。也就是说,用户让 Agent 查自己部门的报销单,Agent 在 OA 里实际使用的就是该用户本人的账号权限,查不出权限范围外的数据;要执行创建凭证这类写操作,同样走该用户的权限校验。
同时,适配层还要做一道清单检查:哪些工具属于只读类、哪些属于写操作类。只读工具可以直接放行,写操作工具默认进入"待确认"状态,除非在系统配置里明确标记为"可信自动执行"。这个开关一开始我们全部置为需要人工确认,跑了两个月逐步放开一小部分,才敢让 Agent 真正无人值守。
5.2 审计与断点接管:Agent 的每个动作都要讲得清楚
Agent 项目上线后,业务方最常问的一句话不是"它做对了没有",而是"它做了什么、为什么要这么做"。所以我们从第一天起就坚持把两条日志写全:一条是工具调用轨迹,记录每次调用的工具名、参数、返回结果、耗时、对应到哪个业务系统;另一条是模型推理摘要,记录它基于什么信息做出了这个调用决策。
这两条日志的作用,在出问题的时候就能体现出来。有一次 ERP 里多了一张凭证,财务来问怎么回事,我们翻工具调用轨迹,发现是 Agent 在异步轮询时误判了 job_id 的归属,把上一个任务的凭证状态当成了当前任务的,于是又补了一张修正凭证。如果没有轨迹日志,这种"连锁误操作"几乎没法定位。
另外我们还做了一个小功能:人工断点接管。Agent 在执行链路中如果检测到异常,比如审批状态从通过变成了驳回,会主动停下来在协作工具里通知人,而不是自作聪明地绕过去。后来复盘时我们认为,这个"停下来问人"的设计比任何护栏都管用。
5.3 稳定性设计:超时、重试和限流都得在适配层做
最后聊稳定性的几个细节。Agent 业务的特点是并发不确定,用户可能同时发起几十个任务,也可能一个任务里连续调用几十次接口。企业系统的接口从来不是给这种访问模式设计的,被冲垮的风险是真实存在的。
我们在 FDE MCP Blade 里做了三件小事:第一,给外部系统调用统一设置超时时间,OA 的读操作给 10 秒,ERP 的写操作因为可能排队给到 60 秒,超过就返回超时错误而不是无限等;第二,重试区分幂等与非幂等,读操作可以自动重试三次,写操作只允许使用幂等键重试,否则直接转人工;第三,对后端系统做简单的并发限流,每系统每秒最多放行多少请求,超出部分排队等待,避免 Agent 的一波并发把老系统打挂。
这些设计在联调阶段完全看不见价值,但生产跑一段时间后,你会发现系统稳定性问题的根源基本都在接入层。模型本身反而不容易出幺蛾子,它出问题是有规律可循的,而系统超时、票据失效、数据对不上这些事才是真正防不胜防的。
如果你也在做类似的 Agent 接入项目,我的建议很简单:先把系统接入层当成一个独立的工程来对待,别把它当成模型能力的附属品。模型迭代很快,但企业系统不会一夜之间长出新接口,把那层"脏乱差"消化在适配器里,所有的业务逻辑和权限控制都在这一层收敛清楚,Agent 上层反而可以保持轻盈。
最后再分享一个我们在写工具描述时总结的小技巧:把所有工具的 description 都当成"给实习生写的操作手册"来写,明确写清楚前置条件、返回含义、枚举值、注意事项,而不是写"查询报销单"这种一句话描述。模型每次调用前都会读这些文字,你写得多清楚,它就执行得多靠谱。这套 FDE MCP Blade 我们还在往更多系统上铺,但核心思路没变:模型负责聪明,适配层负责靠谱。