资讯动态

从“会聊”到“会干活”:AI Skills让Agent稳定执行真实任务

发布时间:2026/9/4 21:46:27 来源:尧图企业网站定制
最近三个月我把手头一个只会陪用户聊天的 Agent 迭代成了真正能干活的全能体能自己翻仓库、改代码、跑测试、发结果。推倒重来最多次的不是模型选型而是 AI Skills 的写法与编排方式。腾讯云这套环境里把技能挂给 Agent、用 Serverless 做执行体、再用日志定位每一次中断整个过程走下来我最大的体会是——多数 Agent 项目卡住不是因为大模型不够聪明而是它没有一组能稳定复用的技能可以调用。这篇文章不是官方文档的复述而是我基于腾讯云 AI Skills 的完整实践复盘怎么理解 Agent 和技能的关系、技能怎么写才不会被模型乱调用、Serverless 怎么部署、执行超时和中断怎么排查、最后再聊聊记忆与编排。适合正在入门 Agent 开发、或者已经被 Function Call 坑得想砸电脑的工程师。1. 为什么你的 Agent 经常“只会说不会做”先把 Skills 放进正确的架构位置1.1 一次真实的对比测试我在项目初期犯过一个非常典型的错误把“你可以读取文件”“你可以执行命令”这种话全部塞进 System Prompt然后指望模型自己学会调用。结果很残酷——用户让它“把报错信息里的关键字找出来”它确实会热情地回复“好的我来搜索”然后自己脑补了一段不存在的文件路径甚至把报错原因也一起编出来了。后来我把所有能力全部重构成结构化 AI Skills同一个模型、同一个任务成功率从不到四成直接拉到九成以上。这不是玄学。模型的嘴是生成出来的信口开河是它的本能但 Agent 的手必须是约束出来的Skills 就是那套约束机制。模型只能在“你给它定义好的动作集合”里做选择就像导演手里只有固定的几个工种可以调度它没法让一个不存在的工作人员突然上场。1.2 Agent 和 Skill 不是一回事别再混着聊很多刚开始接触 Agent 开发的人会把 Agent 框架、技能、工具、函数调用这几个词混在一起。我先说清楚我理解的分工。Agent 是决策单元它负责理解目标、拆解计划、决定“现在应该调用哪一个能力”并根据返回结果判断下一步动作。Skill 是能力单元它封装了一个可执行的动作对外暴露清晰的输入输出契约什么场景适合用、需要传哪些参数、执行完返回什么结构。Agent 管“想”Skill 管“做”。如果把 Skill 裸写成 Prompt 里的几句话等于让模型自己在语言层面即兴发挥。今天它可能理解成“打开文件”明天可能理解成“把内容打印出来”后天甚至可能理解成“你帮我编一个文件内容”。但当你把动作包装成带有参数 Schema 的 Skill模型每次调用都必须走结构化请求请求到执行体之后是确定性的代码在运行没有即兴空间。这也是为什么我觉得“Agent 的稳定性是靠约束出来的不是靠聪明出来的”。1.3 各家框架都在做同一件事别被叫法唬住OpenAI 的 Function Calling、Claude 的 Tools、Codex 里的 Skill 机制、CodeBuddy Agent SDK、Microsoft Agent Framework包括一些开源项目里叫 Plugin、Action、Tool 的东西名字五花八门但核心思路都一样准备一份机器可读的能力清单让模型在恰当的时机发起一次结构化调用再让外部执行体把真实结果回填给它。腾讯云 AI Skills 的实践本质上也是这个逻辑——把技能描述文件当作模型能理解的“能力菜单”把云函数当作真正干活的“执行工人”。所以你在任意一个框架里学会了设计好一个 Skill换到另一个框架只是换了一层协议壳子底层那套“描述要清晰、参数要严格、返回要结构化”的规律完全通用。1.4 新手入门 Agent 开发的最小知识集给还在纠结从哪开始的朋友一个精简版路线先跑通一个最基础的 HTTP 函数知道请求怎么进来、结果怎么返回把一个函数包装成标准的工具描述挂到 Agent 框架里观察模型会不会在正确时机调用故意设计几个“模型容易误调用”的场景看它会传错什么参数回头改描述再研究执行超时、错误码、记忆缓存这类工程问题。这个顺序能让你少走一半弯路。不要一上来就研究多 Agent 协作、复杂记忆系统——这些都是在单个技能还没稳定之前不该碰的东西。2. 把技能部署到腾讯云SCF 做执行体的接入模板与避坑记录2.1 为什么我用云函数 SCF 而不是一台常驻 CVM做技能执行体的第一反应通常是租一台服务器把所有 Skill 服务都跑在上面。但我实际对比之后选了腾讯云云函数 SCF原因有三。第一技能天然是短任务。绝大多数 Skill 调用都是“收到请求、执行一个动作、返回结果”用函数计算按调用次数计费空闲时不产生费用。第二弹性伸缩不需要我操心技能调用量突然翻倍时函数自动扩容。第三SCF 可以挂在 API 网关后面对外就是一个标准的 HTTP 接口任何 Agent 框架都能对接。SCF 的最大短板是冷启动。第一次请求有时要多等几百毫秒甚至一秒多对在线聊天场景确实有点影响。我的解法是给核心技能开启预置并发让高频技能保持常热低频技能则接受冷启动延迟。下表是我自己选型时的一个简单对比选型方案适用场景成本结构维护成本冷启动问题SCF 云函数短任务型 Skill、调用量波动大按调用次数和资源使用计费低不用管服务器有可用预置并发缓解常驻 CVM长连接、大内存任务、需要固定 IP包月/按量空闲也计费高要打补丁、盯监控无云托管/容器微服务化、需要自定义镜像按实例运行时长计费中较低但仍有拉起时间2.2 技能描述文件与执行函数怎么配合我给自己定的规范是每个 Skill 由一个 YAML 或 JSON 描述文件加上一个云函数组成。描述文件给 Agent 看云函数替 Agent 干活。下面是一个精简的示例version: 1.0 name: search_code description: 在指定代码仓库根目录下递归搜索文件名或文件内容。 适用于定位报错关键字、查找函数定义、确认某个字符串出现在哪些文件中。 当用户提到“找一下 xx 在哪里定义”“搜索日志里的报错字段”时优先使用本技能。 endpoint: method: POST path: /v1/skills/search_code parameters: - name: keyword type: string required: true description: 要搜索的关键字可以是标识符、报错片段或普通字符串 example: DataSourceException - name: search_type type: string required: false enum: [filename, content] default: content description: 按文件名搜索还是按文件内容搜索 output: format: json schema: code: int data: matches: array message: string配套的云函数实现里我不需要把内部逻辑告诉模型只需要严格按照协议处理请求并且把结果统一成固定结构。返回格式我全部采用下面这种{ code: 0, data: { matches: [ { file: src/dao/UserDao.java, line: 87, content: throw new DataSourceException(...) } ] }, message: success }在函数内部也可以加一个极轻量的 JSON Schema 校验入参不合规时直接返回带业务错误码的结果而不是抛 HTTP 500。原因后面讲中断时会展开业务失败和 HTTP 错误在 Agent 运行时的待遇完全不同。2.3 部署时的几个细节上传、依赖、鉴权腾讯云上部署函数最常见的坑是代码包里塞了太多依赖导致上传超时或包体积超限。我后来把第三方依赖单独打包成 Layer函数代码本体只保留业务逻辑这样每次更新代码都很快。依赖超过几十兆时也不要硬传改用对象存储中转或直接用官方 CLI 指定代码包地址。鉴权方面我的做法是在函数环境变量里配置一个自定义 TokenAPI 网关触发时通过 Header 传入函数入口处做校验。示例逻辑很简单TOKEN os.environ.get(SKILL_TOKEN, ) def main(event, context): headers event.get(headers, {}) if headers.get(X-Skill-Token) ! TOKEN: return {code: 403, data: None, message: forbidden} ...有一点必须提醒不要把腾讯云的永久密钥明文写在函数代码里。用环境变量承载、配置最小权限的子账号密钥是基本操作。热词里经常看到有人搜“腾讯云如何开放所有端口”我强烈不建议这么做。安全组和网关的作用是收敛暴露面而不是为了让调试方便把所有口子都打开。对外只暴露 API 网关的 URL函数本身不直接暴露公网入口才是正确的姿势。2.4 本地联调的关键一步别等部署了才发现问题我最开始是写完函数直接部署然后在 Agent 里调用出了问题只能一层层翻日志非常被动。后来改成先在本地把云函数跑起来做一次完整验证。SCF 的本地调试方式有好几种我用的是起一个本地 HTTP 服务模拟网关触发事件把云函数的入口函数包一层。这样一个 Skill 在本地验证接口逻辑没问题之后再部署到云端再挂到 Agent 上测试调用链路。本地能过滤掉大约 70% 的低级错误剩下的才是真正的云端环境问题。3. 怎么写一组能被模型正确调用的 AI Skills描述、Schema 与防幻觉设计3.1 一个 Skill 的四个组成部分写 AI Skills 不是写代码那么简单你要同时面向两个读者一个是模型它靠描述决定“什么时候调用你”另一个是执行函数它靠参数决定“具体干什么”。一个完整的 Skill 由四部分组成能力声明用自然语言写清楚这个技能在什么场景下用、怎么用、边界在哪里触发请求Method 和 Path模型/运行时据此发起调用参数 Schema每个参数的类型、是否必填、枚举范围、示例值执行逻辑与输出格式函数内部做什么、成功和失败各自返回什么结构。很多 Skill 写得不好问题都出在第一部分。模型根本不能“理解”你的代码它只能通过描述来判断是否触发。描述写得太宽泛它会在不该调用的时候调用描述写得太窄它又会在需要的时候完全想不起来。3.2 描述怎么写才能让模型在正确时机调用举个例子。假设我要做一个代码搜索技能。差的描述大概长这样description: search files in repository这种描述几乎等于没写。模型不知道这个技能需要在什么场景用也不知道传什么参数更不知道返回什么。实测下来这种技能经常被模型忽略或者被随机触发。我优化的描述会长得多也更啰嗦description: 在指定代码仓库根目录下按文件名或文件内容进行递归搜索。 当用户试图定位某个报错关键字、查询某个函数在哪里定义、确认某个字符串出现在哪些文件 或希望理解项目中某段逻辑的引用关系时优先调用本技能。 搜索范围限定在仓库内不支持远程地址也不支持搜索二进制文件。 返回结果是完整的命中列表包含文件路径、行号和命中内容。改完之后模型误用率明显下降。为什么因为模型做工具选择时本质在做一个语义匹配把用户需求和你给的描述做相似度比较。描述越贴近真实使用场景匹配越准确。你甚至可以在 description 里直接写“当用户提到×××说法时优先使用本技能”相当于主动给模型喂提示。3.3 Schema 参数设计防模型编造参数的几种手段模型在调用技能时最大的风险不是不调用而是“编参数”。它可能会给你一个看起来合理但完全虚构的文件路径也可能把一个枚举值写成自由文本。我在设计 Schema 时用了三种手段来对抗。第一是能枚举就枚举。比如搜索类型不要写成任意字符串而是明确限定filename或content。第二是必填参数绝不带默认值避免模型偷懒不传。第三是在执行函数里加二次校验不要相信模型传进来的任何数据。看一段极简的校验逻辑def validate_params(params): if not params.get(keyword) or len(params[keyword]) 2: raise InvalidParam(keyword is required and must be at least 2 chars) if params.get(search_type) not in (filename, content): raise InvalidParam(search_type must be filename or content)还有一条更重要的原则凡是需要真实数据的场景宁可让模型先调用工具拿真实结果也不要让它凭记忆生成内容。比如用户问“这个项目里有没有地方引用了旧接口”模型可能会根据自己的训练记忆胡编一个结论。正确做法是让模型先调用搜索技能去仓库里找真实命中再基于命中结果回答。这个防幻觉思路贯穿了我后续所有技能设计。3.4 编程类 Agent 最常用的一组 Skill 长什么样回应一下很多人搜的“编程好用的 AI Skills”。我自己做代码助手类 Agent 时沉淀了三个高频技能几乎每个任务都会用到。第一个是read_repo负责把仓库结构和指定文件内容返回给模型。关键设计点在于加行号返回否则模型在回答“第几行出错了”时会没有依据。文件太大时不能整份返回要做分段读取否则上下文很容易被撑爆。第二个是run_command负责在沙箱里执行命令并返回输出。这几乎是编程 Agent 的刚需但也是最容易出事的技能。我给它设计的参数包括工作目录、超时时间、是否需要终端交互返回里必须带退出码、标准输出和标准错误。输出要截断默认最多返回最近 100 行超出部分单独存到文件等模型需要时再调用另一个技能去按行读取。第三个是search_symbol负责定位函数、类、变量的定义与引用位置。单靠文本 grep 不够准做了语义与文本混合的检索对模型来说这个技能的描述要特别强调“当用户问某个函数在哪里定义、哪里引用时使用”。三个技能的设计要点对比如下表技能名核心用途必备参数返回关键点最容易踩的坑read_repo让模型理解代码结构和文件内容路径、起始行、行数带行号内容、总行数大文件整体返回导致上下文爆炸run_command让模型能够执行命令、跑测试命令、工作目录、超时时间退出码、stdout、stderr命令执行时间过长、输出过长search_symbol定位定义和引用位置关键字、搜索范围文件路径、行号、上下文语义检索不准时会返回噪音结果3.5 什么需求不该做成 SkillSkill 不是越多越好。有一种情况我见过很多次有人把“让模型写 Python 代码”做成了一个 Skill调用外部代码生成服务。但模型本身生成代码已经足够强了这种外部包装不仅多余还可能因为接口返回格式问题打断模型原本流畅的推理。我的判断标准只有一条如果一个动作只需要模型的语言能力不涉及外部状态、权限、副作用或校验就不需要做成 Skill直接在 Prompt 里告诉它怎么做就行。反过来凡是需要读真实文件、执行真实命令、调用外部系统、修改持久状态的事情都应该封装成 Skill。这个边界想清楚你的技能数量会少一半但每个都更可靠。4. 从执行中断到稳定运行超时、错误日志与三个事故现场4.1 一条 Skill 调用的完整时序链很多人在 Agent 报错之后不知道从哪里查起是因为脑子里没有调用链路的全景。我每次排查都先画一条这样的逻辑链虽然没有办法用图表达但顺序是死的用户消息 → 模型做出工具调用决策 → Agent 运行时把请求转发给执行体SCF / API 网关→ 执行体跑完返回结构化结果 → 运行时把结果回填给模型 → 模型继续推理或发起下一次调用。基于这条链我把所有故障分成四类模型侧决策错误、运行时转发/等待超时、执行体内部崩溃、结果回填阶段格式不合法或上下文过大。很多“execution terminated due to error”表面上看是执行体挂了实际上是在回填阶段出了格式问题。不要一看到 error 就去翻函数代码先判断它发生在哪一段。4.2 现场一provider did not respond in time其实是超时夹层效应第一次在日志里看到the agent execution provider did not respond in time这个报错我第一反应是云函数出了问题赶紧去翻函数日志结果函数执行成功了耗时 28 秒。问题出在哪是模型端的等待窗口只有 20 秒。函数还在跑模型已经等不及主动断开了。这里有一个典型的“夹层效应”SCF 侧超时你设置得再大模型端也不会无限等。模型、Agent 运行时、HTTP 客户端、函数执行体四方都有自己的超时配置任何一个环节设得比上游小都会导致上游先放弃。我上线后调整的配置参考如下层级配置项参考值说明Agent 模型调用等待run 超时120 秒要给工具调用留出足够空间HTTP 请求超时requests 超时45 秒大于单个技能常规耗时SCF 函数超时执行超时30-60 秒原则上单个技能不该跑这么久重试策略最大重试次数1 次幂等技能才允许自动重试调整后这类超时中断基本消失。有一点很关键不是把所有超时都调大就安全。SCF 超时如果配成 900 秒某个技能卡住时它会占着资源跑很久模型端却早已放弃结果依然是失败。设计技能时应该把单个动作尽量控制在 15 秒内超过 30 秒的基本要重新审视是不是动作拆得太大。4.3 现场二函数跑完但模型拿到半截 JSON是上下文被撑爆了另一个高频事故是SCF 正常返回日志里也看不到错误但 Agent 在下一步突然报解析失败。后来定位到原因——run_command跑测试时输出了 3000 行内容我把整段 stdout 都回传给模型。模型端上下文长度有上限超长文本把结果从中间截断了Agent 收到的是残缺 JSON解析自然失败。这是所有技能设计里最容易忽视的坑你返回的每个 token 都会占据模型的上下文窗口。解决方式是严格控制回传大小。日志类输出截断保留头尾结构化结果单独抽取出来完整日志存到对象存储等模型确实需要细节时再调用专门的日志读取技能去拿。简单说工具返回给模型的内容应该是“经过提炼的证据”而不是“原始日志的搬运工”。另外函数返回的 JSON 本身也可能携带脏数据导致解析失败。控制字符、NaN、Infinity 这些值在某些 JSON 解析器里会直接报错。我后期统一用json.dumps(data, ensure_asciiFalse, allow_nanFalse)做序列化并把异常情况转换成业务错误码。4.4 现场三函数明明成功了Agent 却当成失败处理还有一种更隐蔽的情况函数执行成功数据也确实返回了但 Agent 不认。查到最后是返回结构问题。SCF 通过 API 网关触发时返回结构有时候会包裹一层网关字段模型拿到的不是一个干净的 JSON而是一个带着 statusCode、headers、body 的外壳如果 Agent 框架在转发前没有帮你剥掉这层模型就会看到一堆无关字段判断失败。我的规避方案是业务代码里统一返回标准结构终端用户在网关层做一次响应体映射保证到 Agent 手里的只有{code: 0, data: ..., message: ...}。还有一条坑了我很久的经验业务失败不要用 HTTP 4xx 状态码返回。像“未找到文件”“参数不合法”这类业务错误我用 HTTP 200 加上业务 code 非 0 来标识。因为很多 Agent 运行时会自动把 4xx/5xx 当作执行异常触发重试甚至直接终止而用 200 业务 code模型能正常读取 message 内容自行决定下一步动作。4.5 排查中断问题的工具箱给正在排查同类问题的朋友一套可直接用的思路打开云函数日志和网关访问日志按 request_id 做关联在 Agent 调用里透传 trace_id 到技能执行端能快速定位一次规划对应了哪几次工具调用技能执行前后打点记录“开始时间、结束时间、返回内容长度、是否截断”对高频故障做回放保存历史请求在修改技能描述或函数代码后重跑一遍对比成功率变化。日志的可观测性对 Agent 项目比对传统 Web 项目更重要。普通接口的输入输出是确定的但 Agent 的调用序列是非线性的一次用户请求背后可能连着七八次技能调用没有链路追踪根本没法复盘“它为什么会在第三步突然乱掉”。5. 从十几个 Skill 到全能 Agent编排、记忆与上线后的养成节奏5.1 技能分层感知、决策、动作不要混在一个池子里当技能数量超过十个之后如果你的 Agent 只有一层工具列表模型每次做选择时都会被大量无关技能干扰。我开始尝试分层编排之后误用率又降了一截。我把技能分成三类感知类技能负责从外部获取信息比如读仓库、搜日志、查数据库分析决策类技能负责基于感知结果做判断比如调用模型做代码审查、风险评分动作类技能负责产生副作用比如改文件、发请求、创建工单。系统主循环是感知 → 决策 → 动作 → 验证。分层的好处不只是逻辑清晰还能在权限上做隔离。动作类技能需要更严格的授权和审批感知类技能可以更开放。模型不会在一个只读任务里被“执行命令”这种高权限技能带偏。5.2 短期记忆、长期记忆和“技能状态”的正确分工记忆是很多 Agent 从“能用”走向“好用”的关键。我把记忆拆成三层来设计。短期记忆由 Agent 运行时自动管理本质是当前任务里的上下文。但我不会把所有内容都塞进上下文关键的中间结果让 Agent 写入临时工作区文件需要时再按需读取避免上下文被中间步骤撑爆。长期记忆用于跨任务沉淀背景信息。我的做法是做一个save_memory技能让模型在任务结束后把值得记录的信息写入对象存储标题、标签、内容正文一起保存。查询时先做关键词检索再结合向量检索召回相关内容。前期没有向量库时纯关键词检索也能撑住测试阶段别为了记忆功能先搭一套重架构。还有一层容易被忽略的是技能状态。Skill 本身应该尽量设计成无状态的。如果某个技能确实需要记录上次执行的位置不要放在进程内变量里因为 Serverless 实例随时可能被回收。正确做法是写入工作区文件下次执行时再读出来。5.3 从单 Agent 到多 Agent什么时候真的需要拆很多人看到一个复杂任务第一反应是设计三个 Agent 分工协作。我自己的结论是先把单 Agent 的技能树做厚再考虑拆分。确实需要拆的场景通常只有三种并发受限、权限隔离、不同 Agent 用不同模型。拆分之后每个 Agent 依然需要自己的技能集但多 Agent 之间要协调状态、传递上下文引入的故障面是指数级增长的。我的经验是一个能稳定调度 20 个技能的单一 Agent远比三个互相传递消息但经常把上下文搞丢的 Agent 可靠。多 Agent 是最后的手段不是一开始的架构。5.4 一个可复制的“养成节奏”四步法最后分享一下我现在给新项目定下的迭代节奏。第一步每次只新增一个技能。把一个场景彻底跑通、连续调用 20 次不出错再开始做下一个不要贪多。第二步重点调描述。新技能上线前三天模型大概率会出现误调用或者该调不调的情况每一次误用都是改描述的机会。第三步建立回归用例集。把已经验证的核心技能调用场景保存下来每次改完代码或描述后跑一遍确保没有改坏旧功能。第四步用真实任务数据做持续观测。我会用三个指标来判断某个技能是否进入了稳定期调用成功率、无效调用次数、结果对最终回答的贡献度。如果一个技能经常被调用但模型用完之后还是给出错误结论那说明返回给模型的内容质量不达标需要重新提炼结果结构。按这套节奏走差不多三到四周可以让一个 Agent 真正变成能干活的“全能体”。我自己现在的态度是所谓养成成功看的不是这个 Agent 能展示多少种技能而是它在真实任务里能不能稳定、按预期地调用一组技能把事情做完。能稳定做好二十件事比什么都能聊两句但频繁出错有价值得多。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价