ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Agent Skills 实战:从工具调用到技能封装,基于 GKE 与 Genkit 的落地指南

Agent Skills 实战:从工具调用到技能封装,基于 GKE 与 Genkit 的落地指南 1. Agent Skills 到底是什么从“工具调用”到“技能封装”的认知升级第一次看到 “Agent Skills” 这个词很多人会下意识地把它和 Function Calling、Tool Use 混为一谈。我一开始也这么想直到在几个真实项目里被“工具太多、提示词爆炸、行为不稳定”这三座大山反复摩擦之后才真正理解 Skills 想解决的是什么问题。简单说Agent Skills 是把“一个智能体完成某类任务所需的指令、上下文、工具和资源”打包成一个可复用、可组合、可版本化的能力单元。它不是单纯的 API 封装也不是简单的提示词模板而是介于两者之间、更贴近“人类专家经验”的一种抽象。你可以把它类比成给一个新员工写的“岗位操作手册”手册里写清楚了什么场景下该做什么、用哪些系统、注意哪些坑、输出什么格式。Function Calling 更像是给员工一把钥匙告诉他“这扇门你能开”而 Skills 是告诉他“遇到这类客户投诉先查订单、再核对物流、最后按话术模板回复并且超过 48 小时未处理要升级”。前者是能力后者是能力加经验加流程。这个认知转变非常关键因为它直接决定了你在 Google Cloud 上构建 AI agents 时的架构选择。如果你只是把一堆工具塞给模型指望它自己“涌现”出正确行为那在 demo 阶段可能看起来很惊艳但一上生产就会暴露问题工具选错、参数填错、多轮对话后忘记约束、成本失控。Skills 的思路是把这些不确定性提前收敛到一个个经过验证的单元里让 Agent 在运行时做的是“选择和组合”而不是“从零推理”。从技术演进的角度看这个方向并不意外。早期大家拼的是模型本身的能力后来发现光有聪明的大脑不够还得有靠谱的手脚和记忆。GKE 提供了运行环境Genkit 提供了编排框架Google Cloud 提供了模型和基础设施而 Skills 补上的正是“经验沉淀”这一环。它让一个团队里某个高手调好的 Agent 行为能够被其他项目直接复用而不是每次重新写一遍提示词、重新调一遍参数。适合谁来关注这个内容如果你正在用 Genkit 或类似框架搭建多步骤 Agent如果你发现自己的提示词越来越长、越来越难维护如果你需要让非技术同事也能配置和调整 Agent 行为那 Skills 这套思路值得你花时间研究。它不要求你一开始就做得完美但要求你转变视角不要问“我的 Agent 能调用哪些工具”而要问“我的 Agent 需要掌握哪些技能每个技能由什么组成”。2. 拆解一个 Skill 的骨架指令、工具、资源与触发条件2.1 为什么不能只有一段提示词很多人做 Agent 的第一步是写一段很长的 System Prompt把所有规则、示例、注意事项都塞进去。我早期也这么干结果就是每次改一个细节都要动整段文本改完还得重新测试所有场景生怕影响了别的行为。更麻烦的是当 Agent 需要处理的任务类型变多时提示词会膨胀到模型难以稳定遵循的程度。实测下来超过一定长度后模型对中间部分的指令遵循度会明显下降这就是所谓的“中间遗忘”。Skill 的第一个价值就是把这段大提示词拆开。每个 Skill 只负责一类任务它有自己的指令集、自己的工具白名单、自己的输出格式要求。Agent 在运行时根据当前上下文判断该激活哪个 Skill然后只加载那个 Skill 的指令。这样做的好处是每个 Skill 的提示词可以保持精简和聚焦模型遵循起来更稳定调试时也更容易定位问题。2.2 一个 Skill 的四个核心组成部分根据我在实际项目中的拆解一个可用的 Skill 通常包含四个部分。触发条件决定这个 Skill 什么时候被激活可以基于用户意图、对话状态、外部事件或显式调用。指令集是这个 Skill 的核心描述目标、步骤、约束和输出格式通常用自然语言写但结构要清晰。工具集是这个 Skill 可以调用的函数或 API需要明确每个工具的用途、参数和返回值。资源包括这个 Skill 需要的知识库、示例、模板或配置文件可以是静态的也可以是动态检索的。这四个部分缺一不可。我见过有人只写指令不绑工具结果 Agent 在需要查数据时开始“编造”也见过有人只绑工具不写指令结果 Agent 拿到工具后不知道什么时候该用、怎么用。触发条件则是最容易被忽略的很多人默认 Agent 能自己判断但实际上如果没有明确的触发规则多个 Skill 之间会互相干扰出现“该用 A 的时候用了 B”的情况。2.3 用 Genkit 定义 Skill 的实操结构在 Genkit 里你可以用一个配置对象来定义一个 Skill。下面是我常用的一个模板结构以“订单查询与售后处理”为例const orderSupportSkill { name: order_support, description: 处理订单查询、物流跟踪和售后问题, triggers: [ { type: intent, value: order_inquiry }, { type: intent, value: after_sales }, { type: keyword, values: [订单, 物流, 退货, 换货] } ], instructions: 你是一个订单支持专员。按以下步骤处理 1. 先确认用户身份和订单号如果用户没提供礼貌询问。 2. 调用 getOrderDetail 获取订单状态。 3. 如果订单已发货调用 getLogistics 获取物流信息。 4. 如果用户要求退货或换货先检查订单是否在售后期限内。 5. 根据检查结果按对应话术模板回复并调用 createTicket 创建工单。 注意不要承诺具体退款时间不要泄露其他用户信息。 , tools: [getOrderDetail, getLogistics, checkAfterSalesPolicy, createTicket], resources: { templates: gs://my-bucket/skills/order_support/templates.json, policy: gs://my-bucket/skills/order_support/policy.md } };这个结构看起来简单但每个字段都有讲究。triggers里的 intent 需要和你的 NLU 或分类器对齐keyword 是兜底用的。instructions我习惯用编号步骤因为模型对有序列表的遵循度比大段文字高。tools只列这个 Skill 需要的不要把所有工具都放进去否则模型容易选错。resources用外部存储而不是硬编码方便更新而不需要重新部署。注意instructions里一定要写“不要做什么”这比只写“要做什么”更能约束模型行为。我在多个项目里验证过加入否定约束后违规率能下降一半以上。3. 在 Google Cloud 上落地GKE、Genkit 与 Skills 的配合方式3.1 为什么选 GKE 作为运行底座Agent Skills 本身是逻辑概念但运行起来需要计算资源。你可以用 Cloud Functions 或 Cloud Run 跑简单的 Agent但当 Skill 数量变多、需要长连接、需要 GPU 推理、需要复杂网络策略时GKE 的优势就出来了。我选 GKE 主要看中三点一是 Pod 级别的隔离不同 Skill 可以跑在不同 Pod 里互不影响二是自动扩缩容Agent 流量波动大GKE 的 HPA 能根据 CPU 或自定义指标伸缩三是网络控制Skill 调用外部 API 或内部服务时可以用 Network Policy 做细粒度管控。当然 GKE 也有代价就是运维复杂度比 Serverless 高。如果你只是做个原型Cloud Run 更快。但如果你打算把 Agent 做成生产系统尤其是需要和现有微服务打通GKE 的长期成本反而更低。我的建议是原型阶段用 Cloud Run 验证 Skill 逻辑生产阶段迁移到 GKE两者之间的代码结构可以保持一致迁移成本可控。3.2 Genkit 在 Skill 编排中的角色Genkit 是一个用于构建 AI 功能的框架它提供了流程定义、工具注册、模型调用、追踪调试等能力。在 Skills 架构里Genkit 主要承担三个职责。第一是 Skill 注册中心你把每个 Skill 定义好之后注册到 Genkit它负责管理生命周期。第二是运行时调度当用户输入进来Genkit 根据触发条件决定激活哪个 Skill然后加载对应的指令和工具。第三是可观测性Genkit 的追踪功能可以记录每个 Skill 的调用链路、耗时、token 消耗这对调试和优化至关重要。我实际用下来Genkit 最让我满意的是它的工具注册机制。你只需要把工具函数写好加上描述和参数 schemaGenkit 会自动生成模型能理解的工具定义。这样你就不用手写 JSON Schema 了减少出错概率。另外它的流式输出支持得不错对于需要实时反馈的场景很实用。3.3 一个完整的 Skill 调用链路示例假设用户在聊天窗口输入“我上周买的鞋子还没到帮我查一下”。整个链路是这样的首先Genkit 的入口流程接收到消息调用意图分类器可以用一个小模型或规则引擎识别出 intent 是order_inquiry。然后Skill 路由器根据 intent 匹配到order_supportSkill加载它的指令和工具集。接着Agent 按照指令第一步发现用户没提供订单号于是生成追问“请提供您的订单号”。用户补充后Agent 调用getOrderDetail拿到订单状态是“已发货”再调用getLogistics拿到物流轨迹。最后Agent 根据指令中的话术模板组织回复并返回给用户。这个链路里每个环节都可以独立测试和替换。比如意图分类器不准你可以单独优化它而不动 Skill 定义物流 API 换了你只需要改工具实现Skill 指令不用变。这种模块化带来的可维护性是我认为 Skills 架构最大的工程价值。4. 从零搭建一个 Skill 的完整实操记录4.1 环境准备与依赖安装我假设你已经有一个 Google Cloud 项目并且启用了必要的 API。下面是我在本地开发时的步骤你可以跟着走一遍。首先安装 Node.js 20 或以上版本然后初始化项目mkdir agent-skills-demo cd agent-skills-demo npm init -y npm install genkit genkit-ai/google-cloud genkit-ai/vertexai如果你要用 GKE 部署还需要安装 gcloud CLI 和 kubectl并配置好集群上下文。本地开发阶段可以先不碰 GKE用 Genkit 的本地开发服务器就能跑通大部分逻辑。gcloud auth application-default login gcloud config set project YOUR_PROJECT_ID提示gcloud auth application-default login这一步很多人会漏掉导致本地调用 Vertex AI 时权限报错。如果你用的是服务账号记得设置GOOGLE_APPLICATION_CREDENTIALS环境变量指向 JSON 密钥文件。4.2 定义第一个 Skill天气查询为了让你快速看到效果我们先做一个最简单的 Skill天气查询。它只有一个工具指令也很短。创建skills/weather.jsimport { defineTool } from genkit-ai/core; export const getWeather defineTool( { name: getWeather, description: 查询指定城市的当前天气, inputSchema: { type: object, properties: { city: { type: string, description: 城市名称如北京 } }, required: [city] }, outputSchema: { type: object, properties: { temperature: { type: number }, condition: { type: string }, humidity: { type: number } } } }, async (input) { // 实际项目中这里调用天气 API const mockData { 北京: { temperature: 22, condition: 晴, humidity: 40 }, 上海: { temperature: 25, condition: 多云, humidity: 65 } }; return mockData[input.city] || { temperature: 20, condition: 未知, humidity: 50 }; } ); export const weatherSkill { name: weather_query, description: 查询城市天气, triggers: [ { type: keyword, values: [天气, 气温, 下雨, 温度] } ], instructions: 你是一个天气助手。用户询问天气时 1. 提取城市名称如果用户没说城市询问用户。 2. 调用 getWeather 获取天气数据。 3. 用友好语气回复包含温度、天气状况和湿度。 4. 如果温度低于 10 度提醒用户注意保暖。 , tools: [getWeather] };然后在主流程里注册这个 Skillimport { genkit } from genkit; import { vertexAI } from genkit-ai/vertexai; import { weatherSkill, getWeather } from ./skills/weather.js; const ai genkit({ plugins: [vertexAI({ location: us-central1 })], model: vertexai/gemini-1.5-flash }); // 注册工具 ai.defineTool(getWeather); // 注册 Skill const skills [weatherSkill]; // 简单的 Skill 路由 function routeSkill(userInput) { for (const skill of skills) { for (const trigger of skill.triggers) { if (trigger.type keyword) { if (trigger.values.some(v userInput.includes(v))) { return skill; } } } } return null; } // 主流程 export const chatFlow ai.defineFlow( { name: chatFlow, inputSchema: { type: string }, outputSchema: { type: string } }, async (input) { const skill routeSkill(input); if (!skill) { return 抱歉我暂时无法处理这个请求。; } const prompt ${skill.instructions}\n\n用户说${input}; const response await ai.generate({ prompt, tools: skill.tools.map(t ai.getTool(t)) }); return response.text; } );跑起来之后你输入“北京天气怎么样”应该能看到 Agent 调用工具并返回结果。这个例子虽然简单但包含了 Skill 的完整要素触发、指令、工具、路由。4.3 参数选择与性能调优在实际部署时有几个参数需要你根据场景调整。模型选择方面Gemini 1.5 Flash 适合对延迟敏感、任务简单的 SkillPro 适合需要复杂推理的 Skill。我通常会给每个 Skill 单独指定模型而不是全局用一个。温度参数方面涉及工具调用的 Skill 建议设低一点0.1 到 0.3 之间减少随机性纯对话类 Skill 可以设 0.7 左右。最大输出 token要根据 Skill 的输出格式来定如果要求 JSON 输出留够空间但别太大避免模型啰嗦。还有一个容易被忽略的参数是工具调用轮次上限。默认情况下模型可能会反复调用工具如果不设上限遇到逻辑死循环会烧掉大量 token。我在 Genkit 里通常会设maxToolCalls: 5超过就强制返回当前结果并记录告警。5. 多 Skill 协作与冲突处理真实项目中的坑5.1 Skill 优先级与互斥规则当你的 Agent 有十几个 Skill 时触发条件重叠几乎不可避免。比如“查询订单”和“查询物流”都可能被“我的快递到哪了”这句话触发。如果没有优先级规则Agent 可能随机选一个导致行为不稳定。我的做法是给每个 Skill 设一个priority字段数字越小优先级越高路由时按优先级排序命中第一个就停止。同时对于明确互斥的 Skill加一个exclusiveGroup标记同一组内只能激活一个。const skillA { name: order_query, priority: 1, exclusiveGroup: order }; const skillB { name: logistics_query, priority: 2, exclusiveGroup: order };这样当两个都命中时order_query会胜出。但这里有个问题如果用户真正想问的是物流却被订单查询拦截了怎么办我的经验是在指令里加一个“转交”机制Skill A 在处理过程中如果发现用户意图其实是 B可以主动调用transferToSkill(logistics_query)把控制权交出去。这比在路由层做复杂判断更灵活也更符合真实对话的流动性。5.2 上下文传递与状态管理多 Skill 协作时上下文怎么传是个大问题。比如 Skill A 收集了订单号转交给 Skill B 时B 需要知道这个订单号否则又要问一遍用户体验很差。Genkit 提供了 session 或 state 机制你可以在 Skill 之间共享一个上下文对象。我的做法是在流程入口初始化一个context对象每个 Skill 处理完后把关键信息写进去下一个 Skill 从里面读。const context { orderId: null, userId: null, history: [] }; // Skill A 处理后 context.orderId 12345; // Skill B 读取 if (context.orderId) { /* 直接使用 */ }注意上下文里不要放敏感信息比如完整手机号、身份证号。如果必须用做脱敏处理或加密存储。我在一个项目里因为上下文里存了明文地址被安全审计提了问题后来改成只存引用 ID用时再查。5.3 冲突排查速查表下面这张表是我在实际运维中总结的常见冲突和解决方法你可以直接拿去用现象可能原因排查方法解决措施Agent 选错 Skill触发条件重叠打印路由日志看命中顺序调整 priority 或加互斥组Skill 激活后不执行工具工具未注册或名称不匹配检查 Genkit 工具注册列表确保 tools 数组里的名称和注册名一致多轮对话后行为漂移上下文过长或指令被稀释查看每轮 token 数和指令位置精简指令把关键约束放前面工具调用参数错误Schema 定义不清晰查看模型生成的参数和报错完善 inputSchema 的 description响应延迟高模型太大或工具串行调用看追踪链路各环节耗时换小模型或并行化工具调用这张表里的每一条都是我踩过的坑。特别是“工具名称不匹配”这一条Genkit 注册工具时用的是name字段但你在 Skill 的tools数组里如果写的是变量名而不是name值就会静默失败模型会以为没有这个工具然后开始编造答案。这个 bug 我找了两个小时才定位到。6. 测试与可观测性让 Skill 行为可衡量6.1 单元测试与集成测试的分工Skill 的测试不能只靠人工聊天。我的做法是分两层单元测试针对每个 Skill 的指令和工具用固定的输入验证输出是否符合预期比如给定“北京天气”检查是否调用了getWeather且返回了温度字段。集成测试针对多 Skill 路由和上下文传递模拟完整对话流验证 Skill 切换是否正确、上下文是否丢失。Genkit 提供了测试工具你可以用ai.runFlow在测试里直接调用流程。我通常会写一个测试用例表覆盖正常路径、边界情况和异常路径。比如用户输入空字符串、输入超长文本、输入包含多个意图的句子看 Agent 怎么处理。6.2 用追踪数据优化 Skill 设计Genkit 的追踪功能会记录每次调用的完整链路包括模型输入输出、工具调用参数和结果、耗时、token 消耗。这些数据是优化 Skill 的金矿。我每周会看一次追踪数据重点关注三个指标工具调用成功率如果某个工具经常被调用但失败说明 Schema 或实现有问题平均轮次如果某个 Skill 平均要 5 轮以上才完成说明指令不够清晰token 消耗分布如果某个 Skill 消耗异常高可能是指令太长或模型选得太大。根据这些数据我做过几次优化。有一次发现“售后处理”Skill 的平均轮次是 7远超预期的 3。看追踪发现模型总是在确认订单号上反复询问因为指令里没写清楚“如果用户已经提供了订单号不要再问”。加上这一句后轮次降到 3.2。这种优化不需要改代码只改指令但效果立竿见影。6.3 告警与降级策略生产环境里Skill 不可能永远正常。外部 API 会挂模型会限流网络会抖动。我的做法是给每个 Skill 配一个降级策略如果工具调用失败超过阈值返回预设的兜底话术并记录告警如果模型调用超时切换到更小的模型或返回缓存结果。这些策略写在 Skill 定义里由 Genkit 的中间件执行。告警方面我接入了 Google Cloud 的 Monitoring对 Skill 失败率、P99 延迟、token 消耗突增设阈值。一旦触发发通知到团队频道。这样即使半夜出问题也能第一时间知道而不是等用户投诉。7. 我踩过的五个坑和对应的解法第一个坑是指令里用了太多“可以”“建议”这类软性词。模型对软性词的遵循度很低你说“建议先确认订单号”它可能觉得不确认也行。后来我全部改成“必须”“禁止”“务必”遵循度明显提升。第二个坑是工具描述写得太简略。比如getOrderDetail的描述只写“获取订单详情”模型不知道参数该传什么。后来我把描述改成“根据订单号查询订单的详细状态包括下单时间、支付状态、发货状态”模型调用准确率大幅提高。第三个坑是Skill 之间共享了可变状态但没有加锁。在并发场景下两个请求同时修改同一个上下文对象导致数据错乱。后来改成每个请求独立上下文需要共享的数据通过外部存储传递。第四个坑是没有限制工具调用的返回大小。有个物流查询工具返回了完整的物流轨迹 JSON几千个字符直接把上下文撑爆模型后面就胡言乱语了。后来我在工具实现里做了截断只返回最近五条轨迹。第五个坑是忽略了模型的版本更新。有一次 Vertex AI 悄悄更新了 Gemini 的版本导致某个 Skill 的输出格式变了下游解析失败。后来我固定了模型版本号并且在追踪里记录版本每次更新前先跑回归测试。这个习惯帮我避免了好几次线上事故。8. 从单 Skill 到 Skill 生态后续扩展思路当你跑通了几个 Skill 之后自然会想怎么规模化。我的建议是先建一个 Skill 注册中心把所有 Skill 的定义、版本、依赖、测试用例集中管理。可以用一个简单的 JSON 文件或数据库表记录每个 Skill 的元数据。然后建一个 Skill 市场让团队成员可以发布、搜索、复用 Skill。这听起来有点重但其实用 Git 仓库加 CI 就能实现每个 Skill 一个目录提交时自动跑测试合并后自动部署。另一个扩展方向是让 Skill 支持动态组合。现在的 Skill 是预定义的未来可以根据任务临时组合多个 Skill 的能力。比如用户要“查订单并推荐相似商品”可以动态激活order_query和product_recommend两个 Skill让它们协作完成。这需要更复杂的编排逻辑但 Genkit 的流程定义已经提供了基础。最后Skill 的评估和迭代应该常态化。我每个月会抽样一批真实对话人工评估 Skill 的表现找出失败案例归因到具体 Skill 或指令然后迭代。这个闭环跑起来之后Agent 的能力会持续提升而不是上线即巅峰然后慢慢退化。我个人在实际操作中的体会是Agent Skills 这套东西入门不难难的是持续维护和迭代。它不像传统软件那样一次写完就稳定运行而是需要你像带团队一样不断观察、反馈、调整。但一旦跑顺了你会发现它带来的复用性和可控性远比每次从零写提示词要划算。
返回列表