用腾讯云AI Skills构建生产级Agent:从能力拆分到稳定编排
我一开始做 Agent 的时候和大多数人一样直接把所有逻辑揉进一段大 Prompt一个函数一个函数往下堆结果 Prompt 越写越长行为越来越飘稍微改一个场景就得连着重写。后来我把这套东西整体迁移到腾讯云 AI Skills 上用 Skill 作为能力单元重新拆了一遍才真正体会到什么叫“先拆能力再组 Agent”。这篇文章就把我在这个过程中摸出来的方法、踩过的坑、验证过的路径完整记录下来目标读者是准备做 Agent 但还没有一套成熟工具链的开发者尤其是对腾讯云 AI Skills 的架构逻辑不太熟悉、想直接上手跑通并落地到真实项目里的人。1. AI Skills 到底解决了我什么问题聊 Skill 之前先说说我在那之前遇到的几个真实困境。当时我在做的是一个多工具协作的 Agent它需要联网搜索、查数据库、跑一段分析代码、再输出结构化报告。听起来不复杂但一旦这些能力叠加起来问题就开始失控了。1.1 一段 Prompt 塞不下所有能力最开始我把所有工具的调用说明全写在一个 System Prompt 里。工具少的时候没问题到了六七个工具、每个工具又有十几个参数的时候模型开始频繁出错要么把参数类型传错要么在一个工具调用还没返回结果时就去调下一个要么直接把工具名拼错。这背后的原因其实很朴素大模型的注意力是有限的一段 Prompt 塞得越长模型对具体细节的分辨率就越低。就像让一个实习生同时记住三十条规定再去干活他一定会漏掉中间某一条。AI Skills 的做法是把每个能力单独封装成独立单元每个 Skill 有自己的名称、描述、输入输出协议模型只有在需要这个能力时才去“翻阅”对应 Skill 的说明而不是把所有说明一次性背下来。这个设计思路和微服务如出一辙解耦、隔离、独立演化。1.2 Skill 和 Agent 的分工边界很多朋友问过我一个很实际的问题Skill 和 Agent 到底什么关系我自己的理解是——Agent 是调度大脑Skill 是执行手脚。Agent 负责判断“现在该干什么”Skill 负责把“某一件事”干好。举个例子。我那个 Agent 需要一个“天气查询”能力我可以把它写成一个 Skill入参是城市名和日期出参是结构化的天气数据。Agent 要做的是在用户说“明天上海适合出行吗”时调动“天气查询”Skill 拿到数据再结合“出行建议”Skill 做二次判断。如果我把这两个逻辑混在同一个 Skill 里Agent 的调度灵活性立刻下降比如用户只问天气不问建议时我还是会白白消耗一次建议生成的调用。所以 Skill 的粒度控制是有讲究的后面专门讲。1.3 腾讯云 AI Skills 的实现逻辑腾讯云 AI Skills 本质上是一个“工具协议 运行时 调度”的组合。它不限制你用哪种模型也不强制你使用某种特定框架。你只需要按照约定好的方式定义 Skill把它注册到平台上就可以在自己的 Agent 流程里按需调用。这里我不得不提一个容易被忽略的点AI Skills 的服务端支持 HTTP 接口调用也支持云函数、容器等后端形态。也就是说Skill 本身可以是完全无状态的它的输入输出协议统一使用 JSON这让调试和复用都变得异常舒服——我可以把一个 Skill 放在 A 项目里跑也能原样搬到 B 项目里用成本几乎为零。2. 我如何设计一个可复用的 Skill从协议到代码很多人拿到 AI Skills 之后问的第一句话是我该怎么写一个 Skill我给的回答往往是先别急着写代码先把“这个 Skill 是干什么的、需要什么输入、产出什么输出”这三个问题用文字回答清楚。2.1 工具协议的定义方式在腾讯云 AI Skills 里工具协议本质上是 JSON Schema。你告诉系统这个工具叫什么、描述是什么、参数有哪些、每个参数的类型和约束是什么。模型在跑的时候会根据用户请求和对话历史自动决定要不要调用这个工具、每个参数传什么值。我第一次定义工具时栽过一个大跟头描述写得模棱两可。比如我写“获取商品信息”模型根本分不清这个工具是“获取商品列表”还是“获取商品详情”。后来我把描述改成了“根据商品 ID 获取商品详细信息包括价格、库存、图片列表、规格参数如果只知道商品名称请先调用搜索工具获取商品 ID”准确率瞬间就上来了。我总结了一个工具描述模板大家可以参考动词开头说明这个工具做了什么紧接着说明入参是什么以及入参之间有没有依赖关系最后说明输出结构包含哪些关键字段如果有调用前置条件比如必须先获取某个 ID一定写清楚2.2 入参出参格式的设计原则入参设计的原则是“够用但不冗余”。一个工具的参数不是越多越好模型每多一个可选参数出错概率就高一分。我习惯把参数分成两类必填参数和条件参数。必填参数是模型无论如何都要提取的条件参数则依赖场景。出参格式上我强烈建议所有 Skill 都返回统一的包裹结构{ code: 0, message: success, data: { field1: value1, field2: value2 } }别小看这个包装它让 Agent 的后续判断变得非常稳定。模型可以从 message 字段直接判断这次调用是否成功从 data 字段提取真正需要的数据。如果返回结构五花八门模型 ToT 的解析出错率会明显上升。2.3 一个最小可用的 Skill 示例下面我给出一个非常简单的 Skill 定义这个 Skill 的作用是根据城市名称返回当天天气。结构上完全遵循腾讯云 AI Skills 的协议。name: weather_query description: 根据城市名称查询当天天气情况包括温度、湿度、天气现象输入必须为城市中文名如“北京”“上海” parameters: type: object properties: city: type: string description: 城市中文名称例如“北京” date: type: string description: 查询日期格式为 YYYY-MM-DD不传时默认为今天 required: - city后端逻辑我直接用云函数承载核心代码如下import json import datetime def main(event, context): params json.loads(event.get(body, {})) city params.get(city, ).strip() if not city: return {code: 400, message: 缺少必要参数 city, data: {}} # 实际项目中这里调用天气服务 API我这里返回模拟数据 data { city: city, date: params.get(date, datetime.date.today().strftime(%Y-%m-%d)), temperature: 23, humidity: 60%, condition: 晴, } return {code: 0, message: success, data: data}这里我想重点提醒一个很多人会犯的错误不要在 Skill 内部依赖对话上下文。Skill 是被 Agent 调用的它自己不应该猜测用户意图只应当做一件事——根据入参返回结果。我见过有人把“如果用户没说城市默认推荐北京”写进 Skill 逻辑里结果多个 Skill 都开始自作主张推默认值Agent 的整体行为就变得完全不可控了。2.4 错误处理不能只靠正常返回Skill 的错误处理做得好不好直接决定 Agent 能不能平稳收场。我在初版里犯过一个大错把异常情况直接抛出 HTTP 500。结果模型收到一个不知道该往哪里塞的报错只能硬着头皮把错误信息原样展示给用户。用户体验可以说是灾难级的。后来我改成Skill 内部捕获所有异常统一转成结构化的错误返回。比如参数缺失返回 code 400外部接口超时返回 code 504。这样 Agent 收到后可以按照约定进行下一步处理——补问参数、兜底回复或者转人工。这里有一个非常关键的经验错误信息要写成“给 Agent 看的”而不是“给程序员看的”。比如返回“城市不存在请重新提供有效城市名”就比返回“KeyError: city”有用得多。模型是自然语言驱动的东西你给它一句人话它才知道接下来该怎么办。3. 组装全能 AgentSkills 的编排与调度Skill 一旦多起来真正的难题就出现了怎么让 Agent 在正确的时候调用正确的 Skill怎么防止多个 Skill 打架上下文窗口有限怎么管理多轮对话不丢信息3.1 调度策略的两种实现方式我实践中用过的调度方式主要有两种。一种是完全依赖模型的 Function Calling / Tool Use 能力把 Skill 列表全部暴露给模型让模型自己决定调哪个。另一种是写一个显式的编排层用规则判断当前该走哪条链路比如先判断意图再决定调用哪些 Skill。这两种方式各有适用场景。Skill 数量少、功能边界清晰时全部交给模型是完全可行的Skill 数量超过十五六个或者不同 Skill 之间出现边界重叠时我建议加一个显式意图分类层。我用过最简单的实现是先用一个轻量模型把用户请求分类成若干个意图每个意图映射到一个 Skill 或一组 Skill 流程然后在流程内部再做具体的参数提取。别觉得这种“先分类再调用”多一次模型调用很浪费实测下来它在控制幻觉和降低误调用率上的收益远大于那一点点时延代价。3.2 上下文管理的三板斧上下文管理是 Agent 开发里最容易翻车的地方。腾讯云 AI Skills 本身只是一组工具它不会替你维护对话状态所以对话历史、中间结果、用户偏好这些都得自己管。我的做法分三层。第一层是短期记忆也就是当前会话里最近几轮对话。第二层是长期记忆我把用户的关键信息比如偏好、历史订单、常用地址抽成结构化的“用户画像”存起来在需要时注入 Prompt。第三层是工作记忆也就是一次任务执行过程中各 Skill 的中间输出这些数据往往不需要长期保留任务结束就可以释放。有个细节值得注意不要把整个数据集一股脑塞给模型而是只把“这一轮决策真正需要的数据”塞进去。举个例子某个查询任务返回了 200 条记录但如果用户只是要 Top 5你在 Prompt 里给 200 条就有害无益。数据越精炼模型表现越稳定。这是我在多次暴力测试之后最深的体会。3.3 我踩过的编排坑Skill 之间的隐性依赖编排中最容易出问题的不是单个 Skill 写不好而是多个 Skill 之间存在隐性的先后依赖。比如我的 Agent 有一个“查询订单”的 Skill还有一个“申请退款”的 Skill后者依赖前者返回的订单号。但模型在调用时有时会跳过查询直接尝试申请退款导致退款接口报“订单号缺失”。解决这个问题有两条路。一条是在“申请退款”Skill 的描述里写清楚“必须先调用查询订单 Skill 获取有效订单号订单号为纯数字”让模型在函数调用层面就完成依赖判断。另一条是你在编排层做前置校验发现没有订单号时主动插入一次查询调用。我实际验证下来描述约束加代码校验双管齐下最稳。单靠模型自觉在任务简单时看着挺对任务一复杂就容易破功。4. 一次完整事故复盘Agent 执行终止背后的真实根因这里分享一个很有代表性的故障排查案例。我的 Agent 跑了一个月之后突然开始频繁报“execution terminated due to error”日志里没有任何明显的异常栈所有 Skill 都显示调用成功但整个任务就是中途死掉。我花了一整天才找到根因这个排查过程对做 Agent 的人来说非常有参考价值。4.1 第一反应从日志里找线索而不是猜我最初的直觉是某个 Skill 超时了。但把所有 Skill 的调用耗时拉出来看了一遍最慢的也只有 1.2 秒离超时阈值远得很。又怀疑是模型输出被截断但看生成内容也完整。后来我换了个思路给整个 Agent 链路加了链路追踪把“用户输入—意图分类—Skill 调用—结果汇总”每一段的日志都打点。日志一完整问题立刻显现了每次执行终止前意图分类这一层返回的置信度都非常低低于我设置的阈值系统就直接抛了异常走终止流程。4.2 根因意图分类的决策和 Skill 执行状态脱钩为什么会这样我复盘后发现那个新上线的用户场景非常特殊用户的表达方式和训练时的数据分布差异很大模型给所有候选意图打的分数都不够高。而我当时的代码逻辑是“置信度不达标就终止”这就导致用户话还没说完Agent 就直接放弃了。这个问题的本质是我让一个“决策环节”和“执行环节”完全解耦了但决策一旦出错执行再完美也没有意义。处理方案不是盲目调低阈值而是增加一条“低置信度时追问确认”的路由规则。4.3 修复方案把终止改成追问和降级修复时我做了两个改动。第一意图分类置信度低于阈值但高于 0.3 时不直接终止而是生成一句追问“您是想查询订单状态还是申请退款”把决策权交还给用户。第二置信度低于 0.3 时才走兜底流程——让用户用更明确的语言描述需求。改完之后Agent 的“执行终止”警报基本绝迹用户体验也显著提升。这次排查让我明白一个道理Agent 出问题时先别急着改 Prompt先把自己代码里那些生硬的“短路逻辑”找出来。很多表面的模型问题其实是工程问题。5. 从能跑到稳定生产环境必须过的几道关把一个 Demo Agent 跑通很容易但想稳定运行在生产环境里需要面对的挑战完全不一样。我从自己的上线经历里提炼出五个关键点每一个都是在实际故障中换来的教训。5.1 可观测性没有日志就没有排查权生产环境的 Agent 是一个多环节的复杂系统任何一个环节出错如果没有日志你夹在中间根本无法定位。我在腾讯云上给每个 Skill 都接了日志采集然后设置了三个级别的追踪调用级谁在什么时候调了哪个 Skill、参数级入参出参各是什么、内部步骤级Skill 内部在哪个阶段卡住。这里有个我特别想强调的做法给每一次 Agent 执行生成一个 request_id贯穿整个调用链。以后用户反馈问题你只要拿到这个 ID就能把前后所有日志串起来看。没有这个 ID排查就是大海捞针。5.2 记忆机制短期不出错长期不迷失关于 Agent 的记忆我的经验是能不用长文本历史就不用尽量用结构化数据。把多轮对话压缩成关键信息存进记忆库。每次调用 Skill 前只把和当前任务相关的记忆注入。比如用户上一轮说“帮我选一个续航长的手机”这一轮说“价格控制在三千以内”你要注入的是抽出来的约束条件而不是把两轮对话原样拼接丢给模型。这样既省 token又减少模型被无关信息干扰的概率。5.3 安全边界Tools 层面和内容层面的双重治理Agent 一旦具备调用工具的能力安全就不是可选项了。我在平台上做了一个白名单机制Skill 的调用权限按用户角色细分普通用户只能调查询类 Skill管理员才能调写入类 Skill。同时在 Skill 内部所有外部参数我都做了格式校验防止注入类输入混进系统。内容安全层面我建议在 Agent 的输出侧加一道过滤一是关键词过滤二是模型自评估。后者可以简单实现成生成完回复后再用一次轻量请求判断回复是否符合安全规范。虽然多花一点算力但对提升可用性非常有帮助。5.4 成本控制不能让一个 Agent 吃光全场算力我在成本控制上有一个比较有效的思路给不同 Skill 配置不同档位的模型。简单的实体抽取我用小参数模型复杂推理和生成才用大模型。腾讯云 AI Skills 本身支持后端服务灵活部署所以模型选择完全是我自己控制的。另外我还做了一层“结果缓存”。对于常见问题的高频查询比如天气、汇率、节假日直接缓存最近一次结果命中缓存就不再调用大模型。这一招让我的整体成本下降了大约四成效果非常可观。5.5 评估机制没有评估就没有迭代最后一定要建立评估集。我把过去一个月所有用户的真实请求收集起来挑出 200 条覆盖各种典型场景的每天跑一遍回归测试。只要 Agent 的行为偏离预期立即能看出来避免问题在线上积累成灾。评估集不需要很大但一定要有。我见过太多项目上线后全凭感觉调 Prompt今天觉得好了明天又坏了周而复始。用固定评估集的好处是你调的任何东西都有前后对照能客观判断到底是变好了还是变坏了。6. 一些值得补充的实战细节与扩展思路在把 Agent 从个人项目推向小团队共同维护的过程中我陆续遇到了不少新问题也尝试了一些新的解决方案。写在这里供大家借鉴不一定所有场景都适用但至少能帮你少走几步弯路。6.1 关于 Skill 的命名与目录规范只要 Skill 数量超过 10 个命名不规范就是一场灾难。我的命名习惯是“领域_动作”的格式比如 order_query、product_search、account_balance一眼就能看出是哪个领域下的什么能力。描述里再补一句使用场景比如“在用户询问订单配送地址时调用”。这套规范的收益在团队协作时会放大——别人读你的 Skill 列表时不需要逐个点开详情。6.2 关于 Agent 与外部系统的对接模式Agent 调用外部系统时最好在 Agent 和外部系统之间加一层适配器不要直接让 Agent 面对外部系统的复杂协议。这样做的好处是外部系统改了接口你只需要改适配器不需要重训或重写 Agent 逻辑。我在对接一个老系统的 Socket 接口时深有体会没有这层适配器整个 Agent 都会被那个不稳定的老系统拖垮。6.3 关于 AI Skills 与本地工具链的配合我在本地开发调试时会先用模拟数据把整个 Agent 跑通再切入真实 API。腾讯云 AI Skills 的协议是标准的 JSON Schema本地完全可以写一套 mock server 来模拟。先用 mock 跑一遍等到逻辑稳定了再切真实服务可以极大缩短线上调试时间。6.4 关于模型选择与 Skill 复杂度的关系最后说一个有意思的发现Skill 本身就是一种“复杂度收纳器”。你会发现同一个模型在用一个封装良好的 Skill 时几乎不会犯参数错误而用一个粗糙的不带 Schema 约束的工具时错误率直线上升。所以当你在抱怨“模型不够聪明”时先检查一下是不是自己的工具协议定义得不够健全。我的习惯是所有参数都加上 description 和示例值所有可选的默认行为都写清楚能枚举的字段尽量给枚举值。这些看似琐碎的小事恰恰是决定 Agent 最终是“能用”还是“好用”的分水岭。我自己在实践中最深的体会是Agent 能力的上限不完全取决于模型参数更取决于你怎么把能力拆分、定义、编排和治理。Skill 是一种极好的能力抽象但真正让它发挥威力的是你对整套系统的理解和打磨。保持小步快跑持续调优你的“全能 Agent”会一天比一天更靠谱。