做 Agent 开发这一年多,我最大的感受是:真正让项目“活”起来的往往不是模型本身,而是工具调用这一层。模型再聪明,如果它不知道你的业务接口怎么用、不知道参数该传什么、返回结构一变化就没法处理,整个 Agent 就卡死在那里。最近我把一套内部运维查询类 Agent 迁移到腾讯云 AI Skills 上重新整理了一遍,从原来写死函数调用、到处补异常的泥潭里跳了出来。这篇文章就把我这段时间的踩坑、设计思路和可复用的最佳实践完整写出来,希望对正在做 Agent 项目的你有参考价值。
1. 从“会写工具”到“会调度工具”:Agent 开发为什么需要 Skills
1.1 没有标准化的工具调用有多痛
先说一个非常普遍的场景。你用大模型接一个查询服务,最朴素的做法是:写好一个函数,在 system prompt 里告诉模型“你有一个 query_order() 函数可以用”。但真到生产环境你会发现,这玩意儿根本撑不住。
模型不是人,它不会老老实实按你给的函数签名来。它会记错参数名,会把字符串类型的日期传成整数,会在同一个问题里连续问好几次同一个接口,返回结果一大就容易把上下文撑爆。我最早做那个 Agent 时,光处理函数调用异常就写了一百多行 if else,而且每加一个新工具,就要同步改 prompt、改错误处理、改返回结构,工作量直接爆炸。
后来我意识到,问题不是模型“笨”,而是工具这一层太粗糙。模型需要一个结构化的“技能清单”,它得知道每个技能是干什么的、参数长什么样、什么情况下该用它、调用失败后该怎么办。这些如果靠 natural language 在 prompt 里硬写,既不规范,又浪费宝贵的上下文空间。腾讯云 AI Skills 这套方案解决的就是这个问题。
1.2 腾讯云 AI Skills 到底是什么
如果你之前没接触过这个概念,我这样解释:AI Skills 本质上是把“工具”提升为“可被模型理解、可被平台托管、可被观测复用的标准技能单元”。
过去我们是“给模型一个函数”,现在是“给模型一本说明书加一个对接好的执行通道”。Skill 包含三块核心内容:
- 能力描述:这个技能做什么、适用场景是什么,用自然语言写清楚,让模型能准确匹配。
- 参数 Schema:结构化声明,输入输出都有明确类型、格式、约束,模型按 Schema 填参数,平台侧校验后再执行。
- 执行逻辑:对应一段真实的业务代码,可以是查询数据库、调用 API、做计算,也可以是编排其他 Skill。
把这三块独立开来有个很直接的好处:模型的调用准确率提高了,因为描述和 Schema 是“给它看”的;执行逻辑是“给它跑”的,两者解耦之后,调整业务逻辑不用重新写 prompt,调 prompt 也不用动代码。
1.3 什么项目真正适合用 AI Skills
不是说所有 Agent 都必须上 Skills。我自己判断的标准很简单:如果 Agent 要调用的工具超过三个,或者工具逻辑经常变,或者你希望 Agent 能独立完成一条完整的任务链路,那就值得用 Skills 抽象一层。
比如你只是做一个一次性 Demo,只有一个 call 函数,用标准 function calling 就够了,没必要折腾。但如果你在做的是正经项目,比如我手头这个运维查询 Agent,要查服务器状态、查日志、查告警、执行一些简单操作,每个能力背后都是一组接口加逻辑,Skills 的价值就非常明显了。
另外一个容易忽略的点是团队协作。Skills 拆出来之后,每个技能可以被不同 Agent 复用,也可以分配给不同同学维护。这个对项目长期演进来说,比省那点开发时间重要得多。
2. 动手前的设计:Skill 如何拆、怎么定边界
2.1 先想清楚业务,再想技术
我这次踩到的第一个坑,就是一上来就急着写代码,没先做业务拆解。结果 Skill 定义得特别粗,一个“查询服务器信息”的 Skill 把 CPU、内存、磁盘、进程全塞进去,参数一大堆,模型经常选错。后来我重新梳理,把“获取服务器基础状态”“查询磁盘使用率”“拉取指定服务日志”拆成三个 Skill,每个参数都控制在两到四个,调用成功率立马上去了。
所以现在我做 Agent 项目,第一步永远是画任务流:用户一句话进来,Agent 需要分几步才能完成?例如“帮我看看这两天哪台机器磁盘快满了”,拆出来就是两步:“拉取服务器列表” + “批量查询磁盘使用率”。这个自然就对应两个 Skill。
拆 Skill 的原则我总结成三句话:
- 一个 Skill 只解决一个明确问题,别贪多。
- 参数宁少勿多,模型在少量参数下的选择准确率会高很多。
- 返回结构要稳定,哪怕业务内部变,对外输出的字段也别随便改,否则模型会懵。
2.2 Skill 的输入输出和参数 Schema 怎么设计
这是整个 AI Skills 实践里最重要的部分,没有之一。我见过太多人在这块偷懒,Schema 写得随意,结果模型调用准确率惨不忍睹。
先说参数类型。能用枚举就用枚举,能用 number 就不要用 string。比如“时间范围”这个参数,如果你让模型自由填字符串,它会填出“昨天”“24h”“2025-01-01 到 2025-01-02”这种五花八门的格式。我在设计时把这个参数定义成 number 类型,代表小时数,然后在描述里写清楚“只接受正整数,最近 N 小时”,模型就很少出错了。
再说描述。这里有个容易被忽略的原则:描述是写给模型看的,不是写给用户看的。你要用模型能理解的方式描述场景,比如一个磁盘查询 Skill,参数描述写成“服务器 IP,必填,支持 IPv4 格式”,这比写“请传入要查询的服务器地址”要好得多,因为模型在意图理解阶段会先做语义匹配,明确的约束词能显著提升匹配准确率。
返回结构方面,我强烈建议统一成一个标准格式,比如:
{ "success": true, "data": { ... }, "error_message": "" }这样模型在处理多个 Skill 返回时,可以用一套逻辑做结果判断,不需要每个 Skill 特殊处理。我自己最初每个 Skill 返回格式都不一样,Agent 在判断“这个调用到底成功没有”时偶尔会出错,统一之后这个问题基本消失了。
2.3 同步想清楚失败模式
Skills 设计时还要提前想失败的情况。一个 Skill 可能因为网络问题失败、可能因为参数非法失败、也可能因为依赖的下游接口挂了失败。我的建议是:在 Schema 里明确声明一个统一的错误返回结构,并且在描述里补一句“如果查询失败,返回 error_message,不要重试超过两次”。
别小看这句话,它是在给模型“行为约束”。没有约束时,模型可能会因为一次失败连续重试五六次,把下游接口打爆。我实际遇到过类似情况,后来在描述里加了一句简单的失败处理约定,问题就解决了。这类“行为约定”本质上是把你在代码里可能要写一堆 if else 的逻辑,用模型能理解的方式提前声明好,成本极低,收益却很直观。
3. 实操:在腾讯云上从零搭一个“信息查询类”Agent
3.1 环境准备与基础认知
在开始动手之前,先把最基础的环境理清楚。我做这套项目时的技术栈是:大模型走标准 OpenAI 兼容接口,业务代码以技能函数的方式上传,整个编排和调度都依托腾讯云的 AI Skills 能力。
要准备的东西其实不多:
- 一个腾讯云账号,开通 AI 相关服务,拿到访问密钥,这个在控制台的访问管理里就能申请。
- 准备一个测试用的 API 或者数据库连接,我这边是先拿一个简单的查询请求做验证,不需要一开始就连正式环境。
- 本地装好 Python 3.9+ 和常用依赖库,主要用来调试技能函数本身的逻辑。
在这个过程中我犯过一个很低级的错误:密钥直接写死在配置文件里,后来推到仓库才发现。虽然只是个人项目,但这个习惯很不好。正确做法是用环境变量注入,或者直接放到腾讯云的密钥管理里。建议大家从一开始就养成这个习惯,省得后面返工。
3.2 创建第一个 Skill 的完整步骤
腾讯云 AI Skills 的创建路径,大致是按“新建技能 → 填写技能标识 → 配置能力描述和参数 Schema → 上传执行代码 → 测试并发布”这条线走。
第一步是技能标识。这个标识在后续 Agent 编排里会用到,建议统一命名风格。我自己用的是小写字母加下划线,例如 query_disk_usage。这里多写一句:命名别用太泛的词,比如 tool1、api_call 这种,模型在意图匹配时看到这种名字是很懵的,因为它没有任何语义提示。
第二步是能力描述。这是整个创建过程里对模型行为影响最大的一环。描述写得好不好,直接决定了触发准确率。我的模板一般是三句话:这个技能做什么;什么场景下使用;什么情况下不要用。第三句非常关键,它能防止模型在本来不该调用时乱调。比如磁盘查询的 Skill,我会写“仅当用户明确询问磁盘空间、磁盘使用率时才调用,不要用于查询 CPU 或内存信息”。
第三步是参数 Schema。刚才说了,能限定类型就限定类型,能用枚举就少用自由填写。这一步很考验业务理解,你得把用户五花八门的说法抽象成模型能填的结构化字段。我在设计一个查询类 Skill 时,发现用户经常说“昨天”“最近一周”这种相对时间,但如果 Schema 里的字段是“起止时间字符串”,模型就要做时间转换,容易出错。后来我改成“最近 N 天”这样的整数参数,再在描述里补充说明允许的相对时间表达方式,模型的调用准确性明显提升了。
第四步是执行逻辑。Skill 的执行代码本质上是一个函数,入参就是模型按 Schema 填的字段。这个时候要注意一个容易被新手忽略的点:不要相信模型传进来的参数一定合法,代码层面一定要再加一道校验。比如 IP 地址,模型可能传一个格式明显不对的字符串,你如果直接拿去查数据库,可能查出一堆乱七八糟的结果。我在执行函数开头会先做一次参数清洗和合法性检查,不合法直接返回特定错误码,让模型知道我传的参数有问题,它就会根据错误信息重新组织一次调用。
第五步是测试。上传完代码之后,AI Skills 平台一般会有调试入口,你可以手动填参数跑一遍,也可以模拟模型调用来验证返回格式。这里要特别留意返回字段的类型,比如磁盘使用率,我在测试时就踩过一次坑,代码里返回的是字符串“85”,但我的 Schema 声明的是 number,导致下游 Agent 在做数值比较时出了问题。这个我在后面问题排查部分会单独展开。
3.3 接入 Agent 并跑通全流程
Skill 创建好之后,下一步就是把 Skill 接入 Agent,相当于把技能挂到 Agent 的“工具清单”上。以我这次做的查询类 Agent 为例,整条链路是这个样子的:
- 用户提问:比如“帮我看看 10.0.1.5 的磁盘是不是快满了”
- Agent 收到问题,先做意图识别,命中 query_disk_usage 技能
- Agent 按照 Schema 生成参数:{"host_ip": "10.0.1.5", "time_range_hours": 24}
- 平台校验参数并通过后,执行技能代码
- 代码返回结果,Agent 把结果组织成自然语言回给用户
我在第一次跑通这个流程时,遇到的第一个问题出现在参数生成环节。用户说的“快满了”是一个很口语化的表达,模型在填阈值参数时不知道填多少合适。我在 Schema 里其实没有设置阈值参数,只是返回磁盘使用率数据,所以模型在组织回复时就直接告诉用户“使用率 92%”。结果用户不满意,他希望 Agent 能主动给出判断。后来我在描述里加了一句“如果使用率超过 80%,请在回复中主动提示可能空间不足”,模型就会基于返回数据做进一步的判断,这个体验就自然多了。
所以你看,这里有个很重要的认知:Skill 不只是一个工具执行层,它也是你和模型之间的“沟通层”。你想让模型在拿到结果后做什么,就得在描述和 Schema 里把这些行为约定讲清楚,模型才会按你的预期去处理结果。
3.4 增加一个“有状态”的 Skill:从查询到操作
查询类的 Skill 跑通之后,我开始尝试往 Agent 里加一个“有状态”的操作类 Skill,这里的水比想象中深。所谓有状态,指的是这个操作不是一个纯粹的读操作,它可能改变系统状态,比如创建一个云主机、重启一个服务、备份一份数据。
在接入这个操作类 Skill 时,我做的第一件事不是写代码,而是加一个“确认环节”。原因很简单:模型在意图理解上虽然有进步,但直接让它执行不可逆操作,风险是真实存在的。用户说“帮我把那台挂掉的机器重启一下”,模型如果理解错了对象,把正在跑业务的机器重启了,后果很严重。
我的方案是新增一个前置判断:当模型命中“重启服务”这个操作类 Skill 时,平台侧会先返回一个待确认的结果给用户,用户确认之后才真正执行。这个“二次确认”的能力在 AI Skills 平台上是支持的,我强烈建议所有涉及状态变更的 Skill 都加上这个机制。加完后,操作类 Skill 的安全性就基本可控了。
这个环节给我的启发是:Skills 不仅是在帮助模型“做事”,也是在帮助我们“约束模型做事的方式”。平台执行还是应用层兜底,都应该先把不可逆操作的确认机制设计好,再谈效率。
4. 部署与上线:版本、并发、成本一个都不能少
4.1 版本管理与灰度发布
AI Skills 说是“上传代码就能跑”,但在生产环境里,你不可能改一行代码就直接全部生效。我在上线阶段直接把这个问题踩了个正着:有一次我优化了一个 Skill 的执行逻辑,没做灰度,直接发布,结果和另一个 Agent 在用同一套 Skill 的调用方发生了兼容性问题,连续有几个请求拿到了非预期的返回结构。
后来我养成的习惯是:每次修改 Skill,都会在功能上新建一个版本,调试通过后先发布到测试环境,让测试 Agent 调用验证,确认没问题再推到生产。腾讯云 AI Skills 本身是支持多个环境独立发布的,这个能力建议从一开始就用起来,不要等到出事故了再补。
版本管理的策略我总结了一下:
- 线上稳定版本轻易不更新,除非是修 bug 或加兼容性字段。
- 新功能先发布到测试环境,跑一段时间再切生产。
- 每次发布都记录变更说明,尤其是 Schema 和描述的变化,这些对模型行为影响最大。
4.2 并发与超时参数调优
Skills 跑起来之后,性能和稳定性就变成了主要矛盾。我这边遇到过一个典型场景:用户批量查询几十台服务器的磁盘使用率,Agent 会并行发起多个 Skill 调用请求。这时候如果执行代码是串行写的,一个请求要几十秒才能返回,用户的等待体验就灾难了。
我后来在写耗时类 Skill 时,会把“并发执行”和“超时控制”两个参数一起考虑。比如批量查询场景,我会在执行代码里做并发分组,比如每 10 台一组并发跑,同时整个 Skill 的执行超时时间在合理范围内设置,避免长时间占用资源但反馈却不及时。超时时间设多少,需要看你下游接口的响应速度,我这边一般会设置有明确上限的时间,宁可让模型重试一次,也不让它傻等。
另外还有一个很容易踩的坑,就是模型侧的超时设置和 Skill 侧的超时设置不一致。模型在等待工具调用结果时,它自己有一个超时判断,如果 Skill 执行时间超过了模型等待上限,模型会提前报错或者生成一段“我还在处理中”的话,这时候 Skill 侧实际上还在跑,就有可能出现结果被丢弃或者重复调用的问题。我建议同时检查两边的超时参数,确保 Skill 执行时间明显小于模型等待时限。
4.3 日志与链路追踪的落地做法
很多做 Agent 的人容易忽略日志,但我觉得在 AI Skills 实践里,日志几乎是除 Schema 之外第二重要的事。原因很简单:模型调用是概率性的,同样的输入,它这次走的路径可能和上次不一样,你如果没有日志,出了问题根本不知道是模型决策错了,还是业务代码错了。
我在每个 Skill 的执行逻辑里都会预留一个日志记录点,记录四件事:入参、出参、耗时、异常信息。入参和出参是定位问题的关键,模型到底填了什么参数,返回了什么结构,这两条日志一对照,问题基本上就能定位。异常信息则是给排查留线索,比如某个下游接口连接超时,日志里能看到具体报错。
腾讯云 AI Skills 控制台自带执行的调用日志,可以按时间、按技能维度去查,这个功能我没少用。调试阶段,我建议把日志级别调到能打印所有关键信息,等到稳定了再过滤噪音,不然日志量太大反而看不出重点。
5. 常见问题速查清单与实测避坑指南
5.1 高频问题对照表
我把这段时间实测中碰到的问题整理成了一个速查表,希望对大家有直接帮助。
| 问题现象 | 根因分析 | 解决思路 |
|---|---|---|
| 模型不触发 Skill,明明问了相关问题 | 能力描述与用户问法语义距离太远 | 复述用户真实提问方式,把常见问法写进描述里 |
| 参数经常传错格式或类型 | 参数类型定义模糊,比如自由字符串 | 尽量用枚举或数值类型,同时在描述中加约束词 |
| Skill 执行成功但回复内容不对 | 返回结果缺少关键业务判断逻辑 | 在描述中显式声明结果后处理规则,比如告警阈值 |
| 同一个 Skill 被连续调用多次 | 缺少失败后的行为约定 | 描述中写明失败重试限制,比如“不要重试超过两次” |
| 返回结构偶尔解析失败 | 字段类型不一致,比如字符串 vs 数值 | 统一返回结构,并在测试阶段逐一校验字段类型 |
这几个问题基本覆盖了我遇到的绝大多数情况,本质上都和“描述”“参数”“返回结构”这三个因素有关,只要你在设计 Skill 时愿意多花点时间把这三件事理清楚,后面的调试成本会低很多。
5.2 三个让我印象最深的坑
第一个坑是类型不一致。我做磁盘查询 Skill 时,代码里用 psutil 拿到的 usage.percent 是一个浮点数,比如 85.2,但我 Schema 里声明成 integer,返回给模型时模型看到“85.2”这种数值会对比预期结构,虽然大多数情况下不影响,但有一次模型在组织回复时把它拼成了“85.2%”,结果下游另一个 Skill 拿这个数字去做阈值判断时直接出错了。后来我在执行代码里对返回数值做了格式化,统一保留一位小数,这个问题才彻底解决。
第二个坑是描述里的“否定语义”。我一开始写了一个“文本相似度计算”的 Skill,描述里强调“不要用于情感分析”,结果模型反而在遇到情感分析任务时频繁调用它。后来我才反应过来,模型对否定词的关注度往往低于对正相关性词的关注度,你在描述里强调“不要做 X”,反而强化了“X”这个概念的匹配权重。正确的做法是正面描述“本技能只做 XX,用户如有 YY 需求请勿使用”,并且把“不要做”的内容弱化处理,比如放在描述的最后。
第三个坑是测试环境的数据和生产不一致。我在测试 Skill 时,用的测试数据和生产环境的数据是隔离的,所以调试时一切都很正常,一上生产就开始出现部分请求结果不对。排查到最后发现,生产数据里有些字段是空值,而我在测试数据里没有覆盖到这个情况。后来我把测试用例补齐了空值、超长字符串、特殊字符等边界场景,再上线就稳定很多。这里给大家的建议是:如果 Skill 要做生产数据验证,务必留出充分的联调时间,不要只在造好的数据上自嗨。
5.3 调试技巧:让模型的“想法”变得可见
最后分享一个我自己屡试不爽的调试技巧:把模型在工具调用过程中的“选择逻辑”通过返回信息暴露出来。
什么意思呢?当一个 Agent 调用多个 Skill 时,你很难判断它为什么选择 A 而不选择 B,尤其是多个 Skill 描述相近的时候。我的做法是在每个 Skill 的实际执行逻辑中,增加一个 DEBUG 模式的旁路输出,把模型按照 Schema 填入的实际参数、命中的技能标识、以及该技能触发的置信度判断信息一起记录下来。
这样你在控制台看调用日志时,就不只是看到一个黑盒结果,而是能看到模型内部的决策轨迹。比如某一次用户说“帮我查一下服务器跑得卡不卡”,模型同时触发了“查询 CPU 使用率”和“查询磁盘空间”两个 Skill,但实际用户关心的其实只是 CPU。你在日志里发现自己的 Skill 描述没有把“卡”和 CPU 做足够的绑定,于是你调整描述,加上“如果用户描述为卡顿,优先使用本技能”的语义,下次再遇到同类问题,触发准确率就会高很多。
这种“决策可见性”的思路,是 Agent 调试里最有价值的部分。它和写普通后端代码完全是两回事,普通后端代码你看报错就行,但 Agent 行为出问题,你得先理解模型“是怎么想的”。SKills 的日志体系天然支持这种调试手段,关键在于你有没有主动把关键信息记录在日志里。
6. 最佳实践清单:这些经验可以直接用
6.1 Skill 命名与描述规范
命名上,我推荐用“动作 + 对象”的结构。比如 fetch_server_status、query_alert_list、create_backup_task,一看名字就知道是干什么的。尽量别用 tool1、api_call 这种无意义标识,也别用太长的句子,模型在匹配时更看重语义标签而不是短语本身。
描述上,我提供一个可以直接套用的模板:
- 第一句:这个技能做什么,一句话说清楚。
- 第二句:什么业务场景下使用,列举典型的用户问法。
- 第三句:边界提示,什么情况下不要调用。
这个模板不是我拍脑袋想的,是踩了不少坑后总结出来的,尤其是边界提示这一句,对防止误触发非常有效。
6.2 安全与权限最小化
Agent 技术越往后发展,安全就越不是可选项,而是必选项。我在这套实践里做的最重要一件事,就是把每个 Skill 的权限控制在最小范围。比如查询磁盘的 Skill,只给只读权限;执行重启的 Skill,只单独授权并且加二次确认;数据库账号用的是只读账号而不是 root。
权限最小化看起来会多几步配置,但它能防止很多意外的连锁反应。试想一下,Agent 因为描述写得不好误触发了某个“删除类”的 Skill,如果这个 Skill 的账号恰好有很高的权限,后果就不是一句“抱歉”能解决的了。建议所有人在设计 Skills 的第一步,就先明确每个 Skill 能做什么和不能做什么,权限粒度宁可细一点。
6.3 持续迭代的节奏
我把 AI Skills 的迭代节奏总结成一句话:“小步快跑,常看日志,及时调整描述”。模型的语义理解能力在快速提升,用户的话术也在千变万化,一个 Skill 上线之后,它的描述和参数 Schema 不应该是一劳永逸的。我会在每次新版本发布后隔几天去看一次调用日志,特别关注那些“用户明确提问但没有触发 Skill”的请求,这些就是下一轮描述优化的素材。
有一点我想单独提醒:在调整 Skills 时,最好不要频繁改动一个 Skill 的参数 Schema,因为模型已经通过上下文学会了旧 Schema 的用法,突然改动会增加它的适应成本。更稳妥的做法是新增一个 Skill 版本,在新的版本上做测试和切换,而不是在老版本上直接改。这和前后端接口的兼容性设计其实是同一个道理。
最后一个经验,我在多个项目里都用过:不要把内容直接硬编码在一个 Skill 里。能参数化的全部参数化,能配置化的全部配置化,这样同一个 Skill 可以被不同 Agent、不同场景复用,你的 Agent 体系才能越滚越大,而不是永远在同一个 Skill 上打补丁。这套方法论放在腾讯云 AI Skills 上能跑得通,放到其他 Agent 框架里同样是成立的。