上周帮我一个朋友调试他搭的智能助手现象特别典型大模型回答得流畅又自信但让他去查订单、生成页面、拉一份日报它就开始一本正经地编数据。朋友很困惑说模型已经够强了为什么Agent还是像个满嘴跑火车的实习生这句话背后就是绝大多数Agent项目从Demo走向实用时撞上的那面墙。模型擅长生成文本但完成任务需要的是可执行的动作Agent框架擅长规划路径但路径再清晰没有能落地的工具最终还是只能靠幻觉硬扛。要让一个Agent真正“全能”核心不是换更大的模型也不是堆更多提示词而是给它配一套高质量的Skills——大白话就是给Agent准备标准化的操作手册与工具包。这篇文章我就结合自己在腾讯云上从零搭建Agent服务的经历聊聊Skills的设计思路、部署方法以及文档里不会写清楚的坑。1. 先对齐认知Agent、Skills、最佳实践分别解决什么问题1.1 Agent的“高智商”和“低执行力”为什么能同时存在大模型的本质是下一个词的概率预测哪怕接了长上下文、做了多轮对话它的强项依然是“生成”不是“执行”。所谓执行意味着要读数据库、发HTTP请求、写文件、调外部系统这些动作在模型的参数里根本不存在。Agent框架做的事情本质上是在大模型外面套一个循环思考、选工具、调用、观察结果、再思考。这个循环里最容易被忽略也最关键的是“选工具”这一步能否选得准、调得对。如果工具只有三五个写死的函数模型还有可能靠上下文硬猜一旦业务复杂起来函数几十上百个模型就会开始乱选或者干脆不选。Skills要解决的正是这个问题——它把所有可执行能力变成一份大模型能读懂的“能力说明书”包括什么时候用、传什么参数、返回什么结构、有什么边界条件。把话说得直白点Skills就是给Agent装上的手和工具Agent能不能干活取决于这套工具装得好不好。1.2 Skills在腾讯云技术栈里的真实定位腾讯云上能拿出来做Agent的零件其实非常多大模型API负责生成云函数SCF负责轻量逻辑容器托管负责跑重任务API网关负责统一入口向量数据库负责记忆与知识检索CLS负责日志。把这些零件串起来的胶水就是“Skills”这套设计模式。Skills并不是腾讯云上一个具名的产品功能而是把云上能力编排成可被Agent调用的标准化服务的方法论。这样说可能有点抽象换个角度理解。Agent落地有三个躲不开的问题第一模型怎么知道你到底有哪些能力第二模型怎么正确调用这些能力第三调用结果怎么可靠地回到对话里。腾讯云的容器、网关、函数计算可以把第二和第三个问题解决得比较痛快但第一个问题也就是能力的描述和暴露必须靠Skills的元信息设计来回答。所谓“最佳实践”其实就是这三个问题的统一解用一套规范的Skills结构把云上的算力、存储、业务代码包装成Agent能理解的接口。2. 写一个能用、好用的Skills关键在设计结构2.1 Skills的标准四层结构元信息、参数Schema、执行逻辑、测试用例我自己在实践里习惯把Skills拆成四层元信息、参数Schema、执行逻辑、测试用例。很多人在第一步就翻车只写了一个处理函数然后告诉Agent“这是xxx技能”结果模型要不是不调用要不就是乱传参数。四层结构看着繁琐但每一层都在解决一个具体的坑。先看一个例子这是一个“前端页面生成”Skill的描述文件name: generate_frontend_page version: 1.0.0 description: | 当用户要求生成一个网页、前端页面或HTML页面时使用。 适合生成落地页、列表页、仪表盘、详情页等单页应用。 如果用户想要修改已有页面不要使用本技能。 parameters: type: object properties: page_type: type: string enum: [landing, list, dashboard, detail] description: 页面类型landing是落地页list是列表页dashboard是数据看板detail是详情页 theme: type: string description: 主题色或整体风格例如“企业蓝”、“科技黑” need_mock: type: boolean description: 是否生成模拟数据默认false required: - page_type execute: runtime: python3.11 handler: skill.handler examples: - input: 帮我生成一个电商商品列表页蓝色主题 output: 返回一个可直接在浏览器打开的HTML文件为什么元信息那么重要因为Agent选技能的时候靠的是description的语义匹配。description写得越准确模型就越不会在错误的场景里调用它。参数Schema则决定了模型能不能把用户的话正确翻译成函数入参。有人觉得写枚举、写required是给模型添堵实测效果恰恰相反约束越明确模型的正确率越高。测试用例也不是只给自动化测试用的更是给模型做“少样本参考”用的例子越贴近真实业务模型理解得越快。2.2 粒度怎么切才不后悔Skills的粒度问题是做得越多感受越深。一个常见错误是“贪大”比如把“生成前端页面”做成一个Skill里面塞了列表页、详情页、后台管理、移动端适配结果是描述怎么都写不清楚模型调用时经常选错分支。另一个极端是“切太碎”比如把“查询订单”拆成“查询订单头”“查询订单明细”“查询订单状态”三个Skill模型面对一堆相似描述时反而选择困难。我个人的经验是一个Skill对应一个可以独立验收的业务动作。什么叫可以独立验收就是它输出的结果用户可以直接判断“对不对”。从命名习惯上推荐用“动词对象”的方式拆比如生成列表页、查询订单状态、转换图片格式、汇总每周报告。粒度标准可以参考一句话原则你能不能把一个Skill的用途、输入、输出用一句话说清楚如果能这个粒度基本合适如果一句话塞不下说明它管得太宽了。原子Skill和流程Skill可以分开设计。原子Skill完成单一动作比如“生成二维码”流程Skill负责编排比如“周报生成”内部依次调用数据查询、模板渲染、文件上传。先原子后流程会让整个体系更容易维护模型也更好理解。2.3 让大模型读懂你的Skills描述与参数设计技巧Skills的description是给语言模型看的关键材料不是给人看的接口注释。很多人写description容易犯一个错把“这个Skill能做什么”写得特别全但没写“什么时候不要用”。模型在工作时是典型的按图索骥描述里没有边界条件它就容易把不合适的请求也硬塞给这个Skill。正确写法是“触发场景正例反例”比如“当用户要求生成一个新网页时使用如果用户要求修改已有页面不要使用本技能”。参数命名也直接影响效果。推荐用完整语义的驼峰或下划线命名user_name就写user_name别为了省事写un。模型的训练语料里更常见的命名方式理解和转化率会更高。所有枚举值都要给全比如page_type有哪几种必须逐一列出来并简单注释。数值型参数要写清单位是秒还是毫秒是KB还是MB不然模型可能真的把分钟当秒传。返回结构尽量统一成JSON模型对结构化数据的解析能力比对自由文本强得多后续做结果校验也方便。3. 腾讯云落地实操从本地Skill到在线服务全流程3.1 先选承载方式云函数、容器托管还是API网关直连Skill写完以后得有一个稳定、可观测、可扩容的在线载体。我在腾讯云上试过三条路线简单说下区别。第一条是云函数SCF适合执行时间短、依赖少的轻量Skill比如查询类、简单生成类、Webhook类。它的优势是免运维、秒级扩容、按调用量计费缺点是冷启动偶尔会带来一两百毫秒延迟且单个函数执行时长的上限有限重任务容易超时。第二条是容器托管或TKE适合依赖大、执行时间长、甚至要带GPU的Skill。比如Skills内部要跑一个本地小模型或者要做PDF解析、视频处理这类耗时操作容器是更稳的选择。第三条是API网关直连已有服务如果你团队里已经有现成的HTTP服务最省事的做法不是重写成云函数而是直接在API网关注册新路由把已有服务包装成标准接口。我的建议是Skill逻辑1分钟内能跑完、没有特殊依赖无脑云函数要跑模型或者干长活选容器托管已经有服务就接网关。下面给一个对比表方便决策维度云函数SCF容器托管API网关自建服务适用Skill类型轻量、低频、同步短任务中重任务、长任务、模型推理对接已有业务系统扩缩容自动按调用量自动/手动按实例依赖后端冷启动存在低低运维成本低中中到高典型场景查询、简单生成、回调推理、爬虫、批处理企业内部系统3.2 实操演示把一个“前端页面生成Skill”部署到云端这里直接上操作。假设我们要部署上面设计的generate_frontend_page这个Skill本地代码用Python的FastAPI写对外暴露一个POST接口。代码不需要多复杂重点是演示整条链路。先写主逻辑文件main.pyfrom fastapi import FastAPI from pydantic import BaseModel app FastAPI() class PageRequest(BaseModel): page_type: str theme: str 默认 need_mock: bool False app.post(/generate) def generate(req: PageRequest): # 真实项目中这里会调用模板引擎或大模型生成HTML html fhtmlbodyh1{req.page_type} - {req.theme}/h1/body/html return {code: 0, data: {type: req.page_type, html: html}}本地先跑通这一步千万别跳pip install fastapi uvicorn pydantic uvicorn main:app --host 0.0.0.0 --port 9000 curl -X POST http://127.0.0.1:9000/generate \ -H Content-Type: application/json \ -d {page_type:list,theme:蓝色,need_mock:true}看到正常返回后再往下走。编写DockerfileFROM python:3.11-slim WORKDIR /app COPY requirements.txt . RUN pip install -r requirements.txt COPY main.py . CMD [uvicorn, main:app, --host, 0.0.0.0, --port, 9000]本地构建并推送镜像到腾讯云容器镜像服务。先给镜像打上仓库地址的Tag再pushdocker build -t skill-frontend:v1 . docker tag skill-frontend:v1 ccr.ccs.tencentcloud.com/你的命名空间/skill-frontend:v1 docker push ccr.ccs.tencentcloud.com/你的命名空间/skill-frontend:v1推送完成后在控制台使用这个镜像创建云托管服务或者在云函数里通过自定义镜像方式创建函数。创建好服务后不要直接暴露容器端口建议统一走API网关把生成的服务地址绑定到网关路由上再在网关侧配置鉴权。我习惯用API Key鉴权Agent调用时在Header里带上X-Api-Key简单够用也不容易误伤。到这一步这个Skill就变成了一个标准的云上HTTP接口。3.3 把Skills接入AgentFunction Calling与工具注册Skill上线后会有一个网关地址接下来要把它注册进Agent。目前主流的Agent框架都支持Function Calling或类似的工具注册机制本质是一样的给模型一份JSON结构的函数说明模型根据用户意图决定要不要调、参数怎么填。我经常在调试阶段手动模拟一下Agent的调用不直接用真实Agent调能省很多事{ type: function, function: { name: generate_frontend_page, description: 生成前端单页面代码当用户需要生成新页面时使用, parameters: { type: object, properties: { page_type: { type: string, enum: [landing, list, dashboard, detail] }, theme: { type: string }, need_mock: { type: boolean } }, required: [page_type] } } }把这个配置塞进Agent的工具列表后完整的调用链是这样的用户提问模型判断需要生成页面按parameters填参通过HTTP请求调用腾讯云API网关网关转发到容器托管的Skill服务服务执行逻辑后返回JSON模型再把JSON翻译成自然语言回复给用户。这里有一个容易被忽略的细节模型调用外部接口是有失败概率的Skill的返回结构里一定要带上足够明确的错误码和错误信息模型才能知道是参数问题、服务问题还是业务失败从而做出下一步动作。4. 部署和上线阶段最容易踩的坑含速查表4.1 我在腾讯云上踩过的四个典型坑第一个坑是description写得像给同事看的接口文档。我早期写过一个“前端代码生成”的Skilldescription里写的是“本技能用于生成前端页面代码支持多种页面类型”结果模型在一个修改页面的请求里也调它生成了一堆用户根本不要的新页面。后来我把反例写进去了“如果用户要求修改已有页面不要使用本技能”误用率立刻下降了。这段经历让我意识到Skills的description是在教模型做判断不是在写需求文档。第二个坑是超时设置太短。第一个部署到云托管的Skill默认超时只有几十秒结果用户让它生成一个包含几十个商品的完整页面时请求直接超时。解决思路是有两个选择如果任务能在1分钟内完成调大超时时间如果必然超过就把任务改成异步模式Skill收到请求后先返回任务IDAgent轮询或等回调拿结果。对Agent来说异步不是负担反而让长任务可以有进度反馈。第三个坑是并发控制没做。Skill一旦被Agent自动调用请求量完全不可控。有一次我把一个查询类Skill开放给多个Agent共用结果测试时几个Agent同时发请求把后端数据库的连接池打满了。后来在网关层加了限流在服务层做了简单的信号量控制才把这个问题按住。第四个坑是日志不完整。模型调用Skill失败时返回给用户的往往是一句“抱歉我没能完成操作”这行信息对排查完全没用。最后只能一遍遍复现。后来我在代码里把request_id、参数摘要、执行耗时、返回码都打成结构化日志统一收到腾讯云CLS里再配合请求ID去关联网关日志和服务日志排查效率高了很多。4.2 常用排查路径与速查表经验积累得差不多后我把常见症状整理成了排查表遇到问题可以直接对着查症状可能原因排查方向Agent完全不调用Skilldescription语义匹配不上或工具名称太泛重写description加触发场景和反例调用时参数错乱JSON Schema约束不够枚举和required没写全补齐参数约束简化参数数量请求经常超时Skill执行逻辑太长或超时配置过小调大超时或改异步任务返回结果乱码响应编码不一致统一UTF-8检查响应头Content-Type鉴权失败API Key没传或者Header名不对检查网关鉴权配置和调用侧Headers同类请求有的成功有的失败参数边界条件没校验在服务端补齐字段校验先慢后快还有一个调优经验值得单拎出来说。如果Agent总是选错Skill优先改description不要先去改模型提示词。Skill的入口描述对工具选择的权重非常高我踩过几次坑之后把description都改成“触发场景反例期望输出”的三段式结构整体准确率明显提升。4.3 补上可观测性日志与调用链很多人部署完Skill以为就完事了其实线上出问题的时候最痛苦的不是服务挂了而是不知道挂在哪一环。Agent调用链往往横跨大模型、Agent框架、API网关、容器服务、数据库任何一环出问题都可能导致用户侧表现异常。所以可观测性不是上线以后补而是在设计阶段就放进Skills惯例里。我的做法是每个Skill统一打印四类字段request_id、skill_name、耗时ms、返回码。request_id一定要贯穿整个调用链否则没法在网关日志、服务日志、Agent日志之间串线索。日志统一打到stdout或CLS不要散落在多个地方。如果Skill内部还要调外部系统建议把外部系统的状态码和错误信息也带上。别嫌日志多线上故障排查时这些字段能救你命。5. 从“能用”到“复用”Skills资产化管理5.1 Skills版本管理与多环境隔离Skill是会快速迭代的。今天改一个参数明天调一段逻辑如果不做版本管理很容易出现线上跑的还是旧逻辑、Agent配置里写的却是新参数的情况。我建议按模块发版每个Skill有独立的version字段不兼容变更必须升级大版本并在description里写清楚。云上发布时开发环境、测试环境、生产环境用不同的网关路由或路径前缀隔离比如/prod/skill/gen_page和/staging/skill/gen_pageAgent配置通过环境变量指定对应环境。回滚要能一键完成。容器托管的好处是镜像版本天然支持回滚发布新版本后如果发现异常直接切回旧镜像即可。云函数则需要保留版本别名不然重新发布就覆盖了。版本管理做在前面出问题时才能从容。5.2 把常用Skills沉淀为团队资产当Skill数量超过10个零散管理就会开始拖后腿。我的做法是维护一个“技能注册中心”本质上就是一个JSON列表包含每个Skill的名称、版本、Endpoint、鉴权方式、当前状态。Agent启动时动态加载这个列表新Skill注册后不用改Agent代码就能被调用。这比把Skill写死在代码里强太多。团队协作时还要给Skill写README至少包含三块内容这个Skill解决什么业务问题、入参和返回示例、部署方式。上线新的Skill之前准备好几条典型的测试用例在真实Agent环境里过一遍再开放给用户。我还会让每个Skill记录一个“调用成功率”指标用一段时间之后回头看看调用率低或者成功率低的Skill要么是描述写得有问题要么是职责边界设计得不合理值得专门复盘。这篇文章写下来我自己又复盘了一遍从零搭Agent的过程。最大的体会是Skills做得好不好直接决定Agent的上限。模型本身的能力大家都差不多真正拉开差距的是谁能把业务能力拆成干净、准确、可复用的技能模块。很多人觉得写Skills就是写一个函数其实它更像是在给一个能力很强的实习生写一套操作手册手册写得越清晰实习生越不容易闯祸。最后再分享一个小技巧。如果你发现Agent表现不如预期先别急着换模型或者调提示词花一个下午把你所有的Skill描述都重写一遍用“触发场景反例输入输出说明”的格式重新组织往往会有立竿见影的效果。这个动作我做过好多次每次都有收获。
