2026/10/11 3:43:59

Agent技能中心:用工程约束把AI从Demo变成可交付的生产系统

Agent技能中心:用工程约束把AI从Demo变成可交付的生产系统 做了一段时间 Agent 之后我最大的感受不是“AI 太强了”而是“AI 太脆了”。跑一个演示 Demo 的时候它能给你讲段子、写代码、做表格看着好像什么都会一旦接进真实业务里它就经常一本正经地出错——把参数填反了把日期格式搞混了甚至是告诉你说已经调用成功了但后端日志里压根没有那条请求。所以我很早就意识到一个事想让 AI 可靠不能只靠模型本身更得靠周边配套的工程约束。于是我做了一个给 Agent 用的网站核心是技能中心skills。说白了就是把 Agent 要用的各种能力——查订单、导报表、发消息、调库存——全部变成可注册、可校验、可回放的模块而不是散落在 prompt 里的自然语言描述。这篇文章就把这个项目的来龙去脉、整体设计、实操过程和踩坑记录都摊开讲一遍。适合正准备把 Agent 接到真实业务里的开发者也适合被“模型效果还行但一上生产就翻车”折磨过的团队。你看完不一定照搬我的方案但至少能少走几段弯路。1. 先把 Agent 的可靠性短板盘清楚再做网站1.1 可靠性问题通常不在模型而在外围我最初接手的是一个客服类的 Agent 场景它需要调库存、查订单、写工单。第一版做法很直接在 prompt 里写清楚“如果要查订单调用某接口如果要查库存调用某接口”。看起来没什么问题可上线跑了一周就陆续翻车。最典型的几个现象模型偶尔把 customerId 和 orderId 传反用户问“今天发货了没”时它自己脑补出一个接口名去调后端 404它却告诉用户查询成功甚至有一次它调用了一个已经被下线半个月的老接口因为 prompt 里还留着那段描述。问题不在模型的智力而在于没有任何一层机制去约束它“能调什么、怎么调、调完怎么验证”。这里有个很重要的认知大模型的随机性没法根治但可以通过工程手段把风险压到可接受范围。就像你招了一个能力很强但记性不好的实习生你不给他流程、不给模板、不给审批他发挥一两次还能靠天吃饭做多了一定会捅娄子。Agent 也是如此而且它的“记性不好”会以更隐蔽的方式表现出来——它不会承认自己忘了它会把合理但不正确的行动包装成确定的结果。1.2 为什么不用配置文件而要做成一个网站也有人说这类问题用一套 JSON 配置加几个脚本也能解决为什么非要做网站我的回答是Agent 少的时候可以Agent 一旦变多、业务一旦复杂配置文件模式必然拉胯。配置文件分散在各处改一个技能定义要动代码仓库发布完还要手动同步给多个 Agent很容易出现“A Agent 在用 v1B Agent 已经在用 v3”的漂移问题。另外还有权限和审计配置文件里没法做细粒度的权限控制谁改了什么、谁调用过什么、某个技能最近成功率多少全都是一团黑。这时候就需要一个集中式的控制平面。技能中心做成网站之后至少解决了三件事团队可以对着界面审查一个技能该不该上线Agent 可以通过接口动态获取技能列表和参数说明每次调用都有忠实记录出问题能回放。本质上它就像一个餐厅菜单Agent 是顾客后厨是各种后端服务。菜单必须集中印出来、定期更新否则顾客只能乱点后厨也做不对菜。2. 技能中心长什么样给 Agent 立规矩的四个核心模块2.1 核心模块拆解注册、校验、运行、观测这个网站不是什么大平台它由四块核心构成每一块都对应 Agent 可靠性链条上的一个环节。第一是技能注册表。这是最基础的数据模型存每个技能的唯一 ID、名称、版本、描述、入口地址、请求方式。它决定了 Agent 能看到哪些能力而控制“能看到哪些”本身就是一种约束。第二是参数 Schema。它规定了每个技能能接收什么参数、参数类型是什么、哪些必填、哪些可选甚至可以做正则校验。这是防“传反参数”的关键。你一定要知道大模型对参数的“自由发挥”是错误高发区明明接口要求的是字符串订单号它可能传一个数字或者把日期从 YYYY-MM-DD 改成 YYYY/MM/DD。第三是运行环境配置。包括目标服务地址、密钥、超时时间、重试策略、限流阈值。密钥什么的绝不能写进 prompt一旦写进去就等于公开给模型自由发挥迟早会带进对话里泄露出去。第四是调用日志与回放。每次 Agent 发起调用请求参数、响应内容、耗时、状态码都要留痕。这块是最容易被忽视但恰恰是最关键的一环。没有日志你连它怎么错的都不知道只能靠猜。这四个模块回答的是同一个问题这个技能在什么条件下、由谁、以什么参数、调用哪个后端结果是否符合预期。每一条都有记录可靠性才有讨论的基础。2.2 Skills 的标准化设计让 Agent 看懂说明书我在打磨这个项目的过程中花时间最多的不是前后端代码而是技能定义的规范。刚开始我按直觉写了一大段自然语言描述比如“该接口用于查询订单传入订单号返回订单状态和金额”结果 Agent 理解得七零八落。后来我才明白自然语言描述是给模型看的第一层引导但真正能让系统稳定的是结构化的 Schema。我最终用的技能定义类似于下面这样{ skill_id: order_query, version: 1.2.0, name: 订单查询, description: 根据订单号查询订单状态、金额、收货信息仅支持已归档订单。, entrypoint: https://api.internal.example.com/v1/order/query, method: POST, parameters: { order_id: { type: string, required: true, description: 订单号格式为两个大写字母加12位数字, pattern: ^[A-Z]{2}\\d{12}$ }, include_details: { type: boolean, required: false, default: false } }, output: { type: object, properties: { status: {type: string}, amount: {type: number} } }, permissions: [order:read], timeout: 5, fallback: order_query_from_cache }其中每一项都有它的用意。description 是给模型的引导告诉它这个接口在什么场景下用parameters 里的 type、required、pattern 是给校验器用的硬规则防止模型传错类型或格式timeout 和 fallback 是给运行时用的保护应急时走缓存兜底。这里我特别想强调 pattern 的作用。没有正则校验前模型经常把订单号传成小写或者缺几位接口虽然也能识别一部分但一旦出现边界情况就查不到数据。加上 pattern 之后非法请求在进入后端之前就被拦下来了不仅减少了脏数据也减少了后端压力。对用户来说错误提示也变得更稳定。2.3 权限与隔离工具越丰富越要防呆技能多了以后权限问题会变得非常突出。一开始我的做法很粗糙只要 Agent 会用到的技能就全放开结果出了两次事故。一次是某个 Agent 在测试环境误调了生产环境的接口另一次是一个低风险场景的 Agent 不小心拿到了删除工单的能力虽然没造成实际损失但足够让人后背发凉。后来我加了三层约束。第一层是技能级权限。每个技能定义里有 permissions 字段Agent 使用前必须带 token技能中心校验 token 对应的权限是否包含该技能标识。这样就不会出现“能查就能删”的问题。第二层是环境隔离。开发、测试、生产三套环境在技能中心里是完全隔离的技能定义里的 entrypoint 可以通过环境变量动态注入。Agent 在测试环境里跑永远只会拿到测试环境的地址和测试密钥。第三层是人工审批闸门。高风险技能比如删除、转账、大批量消息推送发布后默认是“禁用”状态需要在网页控制台里人工启用运行时如果触发高风险操作还可以配置二次审批。这相当于给 Agent 加了一道刹车避免它在某个瞬间做出不可逆的动作。我一直觉得权限这件事不能等到出事之后再补前期花半天把权限模型设计好后边省的不止是排查问题的半天还有可能是一次事故的善后成本。3. 从零动手搭技能中心的实操过程与关键步骤3.1 技术选型和环境准备作为个人项目或小团队内部工具我不建议一上来就上微服务、上编排引擎够用就行。我选型时主要考虑三点开发速度快、异步能力强、生态里工具链完整。后端我用的是 Python 系的异步 Web 框架因为它写起来快而且 Agent 调用链路本身是 IO 密集型的异步模型非常合适。数据库用的关系型数据库主要存技能元数据和调用日志表结构不复杂不需要专门引入文档数据库。前端就做一个简单的控制台用来注册技能、编辑 Schema、查看日志不需要花里胡哨。依赖方面建议准备一套容器化运行环境因为技能中心后续要接各种后端服务容器化部署和灰度发布都会方便很多。如果你有消息队列可以作为可选组件用来异步处理调用日志的写入但如果日志量不大同步写库也没问题。我劝你在动手前先想清楚一个事这套技能中心未来的使用者是谁。如果只是自己调试后端接口加一个简陋前端就够如果要给团队用就得考虑多用户登录、操作审计和权限管理。我从一开始就按“团队可用”的标准来设计虽然前期成本高一些但后面不用返工。3.2 技能从定义到上线的完整流程我在项目里把技能上线流程分成了五步配合网页控制台整个链路大概是这样。第一步编写技能定义 JSON通过接口或网页表单提交。这一步的关键是认真写 Schema 里的 description 和 pattern不要偷懒。我发现很多人描述技能时写得太抽象比如“查询订单信息”但根本没说清楚订单号格式、返回结构、异常场景。模型看这种描述跟看天书差不多它只能靠猜。第二步系统自动做格式校验和连通性测试。这里的连通性测试是指在后台对 entrypoint 发起一个最小化的探测请求确认接口活着、鉴权方式正确。这种冒烟测试特别重要能过滤掉一大半“定义写得对但实际调不通”的问题。第三步在测试环境跑一次带样例参数的调用。我会针对每个技能维护一组测试用例参数覆盖正常值、边界值、缺参和类型错误四种情况。这一步的目的不仅是验证接口逻辑更是验证 Schema 的校验能力到底拦不拦得住异常请求。第四步发布到生产环境并记录版本号。发布后技能中心会生成一个新的技能版本同时生成一份版本指纹Agent 启动时拉取这个指纹确保自己用的是最新版。这里我踩过一个坑后面讲排查的时候细说。第五步在网页控制台检查调用日志确认发布后头几个真实请求都正常。一个技能的完整上线流程用不了太久关键是每一步都不能省。尤其是测试环境用样例参数跑一遍很多 Agent 调用错误都是在这一步提前暴露的。3.3 Agent 调用侧让模型学会按说明用技能技能中心本身只解决了“技能怎么被管理”的问题真正要让 Agent 可靠还得在调用侧下功夫。我最初的方案是把所有技能的定义全部塞进系统提示词里结果有几个问题内容太长导致上下文浪费模型抓不住重点技能定义的格式还会干扰它对当前任务的判断。后来我改用“按需注入”的方式。在每轮对话或任务开始前先从技能中心拉取所有技能的轻量索引让模型自己判断哪些技能和当前任务相关然后只把相关技能的完整定义注入到提示词里。这就像给实习生一份目录他先查目录找到对应的章节再细看那一页而不是把整本书扔给他。另外我非常坚定地做了一个动作在系统提示词里明确要求模型在调用工具之后必须等待工具返回结果再生成最终回复不能自己脑补执行结果。这听起来很基本但如果不写这一条模型会经常出现“假装调用成功”的情况。配合技能中心的日志回放能力一旦发现某条任务回复里声称“查询成功”但日志里根本没有对应调用记录就能快速定位问题。我还给 Agent 用了一个统一的工具调用模板示例大致如下你现在可以使用以下技能 1. order_query: 查询订单状态。参数order_id字符串必填格式为两个大写字母加12位数字。 2. stock_query: 查询库存。参数sku_id字符串必填。 当用户意图涉及查询时先确认参数完整再调用对应技能。技能返回结果后基于结果生成回复。这个模板看起来很简单但它同时做了三件事明确技能边界、强调参数确认、要求基于结果回复。很多 Agent 工程的可靠性提升靠的就是把这些微小但关键的细节抠到位。4. 常见问题的排查实录与防呆细节4.1 高频问题速查表技能中心上线几个月之后我把遇到过的典型问题整理成了一张表。这里直接放出来大家可以对照排查。现象可能原因处理办法Agent 声称调用成功但日志为空模型编造执行结果未走真实调用提示词中强制“等待返回结果”并用日志做核对参数值类型错乱Schema 只做了存在性校验没做类型校验在 Schema 中声明 type并在运行时强制校验多个技能同名或功能重叠缺少唯一 ID 和同义技能检测注册时查重设置技能别名并自动合并技能描述太长上下文膨胀全部技能定义都注入提示词改为按需注入只加载高相关技能调用超时但 Agent 还在等未设置单次调用超时上限技能定义增加 timeout 字段超时返回默认值新版本上线后部分 Agent 还在调旧接口Agent 未同步技能版本指纹启动时强制拉取版本指纹发现不匹配立即告警测试环境 Agent 调了生产接口缺少环境隔离将 entrypoint 和密钥通过环境变量按环境注入这张表里的问题我基本都实际遇到过不是凭空想出来的。尤其是前两条几乎每个做 Agent 集成的团队都会撞上。4.2 三个具体案例复盘第一个案例是参数类型问题。有一个技能接收“金额”我在 Schema 里只标了 required 为 true忘了标 type。结果模型在某个场景下传了一个字符串比如 1000.00后端接口按数字处理直接失败报错又不够友好Agent 就回复用户“系统繁忙请稍后再试”。排查了半天才发现是类型校验漏了。后来我把所有参数的 type 都补全并在运行时统一加了一道类型校验类似问题再没出现过。这件事给我的教训是Schema 里的每一项都不能想当然一个默认值、一个类型声明都可能在关键时刻派上用场。第二个案例是版本漂移。某次技能从 v1.1 升级到 v1.2修复了一个字段映射错误。但当时有 3 个 Agent 在并行跑其中一个在升级期间没有重启继续调用 v1.1 描述里的旧接口。由于旧接口并没有立即下线它依然能返回结果只是结果里几个字段的取值规则已经变了导致最终展示给用户的数据有偏差。这个案例说明如果没有版本指纹机制你根本不知道现在线上到底跑的是哪个版本。后来我在启动流程里加了一步强制拉取技能中心版本指纹不一致就拒绝加载。第三个案例是上下文爆炸。早期我把所有技能定义都塞进系统提示词技能数量到 20 个左右时每轮对话光技能描述就占掉了大半 token模型反而开始忽略重要的任务指令。后来我改成索引式注入模型先看技能名称和一句话摘要决定要不要看完整描述。实测下来上下文占用大幅下降技能选择的准确率反而更高了。这也印证了一个观点给模型的信息不是越多越好而是要精、要相关。4.3 让系统更皮实的几个防呆细节除了排查问题我后来还陆续加了一些“防呆”设计让系统在异常情况下不会直接崩掉而是降级或拒绝。幂等设计被我放在第一位。Agent 调用外部服务时技能中心会生成一个 request_id 并透传给后端。这样即使一次调用因为超时而触发重试后端也能识别出这是同一个请求不会导致重复发消息、重复扣款。没有幂等之前我是不敢给 Agent 开放写操作的有了它才敢逐步扩大技能范围。其次是超时降级。每个技能都有 timeout 配置超过这个时间就不继续等直接返回一个降级结果。比如“查询订单”超时后可以先返回缓存中的订单状态并提示模型“数据可能不是最新请提醒用户稍后重试”。这样做虽然牺牲了一部分实时性但避免了 Agent 长时间悬空等待用户体验会好很多。最后是日志保留策略。调用日志是排查问题的金矿但也不能无限保存。我给日志设置了保留窗口热数据保留 30 天冷数据归档到对象存储。这样排查近期问题很快历史数据也不会占用太多数据库空间。做完这套技能中心之后我最大的体会是模型决定 Agent 的上限工程决定 Agent 的下限。skills 中心存在的意义就是不断抬高这个下限让 AI 出错时能快速暴露、快速定位、快速止血。如果你也在做 Agent 应用我的建议很直接别急着堆模型能力先把技能注册、参数校验和调用日志这套基础机制立起来。哪怕一开始只是用配置文件模拟也比让 Agent 在 prompt 里裸奔强得多。