
别急着去看那些眼花缭乱的 Agent 框架和所谓“智能体编排平台”先想一个问题你手里那个看起来很聪明的 Agent真正让它从“能聊天”变成“能干活”的到底是什么答案大概率是 skills——技能。这也是 agent-skills 这个主题近段时间反复被拿出来讨论的原因。模型本身只是大脑而技能是手和脚。一个 Agent 能不能落地到真实的业务场景里取决于你给它装了什么样的技能、技能定义得够不够清晰、执行链路有没有兜底。今天这篇就围绕 agent-skills 这个方向从设计思路到实现细节再到我踩过的坑一次性聊透。1. Agent 的核心不是模型是技能1.1 为什么“能对话”不等于“能干活”大语言模型最擅长的事情是生成文字。你说“帮我查一下上海明天的天气”模型可以给你写一段像模像样的回答比如“上海明天多云气温 12 到 18 摄氏度东南风三级”——但它并不知道真正的天气。它只是在预测“这段文字出现在答案里的概率比较高”。要让 Agent 真正去查天气、查库存、调接口、改数据就必须让它具备调用外部工具和执行具体操作的能力。而这一层能力行业里通行的做法就是把它抽象成“技能skills”。技能的本质是什么我的理解是这样技能是“意图到动作的映射”。模型在对话或者推理的过程中识别出用户意图然后从技能清单里选一个匹配的技能填入参数触发执行再把执行结果带回给模型由模型组织成自然语言回复给用户。所以评价一个 Agent 靠不靠谱不取决于它用的是什么模型而是取决于它的技能体系完不完整、技能调用准不准、执行稳不稳。1.2 agent-skills 到底指什么从工程上看agent-skills 可以拆成两个层面第一层是技能本身。也就是一个可以被 Agent 调用的函数、工具、API 或操作流程它有一个名字、一段描述、一套参数定义以及一段可执行的逻辑。第二层是技能的管理与调度。包括技能怎么注册、怎么被发现、怎么被模型选中、参数怎么校验、错误怎么处理、执行完的结果怎么反馈。如果你的 Agent 只挂了三个技能那也不需要什么复杂体系。但当技能数量增长到几十个、上百个技能之间的命名冲突、描述模糊、参数歧义、上下文干扰这些问题就会全面冒出来。这个主题想解决的就是这一整个生命周期的问题。1.3 适合哪些人关注正在做 AI 应用落地觉得模型“不够聪明”但其实问题出在工具层的人想把 Agent 接入实际业务流程但不知道怎么组织和设计技能的人已经跑通了一个 demo但技能一多就乱想让 Agent 更稳定的开发者。2. 技能体系整体设计思路2.1 技能分层别把所有东西平铺在一张表里我见过不少团队的第一版技能列表就是把所有功能平铺在 JSON 文件里。比如{ skills: [ 查天气, 发邮件, 查库存, 创建订单, 查快递 ] }这看起来简单但马上会碰壁。技能数量超过十个以后模型在做工具选择时经常选错或者犹豫不定因为平铺的清单里缺少“领域感”。打个比方这就像你打开手机通讯录所有联系人按首字母铺排想找一个经常联系的人反而要翻半天。更靠谱的做法是做技能分层。我在实际项目里会把技能分成三层基础技能不可再拆的原子操作。比如“查天气”“查汇率”“发 HTTP 请求”“查询数据库”。复合技能由多个基础技能按固定流程组合而成。比如“下单”可能由“查库存”“创建订单”“发送通知”三个基础技能组成但对外暴露成单一技能。流程技能涉及多轮交互、条件判断、人工确认的技能。比如“报销审批”这种流程Agent 不能一次性执行完需要中间暂停、等人确认再去下一步。分层的好处有两个一是模型在选技能时候选列表更短更聚焦二是复合技能可以被复用不用每个场景都从头编排一遍。2.2 技能注册让 Agent 知道“你有什么”技能不是写在代码里就结束了Agent 要能“看到”每个技能的存在并理解它的用途。这个过程叫技能的注册与发现。常见的注册方式有三种函数级注册把每个技能封装成函数用装饰器或注解声明技能名和描述启动时自动扫描。配置级注册把技能定义写在 YAML 或 JSON 配置里运行时动态加载。目录级注册技能拆成独立模块或微服务通过接口注册到技能中心Agent 运行时动态拉取技能清单。我推荐的方式是配置级注册 函数实现分离。理由很简单技能名和描述是交给模型看的内容改动频率高函数实现是代码逻辑改动需要测试。把它们分开可以让“调技能文案”和“改技能逻辑”互不干扰。2.3 技能描述写不好描述再好的技能也没用这是被很多人忽视但极其重要的一个环节。模型是通过技能描述来理解“这个技能是干嘛的”的描述写得模糊模型就会乱用甚至无视这个技能。好的技能描述应该包含四要素功能定义这个技能能做什么一句话讲清楚。适用条件在什么场景下才应该使用这个技能。不适用条件什么情况下不要用避免误调。参数说明每个参数的含义、类型、取值范围、是否必填。我举一个实际例子。假设你写一个“股票查询”技能糟糕的写法是查询股票信息。模型看完这句话不知道“股票信息”具体指什么不知道是查价格、查市盈率还是查公司公告。更好的写法是根据股票代码查询指定股票的最新交易价格。使用场景当用户询问某只股票今天的价格、涨跌幅、成交量时。不适用场景用户询问股票的历史K线图、公司财报数据时请使用其他技能。参数stock_codestring必填6位数字股票代码。写清楚之后模型选错技能的概率会明显下降。别嫌这一步啰嗦技能描述是模型理解你系统的最主要入口省掉的每一句话最终都会变成用户看到的错误回答。3. 手写一个技能模块的完整实操3.1 技能定义文件怎么写内容安全是底线这里直接展示一个我从实际项目里简化出来的技能定义示例结构可以直接抄。{ skill_name: query_weather, display_name: 天气查询, description: 根据城市名称查询未来5天的天气预报。当用户提到天气、下雨、温度、降水等词语时使用。, input_schema: { type: object, properties: { city: { type: string, description: 城市名称如北京、上海、广州, required: true }, days: { type: integer, description: 查询天数取值范围 1-5默认 1, required: false } } }, output_schema: { type: object, properties: { city: { type: string }, date: { type: string }, temperature_max: { type: number }, temperature_min: { type: number }, condition: { type: string } } } }可以看到每个技能都包含技能名、签名、输入参数 Schema、输出 Schema。其中输入 Schema 是最重要的它会被模型用来决定怎么填参数。注意一个细节days 参数虽然选填但我在定义里专门写了取值范围。这样做是为了防止模型填出一个 999 这种一眼就不合理的数字。模型虽然聪明但你不把边界写清楚它就敢自由发挥。3.2 技能执行器真正的业务逻辑藏在哪技能定义是给模型看的技能执行器是真正干活的。还是用天气查询来举例一个最简单的执行器长这样from typing import Dict, Any def query_weather_executor(params: Dict[str, Any]) - Dict[str, Any]: city params.get(city) days params.get(days, 1) # 这里真实项目会调用外部天气 API比如和风天气、OpenWeatherMap # 我们这里用 mock 数据演示 result { city: city, date: 2025-01-15, temperature_max: 18, temperature_min: 12, condition: 多云 } return result执行器的核心原则只有一个输入必须是参数化的不能硬编码。很多入门选手把用户 query 直接塞进执行器比如def query_weather_executor(text: str): # 从 text 里面自己解析城市这样写的问题在于Agent 框架本身已经帮你了参数提取这一步不需要你在执行器里再做一次解析。执行器要做的是“收参数、调服务、返回结果”职责越单一越不容易出错。3.3 技能编排允许 Agent 自主组合但要加安全边界单技能的调用没什么难度难点在于复杂任务要多个技能配合。比如用户说“帮我订一张明天下午从上海到北京的高铁票然后提醒我后天早上开会”。这个任务涉及“查高铁班次”“提交订单”“创建日历提醒”三个技能而且是串行关系先查班次再订票最后才能加提醒。Agent 不是一个一个独立调用而是需要把三步编排起来。我对编排的建议是让模型自主编排但要在代码层面加约束。具体来说框架层面要支持一个“执行计划”的概念。模型先生成一个计划列出技能调用顺序、每个技能的输入依赖然后由执行引擎按计划执行。如果某个技能失败根据失败类型决定是终止、重试还是跳过。同时要特别注意安全边界。比如涉及付款、删除、修改数据的技能应该设置人工确认节点。不要让模型在没有确认的情况下直接执行破坏性操作。我在项目里就是这样设计的所有技能分三类——只读类直接执行更改类需要用户确认高风险类需要双层确认。这层护栏比什么都重要。3.4 技能执行环境沙箱与超时控制技能的执行环境也是个不容忽视的问题。如果 Agent 能执行代码或者调用系统命令那安全的做法是在沙箱里跑比如 Docker 容器、Firecracker 微虚拟机或者云平台的 Serverless 环境。这样做的目的不是说你的技能会被人恶意攻击而是防止“意外伤害”。模型在执行任务时可能会生成本身就没预料到的参数组合如果这个组合恰好触发了一个破坏性操作那后果由谁承担沙箱能显著降低这种风险。另外每个技能都应该有超时设置。我默认给所有技能设置 30 秒超时超过直接报错返回避免 Agent 卡在某个接口等半天浪费用户的耐心。4. 参考落地方案与工具选型4.1 三大主流实现路径笔记类或者说技能构建这件事现在没有统一的行业标准。市面上主流路径有三条大模型平台原生 Function Calling比如 OpenAI 的 function calling、通义千问的工具调用模式。人工把技能定义成 JSON Schema然后随对话请求一起发给模型模型返回一个结构化调用指令你本地执行后把结果回传。AI 框架集成比如 LangChain 的 Tools、LlamaIndex 的 Query Engine Tools、或各类国产 Agent 框架的插件机制。框架帮你做了技能注册、上下文管理、路由分发你只需要写执行函数。自研技能中心把技能做成独立服务通过统一接口注册到中心Agent 运行时动态拉取技能列表并执行远程调用。适合技能数量大、需要跨团队协作的场景。如果是个人项目或者快速验证原型可以选用第 2 条路径成本最低。如果是企业级应用技能数量多、访问控制严格我建议直接走第 3 条路径自研虽然前期工作量大但后期扩展性远优于前两者。4.2 给技能加上“记忆”会变得更好用纯技能调用有个问题每个调用都是上下文无关的。同样的技能昨天查过上海天气今天再查一次Agent 不知道昨天查了什么。如果要让 Agent 记住历史操作需要在技能层之外加一个记忆模块。我的做法是给每个技能的执行结果做持久化并把它挂到一个短期记忆缓存中。当用户在后续对话中提到比如“昨天你查那个城市的天气怎么样”Agent 能从记忆里找到对应的历史结果而不需要重新调用技能。但这里有个度的问题记忆不是越多越好。上下文窗口是有限的记忆太多会挤占模型处理当前问题的空间。所以记忆也要有淘汰策略最常用的做法是只保留最近 N 条执行记录并按相关性筛选后再注入上下文。4.3 框架和自研怎么选给你一条判断标准我在不同项目里两种方案都试过我的判断标准很简单技能数量少于 10 个交互逻辑不复杂是一个聊天助手类的应用 → 直接用框架。技能数量超过 10 个或者涉及复杂的权限管理、多团队协作、需要跨系统调用 → 自己写技能中心哪怕一开始只是简单地用接口版注册表。框架的问题在于它替你做了很多事也让很多事变得不可控。LangChain 这类框架层理了模型、RAG、Agent、Memory多一层抽象就多一层黑盒出了问题排查起来非常痛苦。而且技能多了以后框架的统一调度策略不一定适合你的业务场景。自研技能中心的好处是可以完全按自己的需求来。技能怎么注册、怎么描述、怎么选择、怎么执行、失败怎么处理每一步都可以自定义。缺点是要写的东西多光技能生命周期管理就要花不少时间。没有绝对正确的方案只有适不适合当前阶段。我的建议是先用框架快速验证发现不够用了再逐步替换成自研不要一上来就陷入自研的黑洞。5. 常见问题与排查技巧实录5.1 模型不调用技能老是自己在“编”表现用户问“上海天气怎么样”Agent 没有走技能直接回复了一段看起来像天气播报的话。这段内容其实是模型根据训练数据“猜”出来的并不是实时数据有时候是假的。排查思路先看技能是否注册成功。最简单的方式是打印一次模型请求的 payload看技能列表是否包含进去了。检查技能描述是否清晰。描述写得太泛模型不会认为它属于当前场景。看温度参数设置。把 temperature 调低到 0 到 0.3可以让模型更倾向于调用工具而不是自由发挥。检查模型版本。不同模型的工具调用能力差距很大一些参数较小或能力较弱的模型在复杂指令下容易忽略工具调用。我遇到的最常见原因是第二点描述写得不够具体模型无法把用户问题映射到技能上。5.2 模型调用技能但参数填错表现模型确实调用技能了但参数填得离谱。比如城市填了“Shanghai City, 100083”、日期填了“明天”。排查思路检查技能的 input_schema 是否写清楚了参数类型和示例。模型在不确定参数格式时会根据自己的理解发挥。在技能执行器里加参数校验逻辑。不要信任模型给的参数执行器必须先校验再执行。做一个“参数不合法时返回错误信息”的机制让模型看到错误后再重新填参数。这个技能层级的反馈循环能显著提升重试时的正确率。一个辅助技巧在 Schema 描述的末尾加一句示例比如“city 参数的示例值北京”。模型对示例非常敏感给一个正确的示例它填错的概率会降低一半以上。5.3 技能太多导致上下文爆炸还没轮到他说话表现Agent 对话不流畅响应时间越来越慢或者技能调用出现“张冠李戴”。原因是技能列表全量塞进上下文模型每次要判断几十个技能哪个合适判断成本非常高。解决方案技能分组根据用户首次输入的关键词先做一次意图粗筛只把粗筛后的技能子集注入上下文。用向量检索的方式动态选技能提前把技能描述转成向量每次用户提问时只取 Top K 的相关技能。对于流程类场景可以状态机化。当前需要哪些技能是确定的不再让模型从所有技能里自由选择而是限定在当前状态允许调用的技能范围内。我比较推荐第三种方式特别是业务流程固定的应用。它大幅缩小了模型的选择面也就大幅降低了调用出错率代价是需要你提前梳理流程。5.4 技能执行报错Agent 不会自己恢复表现技能执行抛出异常Agent 直接说“对不起我暂时无法处理”用户体验很差。原因Agent 框架默认情况下拿到报错信息后不知道怎么处理只会把报错当作最终结果呈现给用户。解决方案在技能执行器里对异常做分类并返回结构化的错误信息。我常用的分类简单直接参数错误 - 返回“参数不合法xxx”让模型重新提取参数。外部服务错误 - 返回“服务超时请重试”让模型隔几秒后重试一次。业务规则错误 - 返回“业务规则不允许xxx”让模型终止流程并告知用户原因。实现时给执行器加一层异常捕获统一包装成以下形式返回{ status: error, error_type: invalid_params, error_message: 城市名称不能包含数字, retryable: false }模型拿到这个错误信息后能做到比“直接报错”好得多的反馈参数错误就重填参数外部服务超时就重试业务规则不允许就结束并解释给用户。5.5 排查实操的一张速查清单症状优先检查点常用解法模型不调用技能技能描述、温度参数、模型能力优化描述、调低温度、换更强模型调用但参数乱填input_schema 清晰度、示例缺失补参数示例、加执行器校验技能调用零散、无组织技能分层缺失、没有编排做技能分组、按状态机限制调用范围执行报错无法恢复异常信息不结构化按错误类型分类返回、允许重试上下文爆炸响应慢技能全量注入向量筛选、意图粗筛、状态机限定6. 经验总结与个人体会看了这么多最后分享几个我在做 agent-skills 这个方向时比较深的体会。第一技能的抽象设计要在动手写代码之前想清楚。技能不是越细越好也不是越粗越好。粒度太细模型编排任务的负担很重一个简单任务要调十几次技能粒度太粗技能复用性变差场景一变就要写新技能。我个人的判断标准是一个技能应该对应一个不可再拆的领域动作或一个完整独立的业务流程介于两者之间的粒度通常都是不合适的。第二技能描述值得花时间打磨。你可能觉得写描述是件很“软”的事不如写代码实在。但一个技能描述的好坏直接决定了模型十几二十次调用的准确率。我给团队的要求是技能描述至少经过三轮 review 才能上线——第一轮让产品负责人看描述是否准确第二轮让工程师看 Schema 是否合理第三轮让测试用各种刁钻话术去试同一个技能会不会被误导。第三这个方向的迭代节奏特别快。我在项目做了一半的时候发现光是技能定义格式社区里就已经有几种不同的方案在竞争。与其等标准落地不如先用一套自己的规范把业务跑起来。只要保留好抽象层未来即使格式变化影响面也能控制在配置层不会震到业务代码。agent-skills 不是一个能一蹴而就搞定的话题它跟 Agent 本身一样是在一轮一轮的试错里迭代出来的。希望这篇里的设计和踩坑经验能给你省掉几周的弯路。