AI Agent稳定性实践:使用Agent Skills构建可靠的大模型应用 做过 AI Agent 的人大概都有过这种体验模型选对了提示词也写了工具接口也接好了但一进入真实场景系统就开始失控。要么模型在多个工具之间反复横跳要么它明明该调用某个查询函数却自己发挥想象编了一段答案。后来我想明白一件事Agent 的稳定性不完全取决于模型有多聪明还取决于我们有没有把任务拆成一组边界清晰的“技能”。这个思路也是最近看吴恩达讲 Agent Skills 的教程时最强烈的感受。他真正想讲的不是某一个华丽的 Agent 框架而是怎么把复杂任务拆成可复用的技能再让模型可靠地选择和调用它们。如果你也正在学 Agent下面这套理解和实践路径应该能帮你少走很多弯路。1. 先想清楚Agent Skills 到底解决什么问题1.1 核心判断Agent 的瓶颈不是模型而是中间层很多初学者以为Agent 就是“一个很强的大模型 一段很长的系统提示词”。模型负责想提示词负责约束工具负责执行。但实际写过一个复杂 Agent 的人会知道最痛苦的地方往往不是模型理解能力不够而是“模型想执行但不知道怎么正确执行”。你可以让模型用自然语言输出“我要调用查天气工具城市是上海”然后你再写一堆正则解析这句话。看起来可行但一旦有两个工具、十个工具解析逻辑就会膨胀而且模型的口径会变今天输出“调用 get_weather 上海”明天输出“上海天气查询”后天干脆不调用直接给答案。这种不可控性才是 Agent 工程里的真正成本。Agent Skills 的理念就是在这个位置加一层“技能中间层”。每个技能不再是传统意义上的“函数”而是由描述、参数约束、执行逻辑、返回结构和错误处理组成的标准单元。模型不再自由发挥而是从一组有限的技能里做选择用严格定义的 JSON 或 tool calling 来传参。这样模型负责“决策”你负责“确定性”。这层中间层一旦立住后面很多事情都会变得顺单元测试可以做日志可以打失败可以重试不同 Agent 之间的技能还能互相复用。1.2 从“写函数”到“定义技能”是一种视角切换很多人刚上手时会写一个 Python 函数然后丢给模型调用。这本质上还是“开发者视角”我有什么工具你拿去用。但 Agent Skills 更强调“任务视角”每一个技能对应的是某类能力的最小原子操作。比如“获取用户所在地天气”是一个技能“计算两个日期之间的工作日天数”是另一个技能。开发者要做的事情不是简单地罗列函数而是像写一份岗位说明书一样告诉模型这个技能在什么场景使用、参数是什么、输出长什么样、失败了会返回什么。这个视角切换带来的收益非常直接复用性。同一个“天气查询”技能可以同时用在客服 Agent、日程规划 Agent 和出行建议 Agent 里。可测性。技能是一个独立单元你可以单独测试它而不需要每次把整个 Agent 跑一遍。可控性。模型的选择空间被限制在技能集合内它不能再自己编造执行过程。可观测性。日志里能清楚看到模型选了哪项技能、传了什么参数、函数返回了什么。所以Agent Skills 不是一个新框架也不是某个 API 的专有名词。它更像是一种设计模式解决的是“模型决策”和“代码执行”之间的协作问题。2. 吴恩达教程的核心价值不是知识而是学习路径2.1 入门到进阶到底在进阶什么看吴恩达关于 Agent Skills 的教程我最大的感受是他很少先扔给你一个复杂框架而是先把“最小可行单元”讲清楚。这类课程通常不会一上来就教你怎么用 LangChain 或 AutoGen而是先让你理解技能的最基本形态。我理解中的学习路径大概是这样的三层入门层单技能调用。让模型根据用户问题选择一个技能并严格填好参数。这一步解决“模型会选技能”的问题。进阶层多技能协作。一个复杂任务被拆成多个步骤模型可能需要先调用 A 技能再根据 A 的结果决定是否调用 B 技能。这一步解决“技能不会编排”的问题。工程层稳定性与评测。技能描述怎么写才不容易被误解参数校验怎么做模型调用失败后如何重试如何用一组测试用例持续验证技能描述改动的效果这一步解决“技能能不能长期用”的问题。很多人看完视频只停在第一层因为代码跟着敲了一遍模型也确实调用了技能就觉得“我懂了”。但真正进入进阶层标志是你开始思考如果用户问的问题需要同时调用两个技能怎么办如果第一个技能返回错误第二个技能还该不该执行如果两个技能都能回答同一个问题模型该怎么选这些都是“编排”问题不是“调用”问题。吴恩达课程里反复出现的小项目和检查点本质上就是逼你从“看懂”走向“做过”。2.2 为什么这种教程比“啃书”更容易上手“比啃书好太多”这种说法当然绝对了但对于动手实践型的学习者视频加代码组合通常比纯文字反馈更快。原因不是书不好而是 Agent 这类项目特别依赖立竿见影的反馈。你改了一段 prompt模型行为立刻变了。你调整了技能描述模型选择准确率上升了。这种即时反馈会不断强化你的理解。而读书时你往往是单向接收信息缺少“改一下、跑一下、看结果”的闭环。课程的优势是把环境配置、模型选择、典型踩坑提前替你走了一遍。你不用从零开始试错。但这里有一个重要提醒视频只是入口不是终点。收藏一百个视频也抵不上自己敲一遍最小示例再改一个任务。我自己的习惯是看一段停一下立刻复制代码去本地跑。跑通了再故意改坏一个地方看看模型行为有什么变化。这样一轮下来对这个知识点的记忆会比单纯看视频深很多。2.3 别把“全网唯一”“最全教程”当成关键词类似“全网唯一”“存下吧”这些说法本质上都是流量话术。我看技术教程时会先把情绪词过滤掉只留下知识主线。真正值得你花时间的不是标题有多吸引人而是它能不能在 30 分钟内帮你建立一套可执行的思路。与其存下三个小时视频不如先写下这篇文章里的最小技能实现。因为 Agent Skills 的难点不在“看”而在“改”。你能不能在本地搭一个最小 Agent然后自己给它加一个新技能如果能你才算真正入了门。3. 动手实现一个带“技能”的最小 Agent3.1 环境准备能本地推理就尽量本地推理学习阶段最怕两件事一是 API 调用不稳定二是每次调模型都要打印一屏未知输出。所以建议你先让模型跑在本地或者跑在你能完全控制的环境中。这里以兼容 OpenAI API 的本地推理服务为例。先安装依赖pip install openaiopenai 库版本按 1.x 的调用习惯写。如果你部署了本地推理服务假设地址是http://localhost:11434/v1代码里可以这样初始化from openai import OpenAI client OpenAI( base_urlhttp://localhost:11434/v1, api_keyollama )api_key是占位符本地服务通常不校验。实际项目里换成你自己的模型服务地址和密钥。3.2 封装第一个技能技能不只是一个函数它至少要包含技能名、描述、参数 schema、执行函数。下面用一个“时间查询”技能和一个“天气查询”技能做例子。先定义两个普通函数from datetime import datetime def get_current_time() - str: return datetime.now().strftime(%Y-%m-%d %H:%M:%S) def get_weather(city: str) - str: # 真实项目里这里会请求天气服务这里先用字典模拟 weather_map { 北京: 晴20 度, 上海: 多云23 度, 广州: 小雨26 度, } return weather_map.get(city, f暂无 {city} 的天气数据)然后建立技能注册表SKILLS { get_current_time: { description: 获取当前时间当用户询问现在几点、今天日期时使用。, parameters: { type: object, properties: {}, required: [] }, function: get_current_time, }, get_weather: { description: 获取某个城市的天气情况当用户询问天气、温度、是否下雨、今天适不适合出门时使用。, parameters: { type: object, properties: { city: { type: string, description: 城市名例如 北京、上海 } }, required: [city] }, function: get_weather, } }这段代码的重点不是函数本身而是description和parameters。模型正是通过这两样东西来决定“要不要调用”以及“怎么调用”。如果你描述写成“获取天气”模型很容易在“下雨”“出门”这些相关场景里漏选。更好的描述是“获取某个城市的当前天气情况适合用户询问天气、降雨、温度、出行建议时调用。参数 city 必须是中国城市名例如 北京、上海。”3.3 让模型学会“选择”技能一个最直接的方式是让模型输出一段 JSON再由你的代码做路由。这个思路不需要依赖模型是否支持 tool calling是最小的可解释骨架。构造系统提示词import json def build_system_prompt(skills): lines [ 你是任务助手。请根据用户问题从以下技能中选择一个并调用。, 可用技能 ] for name, meta in skills.items(): lines.append(f- {name}: {meta[description]}) lines.append(f 参数: {json.dumps(meta[parameters], ensure_asciiFalse)}) lines.append(请直接输出 JSON格式{\skill\: \技能名\, \arguments\: {参数}}) return \n.join(lines)然后调用模型response client.chat.completions.create( modelqwen2.5:7b, # 换成你部署的模型名 messages[ {role: system, content: build_system_prompt(SKILLS)}, {role: user, content: 上海天气怎么样} ], response_format{type: json_object}, )解析并执行result json.loads(response.choices[0].message.content) skill_name result[skill] arguments result[arguments] if skill_name in SKILLS: output SKILLS[skill_name][function](**arguments) print(output)这里有一个非常关键的工程习惯永远不要直接信任模型输出的参数。模型完全可能把city写成shanghai而不是上海也可能漏掉必填参数。所以生产代码里执行函数之前必须做参数校验和类型转换。这个留到后面专门说。如果你的模型服务支持 tool calling也可以把SKILLS转成tools传给 API。但核心思想是一样的模型给出一个结构化的调用意图代码只执行经过校验的技能。3.4 多技能调度从哪开始很多任务不是一次调用就能完成的。比如用户问“现在几点了上海天气怎么样” 这至少涉及两个技能。一个最简单的做法是先让模型生成一个多步骤调用计划然后逐条执行。但这样做很脆弱因为每一步的输出都可能影响下一步。更常见的做法是把历史消息和工具结果回传给模型让模型决定是否还需要继续调用。你可以这样写一个“循环调用”的骨架def run_agent(user_input, max_steps4): messages [ {role: system, content: build_system_prompt(SKILLS)}, {role: user, content: user_input} ] for _ in range(max_steps): response client.chat.completions.create( modelqwen2.5:7b, messagesmessages, response_format{type: json_object}, ) content json.loads(response.choices[0].message.content) skill_name content.get(skill) arguments content.get(arguments, {}) if skill_name not in SKILLS: messages.append({role: assistant, content: content}) continue output SKILLS[skill_name][function](**arguments) messages.append({ role: assistant, content: json.dumps({ skill: skill_name, arguments: arguments, output: output }, ensure_asciiFalse) }) if content.get(finished): break return messages这段代码不追求完美但展示了“多技能协作”的最小思路模型每一次输出一个调用意图你把函数执行结果作为新的消息放回上下文模型就可以看到结果决定是继续调用还是给出最终回答。这里最值得学习的一点是你不是让模型写代码而是让模型在有限技能列表中做决策。真正执行的是你写的、你测试过的、你控制得住的 Python 函数。4. 真正决定技能可用性的不是模型而是这四件事4.1 技能描述模型是“读说明书”来选你的技能描述是模型决策的最主要依据。描述写不好模型再强也容易选错或不选。这里有一个很直接的对比描述写法可能的问题获取天气太宽泛。模型可能不知道该传什么参数也可能在用户问“适合出门吗”时漏选查询某个城市的当前天气适合用户询问天气、温度、降雨、出行建议时调用。必填参数 city例如 北京、上海。返回结果包含温度和天气现象。模型知道什么时候用、参数怎么填、返回什么写技能描述时我一般会按五个维度检查功能边界这个技能能做什么不能做什么。触发场景什么类型的问题应该调用它。参数说明每个参数的含义、格式、示例、是否必填。返回值结构成功返回什么失败返回什么。典型错误哪些输入会导致失败失败后模型应该怎么修正。一个描述两三百字很正常。不要怕“提示词太长”对于技能注册表来说描述越清楚模型选择越稳。4.2 参数校验永远假设模型会传错参数模型不是数据库它生成的参数不一定严格遵循 schema。最常见的问题是城市名多了“市”字、日期格式不对、数字被传成了字符串、必填参数缺失。所以技能函数内部必须做防御式编程。把之前的天气函数改成带校验的版本def get_weather(city: str) - dict: if not isinstance(city, str): return {success: False, error: city 必须是字符串} if not city.strip(): return {success: False, error: city 不能为空} weather_map { 北京: 晴20 度, 上海: 多云23 度, 广州: 小雨26 度, } if city not in weather_map: return {success: False, error: f暂无 {city} 的天气数据} return {success: True, result: weather_map[city]}这里的返回值不再是一个裸字符串而是一个结构化结果。框架会给你带来两个好处失败信息可以被模型读取模型可以据此修正参数重试。日志可以清晰记录每次调用的成败方便后续排查。4.3 返回结构让 Agent 能看懂“成功还是失败”一个技能返回什么会直接影响 Agent 后续的决策。如果你返回的是普通字符串模型可能需要靠猜测来判断是否成功。更好的做法是统一返回结构。我建议使用类似这样的格式{ success: True, result: 多云23 度, error: None }失败时{ success: False, result: None, error: 暂无 长沙 的天气数据 }这样无论模型怎么理解你的路由代码都能稳定判断。你可以写一个统一包装器def ok(result): return {success: True, result: result, error: None} def fail(error): return {success: False, result: None, error: error}然后在每个技能函数里都用这两个方法返回。时间长了你会发现所有技能的错误处理是统一的排查成本和模拟成本都会下降。4.4 日志与测试技能是需要长期维护的代码资产很多 Agent 项目最初看起来很顺跑了一周后开始出现莫名其妙的调用错误。原因往往是某个技能描述在某个版本被不知不觉改了一句或者外部 API 返回格式变了。所以技能不能只靠“感觉”来维护。至少要做两件事第一为每个技能写单测。覆盖正常输入、边界输入、缺失参数、超长字符串、空值、类型错误。单测是最便宜的回归保护。第二记录技能调用日志。每一条至少包含{ timestamp: 2026-03-10 12:00:00, skill: get_weather, input: {city: 上海}, output: {success: true, result: 多云23 度} }有了日志你才能回答后面这类问题模型上周选“天气”技能的准确率是多少它经常把哪个参数填错哪个技能的失败率最高没有日志就只能靠用户投诉发现问题。5. 常见误区与排查链路5.1 误区一模型越强越不需要技能我见过一种观点等 GPT 级别的模型更强了Agent 就不需要预设技能了模型自己会完成一切。我认为这个判断把“理解能力”和“执行能力”混在一起了。只要你的 Agent 需要操作外部系统比如查数据库、调 API、发消息、改配置就需要代码去执行。模型可以决定“应该查数据库”但真正执行查询、处理权限、返回特定格式结果的仍然是代码。技能层在这里的作用不是给模型补脑子而是给你补可控性。模型越强它能调用的技能组合可能越复杂反而更需要清晰边界而不是更不需要。5.2 误区二技能写得越“全”越好有人会把一个技能写成“万能处理器”里面塞了查询、写入、通知、权限校验等一堆能力。这样做看似减少了技能数量实际上会带来两个问题模型很难判断该不该调用这个大全技能。因为它的能力边界已经模糊了。参数 schema 会非常复杂模型填错参数的概率直线上升。更合理的做法是“小技能”原则一个技能只做一件事且这件事的边界足够清楚。如果确实需要多个能力组合就让模型编排多个技能而不是让一个技能内部包揽一切。5.3 误区三只测成功路径初版 Agent 在 demo 时往往很顺利模型选对了技能参数填对了函数返回正常。可一旦遇到用户真实输入问题立刻暴露。最常见的错误是模型对不存在的城市也“编造”了一个天气结果因为你的技能函数没有做失败返回。所以测试用例里至少要有一半覆盖失败路径参数缺失。参数类型不对。输入为空字符串。查询的值不存在。外部 API 超时。技能对失败的处理决定了 Agent 在真实世界里的可用性。5.4 排查链路模型“不听话”时从哪开始查遇到 Agent 行为异常先不要急着改提示词。按下面顺序排查能帮你更快定位问题。看日志。模型到底有没有调用技能调用的是哪个技能传了什么参数看描述。当前问题是否和技能的description匹配描述里有没有写清使用时机看函数。直接调用技能函数确认函数本身没有 bug返回结构是否符合预期。看返回。技能返回的错误信息模型在下一轮能不能看到错误信息本身够不够明确看上下文。历史消息是否太长导致模型“忘记”了技能temperature是否设得过高导致模型随机选择技能看模型。当前模型是否真的支持 tool calling如果支持参数 schema 是否转换正确如果不支持JSON 输出是否能被稳定约束这个顺序的核心逻辑是先确认“代码层正常”再排查“模型决策层”。很多人一上来就改 system prompt结果真正问题出在函数内部抛了异常。先跑通最小链路再谈优化。6. 你的项目真的需要 Agent Skills 吗6.1 适合用 Agent Skills 的场景不是所有项目都适合用 Agent更不是所有项目都需要“技能层”。我总结了几个比较匹配的场景用户问题不固定你需要模型动态决定调用哪个工具。任务需要多步执行且每步依赖前一步的结果。需要接入多个外部 API但输入来源很杂需要模型统一调度。需要保存 Agent 的决策过程用于之后审查、纠错或评测。在这些场景里把每个外部能力封装成技能收益非常大。因为抽象出来的技能不只是给当前项目用还能复用到以后的新 Agent 里。6.2 不需要用 Agent Skills 的场景反过来下面这些场景可能更适合传统程序而不是 Agent固定流程用户点一个按钮就执行固定操作不需要模型判断。单步任务没有动态决策直接调用固定接口更快。强规则场景比如“如果余额小于 0 就返回错误”用 if-else 更好。在这些场景里引入 Agent 和技能层只会增加延迟、成本和不确定性。技术选型上最重要的能力是“知道什么时候不用”。6.3 一条稳妥的学习与落地路线如果你刚开始接触 Agent Skills我建议按下图走先把一个技能跑通。例如只做一个“查天气”技能让模型稳定调用。加第二个技能让模型在两个技能之间做选择。观察描述差异对选择结果的影响。给技能加错误处理和统一返回结构。让模型在失败后能重试或换一个技能。加日志和测试。把技能当成一个长期维护的代码资产。最后再考虑 Agent 框架。到这一步你已经理解底层的输入、输出和路由逻辑用任何框架都不会被框架束缚。我现在重新看吴恩达关于 Agent Skills 的教程记住的不是某个代码片段而是一个观点Agent 的核心不是让模型自由发挥而是给模型一套高质量、边界清晰、可观测的技能集。真正让 Agent 变可靠的不是更长的提示词而是让模型在有限的选项里做出更可靠的选择。如果你正在学 Agent不妨先忘掉那些花哨的框架老老实实把一个技能写好、测好、记录好。这一步做完后面很多事情都会顺起来。