写Agent写了快两年,我最大的体会是:别人口中"啥都能干"的Agent,一落到自己的业务里就原形毕露。让它写个段子没问题,让它按固定流程处理数据、调用内部接口、产出符合规范的报告,它就开始自由发挥,你得在Prompt里把每个步骤重复写十遍。后来我把一套"AI Skills"的玩法搬到腾讯云上,效果完全不同——Agent不再是空有一张会聊天的嘴,而是真能把外部能力和固定流程"穿"在身上,随用随取。
这篇文章就围绕"全能 Agent 养成"这件事,分享我实际跑通的思路、踩过的坑,以及在腾讯云上把Skill落地成可调用服务的具体做法。不管是刚开始做Agent开发,还是已经被工具调用、上下文管理折磨得想摔键盘的人,应该都能从这里找到一点可抄的作业。
1. 先别急着写代码:Agent与AI Skills的关系必须想清楚
1.1 为什么没有技能库的Agent总在"纸上谈兵"
很多人的Agent初版长这样:一个大模型,一大段System Prompt,里面塞满了"当你需要查询订单时,调用get_order接口;当你需要生成报表时,按以下格式输出……"。看起来功能齐全,实际上模型经常会漏掉步骤,或者把工具名记错,甚至在没有必要的时候强行调用工具。
本质问题在于:你把"执行知识"和"对话智能"混在了一起。模型本身擅长的是语义理解、推理和表达,但如果所有操作知识都堆在Prompt里,它既要记住规则,又要判断什么时候适合执行,token消耗大不说,规则稍微一复杂就容易"失忆"。
类比一下:你招了个聪明的新人,却不给他岗位SOP,只让他上班前把公司制度全文背下来。他当然能聊、能推理,但具体办事一定会出错。AI Skills相当于给Agent准备了一套岗位SOP工具卡,Agent按需取用,而不是把整本手册记在脑子里。
1.2 Skill、工具调用、Function Calling到底差在哪
经常有人把Function Calling和Skill混为一谈。从我的实践来看,两者的粒度完全不同。
Function Calling更像是一份"接口清单"。它告诉模型:系统里有这些函数,参数是什么。模型的作用是决定调不调、传什么参数。例如状态机里定义的一个查询天气的函数。它解决的问题是"让模型学会触发动作"。
Skill则是一个完整的"能力包"。它不只有接口定义,还包含使用触发条件、执行步骤、领域知识、模板、校验逻辑,甚至可以再调用更底层的Function Calling。它解决的问题是"让Agent在特定场景下具备完整的执行能力"。
两者的区别可以用下面这个表格概括:
| 维度 | Function Calling | AI Skill |
|---|---|---|
| 粒度 | 单个工具调用 | 一组完整工作流/能力集合 |
| 内容 | 函数名、参数、返回说明 | 触发条件、操作说明、模板、校验规则 |
| 装载方式 | 常驻在请求里 | 按需加载,匹配到技能再执行 |
| 适用问题 | "帮我查一下某个数据" | "按公司规范做完整个季度分析" |
我倾向于把Skill理解为"工作说明书+工具箱",把Function Calling理解为工具箱里的某一把工具。没有Skill封装时,模型看到的是成堆散装工具,不知道怎么组合才合理;有了Skill之后,它能判断当前任务对应哪套组合拳。
1.3 这类技能化方案适合谁,不适合谁
在自己项目里推行技能化之前,建议先对号入座:
如果你的场景是流程相对固定、重复执行率高、领域知识密度大的任务,比如客服工单分类、周报汇总、数据清洗、合同初审、私有知识库问答,那么Skills化是非常适合的。因为这些任务需要遵循一致的规则,且每一步都有可能被审查。
如果你的Agent本质上是一个持续性研究助手,需要长时间自主探索、频繁自我修正、不停试错,那么Skills化只能作为辅助。核心工作仍然要依赖模型推理能力和外部记忆设计,硬把探索过程拆成固定技能反而会限制它。
我的建议是:先把高频、稳定的流程沉淀成Skill,给Agent一个"稳定的底座",再在这个底座上去做自由探索。这样既能让Agent在关键环节上不出错,又保留了它的智能弹性。
2. 动手写一个Skill:从SKILL.md到可执行脚本
2.1 最少需要哪几个文件
一个可交付的Skill,按我现在的习惯,最少包含下面这些文件:
sales-report-skill/ ├── SKILL.md # 技能入口,模型首先读取的说明文件 ├── scripts/ │ ├── generate_report.py # 实际执行数据汇总和报告生成的脚本 │ └── validate_input.py # 校验输入数据的脚本,避免脏数据进入主流程 └── reference/ ├── template.md # 报告输出模板,按需加载,不占主上下文 └── examples.json # 2~3个输入输出示例,帮助模型理解边界SKILL.md是灵魂。它不是写给人看的项目README,而是写给模型看的"使用方法"。文件不宜过长,控制在能被完整读一遍、不把上下文撑爆的范围内。
scripts目录放可执行逻辑。reference目录是辅助材料,模型只有在需要查模板和具体样例时才去加载。
有一点非常重要:模型并不是每次请求都会把reference内容自动读一遍,它通常在SKILL.md的指示下按需打开。所以文件组织越清晰,模型越容易在合适时刻找到合适内容。
2.2 描述文件这样写,模型才愿意"按规矩办事"
很多人在SKILL.md里写"本技能用于生成销售报表,你可以使用这个技能……",这种写法太弱了。模型看了不会有强触发意愿。有效写法应当明确触发条件、执行步骤、输出规范和禁止事项。
我最初一版SKILL.md开头是这样的:
# 销售周报生成技能 ## 用途 生成销售周报。 ## 步骤 1. 获取销售数据。 2. 统计各项指标。 3. 生成报告。结果模型动不动就用这个技能,或者压根不用。问题在于没有说清楚"什么时候必须用"和"怎么判断输入数据齐不齐"。
调整之后:
# 销售周报生成技能 ## 何时使用 仅当用户要求生成周报/月报,并且提供了可访问的数据源或数据文件路径时,使用本技能。 如果用户只是闲聊销售话题,不要调用本技能。 ## 输入要求 - 必须存在 source_path 字段,指向待分析数据文件。 - 必须存在 report_period 字段,值为上周/上月等时间段描述。 - 如果缺少必要字段,先向用户索要,不要自行猜测。 ## 输出规范 严格按照 reference/template.md 的章节顺序生成,禁止新增无关分析模块。 ## 禁止事项 - 不要在数据不足时编造指标。 - 不要修改原始数据文件。改动后最大的区别是:模型知道边界了。它不再把这个技能当成"万金油",也知道输入条件不满足时应该反问而不是硬跑。
2.3 拆技能的正确姿势:把大而全改成小而精
我踩过最大的一次坑,是把数据处理流程做成了一个超大Skill。它的SKILL.md有两千多个字,步骤多达二十步,从数据清理一路写到图表生成。结果模型每次执行都会漏掉中间的某一步,而且很难排查到底是哪一步出了问题。
后来我把这个"巨无霸"拆成了三个子技能:
- >import json from skill_lib import generate_report def main_handler(event, context): body = json.loads(event.get("body", "{}")) source_path = body.get("source_path") report_period = body.get("report_period") if not source_path or not report_period: return { "statusCode": 400, "body": json.dumps({"error": "missing required params"}) } result = generate_report(source_path, report_period) return { "statusCode": 200, "body": json.dumps(result, ensure_ascii=False) }
第二步:在腾讯云控制台创建云函数,选择Python运行环境,把代码上传。这一步对应的正是很多朋友提到的"腾讯云上传"操作,不复杂,但要注意上传时不要把本地依赖一股脑全打进去。建议在函数配置里引用层或使用依赖管理,只保留核心代码,不然上传包体积会大得离谱,冷启动时间也会变长。
第三步:配置API网关触发器。创建触发器时会生成一个HTTPS访问地址,把这个地址作为Skill服务的Endpoint。Agent调用Skill时,本质上就是在调这个HTTP接口。
第四步:如果需要更友好的回调地址,可以申请一个二级域名并做解析。比如把skill-api.mydomain.com这个子域名通过CNAME记录指向API网关默认域名。这里有个容易踩的坑:很多人想申请二级域名,却在DNS解析里配了A记录指向云函数的旧IP,结果怎么都调不通。对这种托管服务,优先用CNAME记录指到平台提供的域名,而不是自己去猜IP。
如果你希望模型能通过LiteLLM Proxy这类统一代理来调度模型与技能,也可以把云函数接口登记到代理工具列表里。LiteLLM Proxy的优势是它对上层提供一个兼容格式的接口,底层模型供应商切换时,Skills本身不用改。
3.3 域名、端口与安全边界:别把公网全敞开
围绕"腾讯云如何开放所有端口"这类需求,我多说一句:永远不要给服务器开放所有端口。正常业务只应该暴露必要端口,其余的全部拒绝。
我自己的安全基线如下:
访问来源 开放端口 用途 API网关公网入口 443 外部Agent调用Skill服务 办公网/管理网 22(仅指定IP) SSH维护 内网服务间 按需最小开放 数据库、缓存等 如果ECS上的服务需要被Agent回调,我建议在安全组里添加入站规则时,"来源"写特定IP或IP段,不要写0.0.0.0/0。如果图省事放开了所有端口,用不了几天你就能在日志里看到各种扫描和爆破尝试,这是必然结果。
二级域名申请本身不难,在DNS服务商处添加一条解析记录即可。但要想清楚为什么需要域名:一是为HTTPS证书,二是因为某些Agent回调场景要求Endpoint是固定域名而不是随机生成的临时地址。域名规划建议统一用skills.你的域名.com作为前缀,后面再接具体技能名,比如skills.yourdomain.com/sales-report,这样Agent多了也好管理。
3.4 密钥管理和多环境隔离
把Skill放到云端之后,代码里绝不能出现API Key和数据库密码。我见过有人把腾讯云密钥直接写进云函数环境变量之外,还在脚本里硬编码了一遍,看到后我整个人都不好了。
正确做法是使用平台提供的密钥管理能力,把密钥写入环境变量或密钥管理系统,运行时读取。同时给每个环境单独配置一套密钥:dev环境用一个只读权限的子账号密钥,prod环境用另一个有独立审计轨迹的密钥。这样即使开发环境泄露,也不会把生产数据一起搭进去。
多环境隔离也很重要。我会在云函数名称里显式区分,例如sales-report-dev和sales-report-prod。只在本地联调时访问dev环境,线上Agent统一指向prod环境。Skill的版本更新先发到dev,跑通后再切流量到prod,避免把坏逻辑直接暴露给生产用户。
4. Agent编排中的上下文、重试与评测
4.1 技能输出不要一股脑丢进对话
Skill跑出来的结果往往很长,尤其当它生成完整报告时。早期的实现是:脚本返回一大段文本,Agent原封不动地把它拼到对话里,再让大模型基于这些文本继续回答。后果是用不了几轮,token窗口就满了,而且大模型还要在超长上下文里重新提取关键信息,回答质量反而下降。
后来我改成"结构化中间产物+摘要回填"的方案。Skill脚本先把结果写成一个JSON结构,原始完整报告存到临时存储或对象存储,Agent只把关键指标和结论摘要放进对话上下文。大模型不需要读全部内容也能回答用户问题;用户要完整报告时,再给一个下载链接。
算过一笔实际账:在一次销售数据分析任务里,完整报告文本接近8000字,如果全部放入上下文,按主流模型的计费方式,单轮成本很高。改成摘要回填之后,对话内只保留约1500字的结构化关键信息,成本下降非常明显,同时回答的准确率反而提升了,因为模型不再被无关细节干扰。
4.2 失败处理与人类介入的兜底策略
Skill和普通代码不一样,它由模型触发,触发时机和参数质量天生有不稳定性。所以失败处理不能只靠代码层的try-except,还要在Agent编排层做设计。
我在SKILL.md里通常会写清楚失败时的应对方式:
- 如果输入缺字段,技能应返回明确的参数错误码,Agent收到后应立即向用户追问,而不是重试。
- 如果是外部服务超时,脚本最多自动重试两次,两次后返回timeout错误,Agent如实告知用户目前系统繁忙。
- 当某个Skill连续三次执行都失败,Agent应停止自动处理,转人工队列,并把前三次的执行日志一起附上。
很多项目只关注"Skill起来了没有",忽略了"Skill起不来时Agent怎么表现"。一个人工兜底机制能挽救很多糟糕体验。线上Agent不是越自动化越好,而是该停时能停下来,才敢放开让它跑。
4.3 用回归评测集盯住Agent的成长
"AI Agent 2026发展趋势"这类话题经常讨论Agent能否越来越强,但对我来说,更实在的问题是:我怎么知道这次改造确实让它变强了?答案是用评测集。
我会维护一个约20条任务的回归集,覆盖典型的技能触发场景、参数缺失场景、边界模糊场景。每次改动SKILL.md或调整云函数逻辑后,都拿这个集合跑一遍。每一条任务都记录四个指标:
指标 说明 完成率 是否给出最终可用结果 准确性 结果与预期答案的吻合度 步骤遗漏率 是否漏掉某必要环节 平均耗时 从触发到结果返回的总时长 没有评测集的Agent改造,基本等于凭感觉做优化。你以为换了Skill描述效果会变好,实际可能只是某个测试用例碰巧通过。回归集虽然只有二十条,却能在任何一次调整后迅速暴露"原来能做的现在做不了"的退化问题。这个习惯帮我少走了很多弯路。
5. 常见问题排查实录与避坑指南
5.1 模型死活不调用Skill
优先级最高的问题往往不是代码Bug,而是模型压根不触发技能。排查时我按这个顺序查:
第一,SKILL.md的"何时使用"是不是写得太宽泛?例如只说"用于生成报告",模型判断不了当前用户请求是否属于报告需求。改成类似"当用户提到周报、月报、季报,并且给出数据源或文件路径"这种明确条件后,触发率会大幅上升。
第二,是不是同时存在另一个名称相近的Skill或Function Calling?如果两个技能在描述上高度相似,模型会随机选择一个。我一度同时定义了create_report和generate_sales_report两个技能,模型经常选错。后来把旧技能下线,只保留一个,问题立刻消失。
第三,示例给得够不够。examples.json里的输入输出样例,本质上是在教模型"这个场景长这样,你应该在遇到它时使用技能"。我通常为每个Skill放三组典型场景样例,一组普通输入,一组边界输入,一组不该触发本技能的负例。
5.2 Skill执行了,但结果一塌糊涂
比"不调用"更让人头疼的是"乱调用"。模型确实执行了技能,但产出完全不可用。
常见原因之一是输入数据字段名不匹配。你在脚本里期待source_path字段,但Agent从上一步拿到的是data_source,于是脚本取不到值,只能瞎跑。解决办法是在SKILL.md的输入要求里写清楚字段的别名,并在代码里做兼容映射。
另一个原因是输出规范不够严格。脚本返回的JSON字段含义不明确时,模型会自行发挥,生成一堆不在预期里的内容。规范做法是在SKILL.md里把输出JSON的每个字段都注释一遍,并给出一份示例输出文件。模型有样可依时,执行结果通常稳定得多。
5.3 项目越写越乱:Skill边界不清
Skill边界问题很像代码里的模块划分问题。一个Skill该管多少事?我的原则是:能在一个执行单元里完成并验证的任务,应该是一个Skill;需要多阶段流转的任务,即使最终目标是一个,也拆成多个子技能。
比如"从原始Excel到可视化仪表盘"这个完整流程,如果做成一个技能,模型不仅要做数据清洗、指标计算,还要懂前端图表配置,任何一步出错都不好排查。拆成data-clean、metric-compute、dashboard-generate三个技能之后,每个技能的输入输出都清晰,Agent能逐步推进,出现问题时也能准确告诉你卡在哪。
另外一个很容易犯的错是让Skill承担Agent的记忆职责。Skill不是记忆库,它不应该帮你记录用户偏好或历史状态。需要长期记录的数据应放到独立的记忆服务里,不要让技能脚本偷偷写全局变量。
5.4 线上检查清单:这样查能少走弯路
上线新的Skill前,我会跑一遍自己的检查清单,这里分享出来供参考:
- 检查云函数入口的入参兼容性:是否兼容缺失字段和额外字段,有没有做参数校验。
- 确认API网关超时配置:如果Skill处理逻辑超过30秒,网关默认超时时间是否够。不够的话调整网关或考虑把长任务改成异步回调模式。
- 核对安全组和网络ACL:端口只开放必要部分,来源IP按最小范围进行限制。
- 测试模型触发表现:用评测集实际跑三轮,确认模型稳定触发、稳定输出。
- 检查密钥环境变量:生产环境的密钥是否被硬编码,权限是否为最小化。
- 查看日志打点:关键步骤是否打印了结构化日志,能否在出问题时快速定位到具体执行环节。
这些事都不复杂,但每一项漏掉,后续都可能变成线上事故。尤其是API网关超时和密钥泄露,属于极其常见但又容易被忽略的问题。
6. 一些写在后面的个人经验
最后再分享一个我最近坚持的习惯:给Skill里的每个脚本都加版本号,并在SKILL.md中明确标注它依赖的最低版本。这是因为Agent的Skills是会"漂移"的——脚本更新了,SKILL.md里的使用说明没同步,模型照着旧说明去调新脚本,就会莫名其妙地失败。每次上线新版本,我会在脚本头部和技能说明中同时打上版本标记,并在云函数注释里写明变更原因。这样当Agent行为出现异常时,我可以立刻对比"这个版本为什么和上次不一样"。
做Agent和做传统后端有一个很大的不同:传统后端只要接口符合契约就能上线,Agent的行为却依赖模型对技能描述的理解,充满不确定性。因此,AI Skills最佳实践的核心并不是把Skill做得多炫,而是用更结构化的方式,把这种不确定性一点点收敛住,让Agent在关键流程上从"偶尔发挥"变成"稳定发挥"。希望这些经验能给你一些启发。