ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

腾讯云 AI Skills 实践:从 YAML 定义到 Agent 工具调用稳定落地

腾讯云 AI Skills 实践:从 YAML 定义到 Agent 工具调用稳定落地 在 Agent 开发这件事上我见过太多人一上来就堆框架、上编排、搞多智能体协作结果卡在最基本的地方Agent 不知道怎么调用工具模型生成的调用参数跟实际函数对不上写一个能稳定跑通的 Skill 比想象中费劲得多。这段时间我把腾讯云的 AI Skills 完整走了一遍从设计、编写、调试到部署上线踩了不少坑。今天这篇不聊虚的直接把我实践下来的可行路径、关键配置和问题排查经验分享出来给正在做 Agent 落地的朋友一个参考。先说清楚 AI Skills 到底是什么。简单说它是部署在腾讯云上的一个代码执行单元以 YAML 文件为入口配上 Python 代码实现Agent 通过自然语言就能触发并传入实际参数。它解决了 Agent 开发里最头疼的分工问题Agent 只负责对话、拆解意图和编排Skill 负责确定性的逻辑和外部系统交互。你不需要追求 Agent 本身什么都会你只需要把能力封装成一个个 SkillAgent 自己就知道该调谁。这里要特别强调一下 skill 和 agent 的区别这是热词里出现频率最高的问题之一。Agent 是大脑负责理解用户意图、拆解任务并决定执行顺序Skill 是手脚提供具体的、可复用的执行能力接收参数并返回结果。如果把这个关系比作带团队Agent 是项目负责人Skill 是下面按约定交付的工程师。你不需要让 Agent 去实现具体功能你只需要告诉它有哪些 Skill 可用、每个 Skill 是干什么的、传什么参数它就能自动组合调度。我要分享的这套实践适合这几类人刚开始接触 Agent 开发但找不到合适落地方式的新手已经用其他框架写完 Agent 但遇到工具调用不稳定问题的开发者或者配置好了 Agent 但不知道怎么把自己写的代码能力优雅接进去的人。这篇博文会按一条完整链路来讲从 AI Skills 设计原则到 YAML 和代码实现再到腾讯云上的部署配置最后是调用验证和问题排查。1. Agent 与 AI Skills 的最佳分工模式1.1 为什么不能把所有逻辑都塞给 Agent很多人第一次做 Agent 时习惯把所有业务逻辑都写进提示词里希望模型自己“悟”出来怎么处理。这个思路在 Demo 阶段可行但一旦进入真实业务场景问题就冒出来了。模型本质上是概率生成你对它提再详细的要求它在执行具体计算、调用外部接口、解析结构化数据时依然会不稳定。让模型记住你的接口地址、参数格式、返回结构不但浪费上下文窗口而且稍微复杂点的任务就会出错。正确的思路是把确定性逻辑从模型里剥离出来下沉到 Skill。Skill 内部可以用任意你熟悉的编程语言实现本地测试能通过的逻辑部署到云端后行为完全一致。Agent 只需要知道这个 Skill 是“算字符串相似度”“查数据库”“调用内容审核接口”的然后按规范传参数并联调结果。这样划分之后Agent 的职责非常纯粹意图识别、任务规划、参数抽取。这也是为什么 AI Skills 把代码实现放在 YAML 描述文件后面的原因——描述文件是给 Agent 看的代码是给逻辑用的两层解耦各干各的。1.2 从项目结构看 Agent 与 Skill 的关系边界实际操作中一个完整的 Agent 工程通常长这样外层是 Agent 的核心逻辑负责接收用户请求、维护会话上下文、调用模型接口然后根据模型决策去调用不同的 Skill里层是多个 Skill 的集合每个 Skill 只要实现了输入输出约定就可以被 Agent 随意组合。这里有个容易踩坑的地方Agent 侧不要对 Skill 的内部实现做任何假设你只需要保证传给它的参数不越界就行。我在腾讯云的实践里把 Skill 的内容组织成三层核心逻辑层Python 或 JavaScript 实现的函数、适配层YAML 里的 meta、input、output 定义、部署层云函数配置、API 网关暴露。核心逻辑层解决“怎么做”适配层解决“Agent 怎么知道这个能力存在以及怎么调用”部署层解决“主体在哪里运行、怎么从公网访问”。三层职责清晰后后续调试、扩展、加权限控制都有章可循。2. 用 YAML 定义 AI Skills 的关键写法2.1 AI Skills 的完整 YAML 结构拆解AI Skills 的 YAML 是整个能力的“门面”模型靠它来理解你的 Skill 是否应对当前任务以及如何调用。我在调优过程中发现YAML 的描述质量直接决定了 Agent 的调用准确率。一个合格的 AI Skills YAML至少要有四个核心字段meta 描述技能的基本信息input 定义入参output 说明返回值结构template 给出模型可参考的执行模板。meta: name: dotnet_execute description: 执行用户提供的 dotnet 代码返回编译及运行输出结果适合需要在 .NET 环境中运行的场景。 version: 1.0.0 author: yourname tags: - dotnet - code_execution - coding input: - name: code type: string required: true description: 要执行的 C# / F# 代码必须包含完整的入口逻辑。 - name: input_data type: string required: false description: 作为标准输入传入代码的数据字符串形式序列化传入。 - name: timeout type: integer required: false description: 单次运行超时时间默认 5000 毫秒最大 30000 毫秒。 output: - name: output type: string description: 执行输出的标准内容。 - name: error type: string description: 异常信息若无异常则为空字符串。这个示例是我实际部署过的 dotnet 代码执行 Skill。description 字段看起来简单但它是模型决定要不要调用它的第一依据。我建议把 describe 写成场景化描述比如“适合……场景”“如果代码需要在 .NET 环境中运行请使用此 Skill”。这个写法比单纯说“执行代码”要有效得多因为模型在语义匹配时会优先关注这些限定性词。input 字段要明确 required 属性和 type这是模型生成参数时的硬约束。如果你这里写了 string 但实际代码里判的是 integerAgent 传参就会 404 或者直接崩掉。2.2 如何用描述和参数约束提高模型调用准确率这里有个身边的对比案例很能说明问题。我最早给一个字符串处理 Skill 写的描述很简陋只有一句话“这个 Skill 可以处理字符串大小写转换”。结果 Agent 经常在对代码执行类需求时也去调用它交互一团糟。改成“在用户要求将英文文本进行统一大写、小写转换或判断字符串是否满足某正则规则时调用若涉及程序运行、代码执行请使用其他 Skill”之后误调用情况基本消失。参数的描述也有讲究。模型设计原理决定了它是通过语义理解来生成结构化参数的所以你 input 里的 description 越像人话生成出来的参数越准确。比如“code”字段的描述不要写“代码”两个字就完了要写完整约束“要执行的 C# / F# 代码必须包含完整的入口逻辑”模型就会自动去补全用户原始输入里的代码内容。另外你还可以考虑把可能发生问题的边界情况直接写进描述里比如“single file 上传时禁用 gc server”这样模型在生成参数时天然就会避开。2.3 实际代码工程 云端 Skill 映射的完整实现有了 YAML 之后核心逻辑代码的组织同样重要。使用内置函数是 AI Skills 扩展能力的最主要机制在代码实现函数后加上cloud_function(代码执行)装饰器即可注意这里的装饰器参数值最好与 YAML 的 name 字段保持一致方便出错时对照排查。import json import subprocess import tempfile import os from skills_lib import cloud_function cloud_function(dotnet_execute) def dotnet_execute(event, context): code event.get(code, ) input_data event.get(input_data, ) timeout min(int(event.get(timeout, 5000)), 30000) if not code.strip(): return {output: , error: code 参数不能为空} # 写临时工程文件 with tempfile.TemporaryDirectory() as tmpdir: proj_path os.path.join(tmpdir, demo) os.makedirs(proj_path) with open(os.path.join(proj_path, Program.cs), w) as f: f.write(code) csproj_path os.path.join(proj_path, demo.csproj) with open(csproj_path, w) as f: f.write(Project SdkMicrosoft.NET.Sdk PropertyGroup OutputTypeExe/OutputType TargetFrameworknet8.0/TargetFramework /PropertyGroup /Project) # 编译并运行 try: compile_result run_command_with_timeout([dotnet, run], tmpdir, timeout) return {output: compile_result, error: } except Exception as e: return {output: , error: str(e)}event 参数就是模型按照你 YAML 的 input 定义生成的参数 JSON 对象代码从 event 里按字段名取值。context 存放执行环境信息比如内存大小、剩余时长。云函数模型的返回值是一个 JSON 对象里面的字段要跟 YAML 的 output 定义对应上Agent 才能正确解析执行结果。3. 部署到腾讯云的完整配置与实现路径3.1 准备腾讯云环境与创建密钥对打开腾讯云控制台进入访问管理 CAM 页面创建密钥对保存 SecretId 和 SecretKey 值。后面配置 CLI 和调用云端 API 都要用到。同时为你的函数计算服务开启相关服务角色权限让 Skill 能访问对象存储 COS 或数据库等其他云资源。这块看不到的权限还挺关键我就因为少配置了 SCF 对 COS 的访问权限导致 Skill 里读取文件一直返回 403。在准备好密钥后安装并配置腾讯云 CLI。macOS、Windows 都可以按官方文档装。配置下来后对输入密钥对、选择地域如果主要面向国内业务建议直接选 ap-shanghai如果涉及海外访问可以用 ap-singapore 等然后初始化。pip install tencentcloud-sdk-python tccli configure # 提示输入 SecretId、SecretKey之后选择 region # 例如ap-shanghai tccli scf ListFunctions --region ap-shanghai执行最后一条命令如果能看到返回函数列表说明环境和权限都是通的。这个方法可以快速确认账号状态不必直接部署完才发现密钥配反了。3.2 创建云函数并挂载代码包在腾讯云 SCFServerless Cloud Function控制台或直接用 CLI 完成创建。推荐在控制台新建一个“事件函数”运行环境选 Python 3.9 或 Node.js根据你 Skill 实现语言来确定执行超时时间本地先按 3 秒配云端可以后调。然后把你写好的代码文件连同依赖一起打包上传。上传有两种形式一是把 Skill 代码、YAML 文件、离线 requirements.txt 放到同一个 zip 包直接上传二是配置 Git 仓库持续集成每次 push 自动部署。前期测试强烈建议用 zip 包迭代快不用折腾流水线。确认代码包上传完毕后再在函数配置里把内存调到 256MB 或 512MBdotnet 编译等操作建议 512MB 以上超时时间根据你的实际需求调到 30 秒内都可以。这些对 Agent 用户侧都不会感知但对运行稳定性影响很大。这里说个容易忽略的点环境变量。你的函数代码里如果需要读取 COS 密钥、数据库连接串、外部 API Key不要硬编码在代码里在 SCF 控制台的环境变量里配置代码里用 os.environ 读取。这样做有几个好处Skill 的代码包可以进 Git 仓库做版本管理敏感信息不会泄露不同环境切换时只改环境变量不改代码万一密钥要轮换控制台改一下立即生效不用重新发包。3.3 API 网关触发器的配置与新版本的发布云函数本身不能直接被公网访问需要绑定 API 网关触发器。在 SCF 控制台对应云函数的“触发器管理”里新建 API 网关触发器选“新建 API 服务”路径可以写/请求方法建议只开 POST。鉴权方式选“免鉴权”可以用于调试但如果这个 Skill 要对接公网一定要加上鉴权配置哪怕只是 API Key 也行。不加的话别人一旦扫到你的域名就能无限调用你的函数账单会被刷爆。腾讯云上传二级域名很简单这里顺手说一句如果你用的是 API 网关默认生成的二级域名开发调试完全够用如果要做生产环境建议在自定义域名里绑定一个你备案过的域名走 HTTPS 会更稳妥。发布流程上建议 SCF 保持“版本控制”。你在云端改了代码并测试通过之后发布一个新版本然后让 API 网关指向这个版本的别名。这样后续迭代上线不影响线上正在跑的版本回滚也只要把别名指回旧版本。我踩过的坑是直接在“$LATEST”版本上绑网关每次改完代码还要小心翼翼生怕影响现网。创建完触发器之后控制台会给你一个公网 URL比如https://service-xxxxxx-xxxxxxxxxx.gz.apigw.tencentcs.com/release/用任意一个 HTTP 客户端curl、Postman、Apifox 都行发一个 POST 请求写上 JSON body检查 response。这一步能很快验证函数代码与环境配置是否正常也能帮你排查 API 网关转发设置是否有误。4. 测试、调用与 Agent 对接的调试心得4.1 本地单元测试与云端联调组合AI Skills 开发到一定规模后建议养成“先本地、再云端”的测试顺序。本地测不通过网络链路只用 mock 的 event 参数调用你写好的函数。你可以在项目根目录写一个简单的模拟请求脚本比如import json from main import dotnet_execute if __name__ __main__: mock_event { code: Console.WriteLine(Hello, AI Skills!);, input_data: , timeout: 5000 } result dotnet_execute(mock_event, {}) print(json.dumps(result, ensure_asciiFalse))把代码上传云端之前先用这样的脚本把可能出现的语法错误、缺依赖、返回格式不对等问题一次性过滤掉。本地跑通后再传云端再通过 API 网关发起真实调用。这样逐层排查问题会让你不会被“本地好了云端不行”这种玄学困住。4.2 通过 API 触发云端 Skill 的方法与调用返回云端功能正常后用 POST 请求调用 API。以 dotnet_execute 为例curl -X POST https://service-xxxxxx-xxxxxxxxxx.gz.apigw.tencentcs.com/release/dotnet_execute \ -H Content-Type: application/json \ -d {code: Console.WriteLine(12);, input_data: , timeout: 5000}返回内容大概长这样{ output: 3\n, error: }这里有一个关键点API 网关触发器的默认请求路径格式。SCF 平台上API 网关触发器默认会加一层/函数名路径如果你在控制台把路径设成/请求时直接用根路径。这个细节我折腾了大半个小时才反应过来明明函数 GET 到/了却一直 404。建议直接把触发路径设成/函数名语义清晰排查起来也快。同时 Body 里的参数必须是你 YAML 里定义过的字段多了字段或字段类型不符函数代码取值时可能拿不到值。4.3 把 AI Skills 接入腾讯云 Agent 编排的完整链路如果你用的是腾讯云的 Agent 编排服务那对接方式会更简洁新建 Agent配置系统 Prompt在“工具”面板里添加 AI Skills选择你刚发布的 Skill确认参数映射正确后即可发布。运行时只需要在对话里描述任务Agent 会自动匹配对应 Skill。这里最考验人的还是 Prompt 编写。建议系统提示词里就写清楚你拥有哪些 Skill、何时调用哪个 Skill、当用户请求不明确时先追问再调工具。这样配合 YAML 里的 description双重约束才能让 Agent 的调度准确率稳定在可接受范围。如果对接的是你自己开发的 Agent比如基于 LangChain 或自研框架那就需要用腾讯云提供的 Python SDK 调用 Skill 的 API本质上是把你上面调试好的 HTTP 接口包装成 Agent 的 Tool。给自定义 Agent 接入时Tool 的 name 和 description 建议直接从 YAML 的 meta/description 拷贝保持一致降低模型理解成本。下面给一个 SCF 云函数通过腾讯云 SDK 触发另一个 Skill 的代码参考from tencentcloud.common import credential from tencentcloud.scf.v20180416 import scf_client, models def invoke_skill(secret_id, secret_key, region, function_name, payload): cred credential.Credential(secret_id, secret_key) client scf_client.ScfClient(cred, region) req models.InvokeRequest() req.FunctionName function_name req.InvokeType Sync req.Payload json.dumps(payload) resp client.Invoke(req) result json.loads(resp.Result.RetMsg) return result同步调用适合需要立即拿结果的场景比如“帮我算一下这个字符串相似度”。异步调用适合耗时任务比如批量渲染视频、运行长时间训练这时 Skill 可以把任务 ID 返回给 AgentAgent 轮询另一个 Skill 获取结果。这属于进阶用法先把同步跑稳再说。5. 高频问题与实战避坑指南5.1 输入参数格式错误导致交互失败现象是 Agent 判断该调用 Skill 了但传参过去后 Skill 返回参数缺失或解析失败。最典型的原因是 YAML 里的 input 类型跟函数内实际处理的类型不一致。我排查过好几个例子都是 YAML 里写的 type: integer但代码里又拿字符串比较长度或者反过来。解决办法很简单写一个全局模板校验函数函数入口统一走一层类型转换和默认值兜底参数缺失时就返回一个统一 error。另外如果 input_data 跨度大比如要传入一长段文本要确认网关和函数代码里字符编码的一致性。网关一般用 UTF-8但如果你在本机用的命令行终端是 GBK那么中文传上去之后在云端就是乱码。建议所有调试环节统一用 UTF-8。5.2 网络策略与安全限制问题腾讯云 SCF 默认只允许公网访问由 API 网关转发进来的请求云函数内部如果还要访问公网资源比如调用一个外部 API需要给函数绑定公网访问能力。刚入手时最容易忽略的是函数执行角色CAM 角色没配好权限导致函数内调用 COS、数据库等内部资源失败。这里再一次建议不要贪方便把 SecretId 直接写进代码里用环境变量加 RAM 角色双保险才是生产环境至少该有的样子。再强调一次安全问题API 网关的触发器鉴权默认是“免鉴权”的话相当于给全世界开了个口子。轻则被扫重则被薅羊毛到欠费。哪怕你做的是内部测试也至少加一层 API Key如果真要开放建议用腾讯云自定义域名加 HTTPS并在 API 网关上绑定合理的流控策略比如每秒 10 次单 IP 每分钟 100 次不然有人并发刷你的接口你都不知道该怪谁。5.3 超时与执行时间配置不匹配有些 Skill 执行时间并不短比如编译 dotnet 代码默认 3 秒超时根本不够用。SCF 控制台在创建函数时可以调整 API 网关的超时时间但别忘了云函数本身的超时时间也要一起调。我先只调了 API 网关超时没调函数超时结果从外面看还是经常“504 Gateway Timeout”查了半天才明白过来是函数侧先掐断了请求。5.4 快速排障方法汇总为了节约排查时间我整理了一张速查表可以按顺序逐项检查问题现象排查点解决办法Agent 不调用 SkillYAML description 是否描述清晰将描述改得更场景化写明“当用户需求是……时调用”Agent 调用了但传参错误input 类型/字段名与代码不一致统一 event 字段名并在代码入口做类型兜底转换调用成功但返回 timeoutSCF 和 API 网关超时配置函数侧超时调大网关超时同步调大调用返回 403/405请求方法、鉴权方式不匹配触发器仅开 POST鉴权方式选择与请求头匹配日志看不到输出未启用日志或日志权限不足在 SCF 控制台启用日志检索给函数角色加日志写入权限中文乱码编码不一致统一 UTF-8客户端和代码都用 utf-8 处理5.5 长期维护与版本管理建议最后说说后期维护。AI Skills 不是写完就完事了它跟业务一样需要持续迭代。我用到的维护策略是每改一次代码就发布一个新版本不让版本号长期停留在 $LATEST每发布一个线上版本就把对应 YAML 里的 version 字段同步升级一次。同时在云函数描述或 TAG 里标明这个 Skill 的负责人、用途、依赖环境以后别人接手时不至于对着代码猜半天。另外如果 Skill 数量多了建议给每个 Skill 建一个 README记录创建时间、修改历史、已知问题。这个工作在初期看起来有点“不务正业”但 Skill 积累到十几个以后你回头看会发现它真的能救命。有一次我半夜排查一个线上事故全靠某 Skill README 里写的“已知问题不要传入空 code 否则会触发运行时错误”一句话直接定位了故障源。我在实际部署这套 AI Skills 方案时还发现一个很适合拿来扩展的场景你可以把 Agent 的“记忆”能力也封装成 Skill。热词里有人搜“agent记忆”本质上是想让 Agent 在多轮对话之间记住用户偏好和历史决策那就可以写一个 memory_skill内部对接云数据库或者 Redis把需要长期保存的用户特征、项目状态、关键结论结构化存起来。每次 Agent 启动时先从 memory_skill 里拉取相关信息任务结束后再把新信息写回去。这个思路比在系统提示词里堆历史记录要可靠得多——对话历史一长上下文很快就被撑爆而记忆 Skill 只保留结构化要点存取都稳定。如果你现在正准备开始做 Agent我的建议是不要盲目堆框架先想清楚你要拆分哪些能力再把这些能力边界划出来然后按“一个能力对应一个 Skill”的原则去实现。每个 Skill 都尽量做到输入明确、输出稳定、边界可控Agent 这层就能做得非常薄。等某天你把 Skill 越积越多回头再看你才算是真正把 Agent 从玩具做到了工具级别。最后再分享一个小技巧善用腾讯云控制台的“日志查询”每次调用都会记录请求 ID 和完整入参出参线上排障时先把这些数据拉出来比你在代码里写满 print 管用得多。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进