
1. 从能聊天到会做事Agent Skills到底在解决什么问题这两年做AI Agent相关项目的人应该都有同感模型本身的对话能力已经不太是瓶颈了真正卡脖子的是怎么让Agent稳定地完成一件具体的事。你让大模型跟你聊哲学它能聊一天但让它去查一下某仓库最近一周的Issue、整理成表格再发到群里它就经常在中间步骤掉链子——要么工具调错、要么参数漏传、要么中途开始自由发挥。我最早做Agent项目的时候用的是最朴素的工具函数列表方案把每个能力写成一个Python函数塞进一个列表告诉模型你有这些工具可以用。简单场景没问题但一旦任务变多、步骤变长这种方案的脆弱性就暴露出来了工具之间的依赖关系没人管、参数校验靠模型自觉、失败重试逻辑散落在各个函数里。后来我接触到agent-skills这个思路才意识到问题出在设计单元上——我们一直在往模型手里塞零散工具而Agent真正需要的是一套可组合、可校验、可观测的技能。这篇文章会围绕我在实际项目中搭建Agent技能系统的完整过程展开包括Skill和Tool/Plugin的边界怎么划分、Skill的描述格式怎么设计、运行时调度和上下文管理怎么做、以及我在真实环境中踩过哪些坑。如果你正在做Agent类应用、或者刚开始接触LLM应用开发这篇内容应该能帮你少走不少弯路。需要说明的是文中不少方案来自我在多个项目里的通用实践具体业务场景里可以按需裁剪。2. Skill与Tool、Plugin的边界先搞清楚你的设计单元是什么很多人第一次接触agent-skills时最大的困惑就是这跟之前说的Function Calling、Tool、Plugin有什么区别我在项目初期也纠结过这个问题后来发现这个边界不搞清楚后面整个系统都会设计得很别扭。2.1 三类概念的典型差异我习惯用粒度和智能归属两个维度来区分它们Tool工具最原子的能力单元。比如发送HTTP请求、执行一段Python代码、读取某个文件。它本身不包含业务判断参数明确输入输出都是严格定义的。Plugin插件按产品功能划分的能力集合。比如一个GitHub插件会封装仓库查询、Issue管理、PR操作等多个方法。它解决的是把某个外部系统的能力接进来的问题。Skill技能按任务目标划分的完整行为单元。一个Skill内部可能编排多个工具调用、包含中间状态的判断、甚至内嵌一小段流程逻辑。它回答的是如何完成某类任务的问题。举个例子给Agent一个整理周报的需求。如果用Tool思维你会提供读取Git提交记录、读取Issue列表、调用大模型生成总结、发送消息到群四个工具然后指望模型自己把整个流程编排对。如果用Skill思维你会直接提供一个generate_weekly_report技能它内部就封装了获取数据、筛选关键项、调用模型总结、格式化输出的完整流程Agent只需要提供项目范围和时间区间两个输入。2.2 为什么工具思维会翻车我在早期项目里吃过亏给了模型七八个细粒度工具它在多步任务里的工具调用准确率从单步的95%掉到60%以下。原因很直接——每一步的决策空间太大模型既要决定下一步该干什么、又要决定用哪个工具、还要决定参数怎么填累积误差非常可观。而Skill的思维是把已经验证过的流程固化下来。**模型不需要在每一步都重新发明轮子它只需要在几个大方向上做选择。**决策空间从几十个工具的排列组合缩小到几个技能的选择和参数填写稳定性自然上来了。从我的实践经验看判断某个能力该做成Tool还是Skill就看一个标准**这个流程是不是已经被验证为标准答案**如果是固定套路就固化成Skill如果是开放探索才暴露成Tool让模型自由组合。3. 一个可落地的Skill注册与调度框架设计明确了设计单元之后接下来就是怎么在代码层面组织这些Skill。我目前采用的是一个注册中心 调度器 执行器的三层结构下面详细拆解。3.1 整体架构与数据流用户请求 - 意图路由 - Skill调度器 - Skill执行器 - 结果校验 - 返回/下一步这个流程看着简单但每一层都有讲究。我直接说我在项目里的落地方案。Skill注册中心用一个全局字典来维护所有可用的Skill。每个Skill在注册时需要提供名称、描述、输入参数的JSON Schema、执行函数、以及依赖的其他Skill列表。注册中心的核心职责是运行前校验——检查参数Schema合法性、检查依赖是否存在、检查是否出现循环依赖。Skill调度器负责根据用户请求选择要执行的Skill。这一步我做了两个策略的组合当意图明确时比如用户直接说生成周报走精确匹配直接根据Skill名称或者固定的触发关键词路由。当意图模糊时把候选Skill的描述全部交给模型做一次技能选择让模型返回最匹配的Skill名称和参数。这里有一个关键经验**给模型看的Skill描述要专门写一版面向选择的简版描述不要把内部实现细节放进去。**系统提示词里的描述越干净模型选得越准。Skill执行器拿到调度结果后开始执行。执行器需要处理三类事情参数的解析与校验、Skill内部步骤的有序执行、以及执行结果的标准化包装。我习惯让每个Skill的执行函数返回一个统一的Result对象包含状态success/failed/needs_human、数据、以及可选的下一步建议这样上层逻辑处理起来非常省心。3.2 注册与调度的最小实现下面是我在Python项目里使用的一段简化示例展示注册中心的核心逻辑。这段代码去掉了业务细节只保留骨架你可以直接套用from typing import Any, Callable, Optional class SkillRegistry: Skill注册中心负责管理所有可用技能并在运行时校验合法性。 def __init__(self): self._skills: dict[str, dict] {} def register( self, name: str, description: str, parameters_schema: dict, handler: Callable[..., Any], dependencies: Optional[list[str]] None, ) - None: 注册一个Skill。 - name: 全局唯一的技能名小写下划线风格 - description: 供模型选择的简版描述必须写清楚何时使用 - parameters_schema: 符合JSON Schema规范的参数定义 - handler: 执行函数接收解析后的参数 - dependencies: 依赖的其他Skill名称列表 if name in self._skills: raise ValueError(fSkill {name} 重复注册) if dependencies: for dep in dependencies: if dep not in self._skills: raise ValueError(fSkill {name} 依赖的 {dep} 尚未注册) if not self._validate_schema(parameters_schema): raise ValueError(fSkill {name} 的参数Schema不合法) self._skills[name] { name: name, description: description, parameters_schema: parameters_schema, handler: handler, dependencies: dependencies or [], } print(f[registry] 已注册技能: {name}) def get(self, name: str) - dict: if name not in self._skills: raise KeyError(fSkill {name} 不存在) return self._skills[name] def list_skills(self) - list[str]: return list(self._skills.keys()) def _validate_schema(self, schema: dict) - bool: # 生产环境建议接入 jsonschema 库做严格校验 # 这里简化成检查必填字段是否存在 return isinstance(schema, dict) and type in schema调度器的实现思路是先检查是否命中精确触发器没命中再构造候选描述列表发给模型。模型选择这一步我没有手写prompt而是直接复用当前项目里已有的模型调用封装只强调了两个约束只能返回Skill名称和参数JSON、不得输出任何额外文字。这样就避免了解析模型自由文本的麻烦。3.3 为什么分层而不是写死调用有朋友问过我你直接写一个大的函数分派每个case对应一个技能不是更简单吗为什么非要搞注册中心和调度器答案在于可扩展性和可测试性。注册中心模式让新技能的接入成本变成写一个handler、调一次register、加一条描述。如果走if-else分派每加一个技能都要改调度主函数改到后期一定会出现加了新技能但旧分支没覆盖的问题。而且注册中心天然支持在运行时检查技能间的依赖关系这在复杂Agent场景里几乎是必须的。4. Skill的定义格式与元数据设计好的描述是成功的一半在Agent场景里Skill的定义质量直接影响模型的选择准确率。项目做到中后期你会发现写Skill的描述和参数Schema比写执行逻辑还费心思。因为执行逻辑是确定性的而描述是给一个概率模型看的必须用模型的思维方式去写。4.1 描述该写什么、不该写什么我踩过的坑是一开始写描述喜欢把实现细节写进去比如本技能通过调用GitHub REST API获取最近七天的提交记录结合Issue数据调用GPT-4o生成摘要……。这样的描述有两个问题——太长占用上下文太碎模型容易关注错重点。后面我把描述改成了使用场景 输入要求 输出效果三段式每条控制在100到150字。举例generate_weekly_report: 根据用户提供的项目名称和时间范围自动汇总该项目的提交记录、Issue变更和关键事件生成结构化中文周报。用户明确要求周报本周总结时使用。需要传入项目名称和时间范围。这种描述对模型来说决策性更强它在选择技能时只需要判断用户的话跟哪条描述匹配而不需要理解内部实现。我在多组对比测试里发现描述从实现细节型改成场景匹配型之后技能选择准确率提升了大约15个百分点。4.2 参数Schema用JSON Schema约束模型的自由发挥Skill的参数定义必须走JSON Schema不要偷懒用字符串模板。因为模型填参数时的自由度直接决定了后续校验的成本。我常用的Schema约束包括required必填字段明确列出模型漏参时会更容易被发现。type和format比如日期字段指定format: date模型就不太会填出下周这种无法解析的值。enum对于可选值有限字段比如报告格式包含markdown、json、html用枚举锁死能极大减少脏数据。description给字段写人话描述比如时间范围格式为YYYY-MM-DD闭区间。模型看到这个描述时填错概率会明显降低。下面是一段实际的Schema示例{ type: object, properties: { project_name: { type: string, description: 项目名或仓库名例如>context { user_request: ..., current_project: data-pipeline, collected_commits: [...], report_content: None, } # 调度器执行逻辑示意 skill registry.get(collect_commits) inputs {k: context[k] for k in skill[input_keys]} result skill[handler](**inputs) context[collected_commits] result.data这种按需取用的模式配合每个Skill的元数据声明input_keys、output_keys读代码的时候非常直观也不容易出现某个中间变量被意外覆盖的问题。5.3 失败重试与降级Agent执行过程中Skill抛错是常态。最典型的是外部API超时、解析失败、参数校验不通过。我形成了三条处理规则可重试的错超时、临时性网络错误自动重试两次间隔递增第二次失败后才上报。不可重试的错参数校验失败、权限不足直接停止当前Skill把错误信息反馈给上层模型让模型决定是换参数还是换Skill。降级路径给关键Skill配置fallback_skill比如根据Github提交生成摘要失败时降级成直接列出原始提交记录。这套规则看着简单但救过我很多次。没有它的时候一个超时错误会让整个Agent任务直接失败有了它之后很多小毛刺都被静默处理掉了用户感知不到。6. 评估一个Skill系统好不好三个层面逐层检查Skill系统做完了怎么知道做得好不好不能只看Demo演示是否顺滑我一般从三个层面来评估。6.1 单Skill准确率每个环节单独验证第一个层面是每个Skill单独拿出来跑能不能稳定完成。我有一组固定的测试用例覆盖每个Skill的正常路径、边界输入空值、超长、错误格式和异常路径。每次修改Skill后都跑一遍回归。这一层的目标是把确定性逻辑做到99%的稳定因为它是整个链路的地基。我举一个实际例子一个解析日期范围的Skill正常输入是上周一到现在边界输入是昨天、前三天、从5号到10号不含10号。你光靠模型自然语言处理这些边界会花式出错但只要把常用的日期表达固化成规则、无法解析时再走模型兜底准确率就能从70%拉到95%以上。6.2 多Skill编排成功率端到端测试第二个层面是端到端测试检查多个Skill串起来能不能完成完整任务。这一层最容易出现每个Skill都正常但连起来就废了的问题主要原因是数据格式没对齐。比如前一个Skill输出的是Markdown表格后一个Skill期待的是结构化JSON中间没有适配层模型强行转换就容易丢信息。我的经验是在Skill执行器里加一个输出格式转换的轻量适配层每个Skill声明自己的输出格式调度器在传给下一个Skill前自动做一次转换。这样就避免把格式转换这件事丢给模型临场发挥。6.3 成本与延迟别让优雅架构吃掉钱包第三个层面是成本和延迟。很多Skill系统的问题不是做不出来而是太贵了或太慢了。我做了一版每个Skill的token消耗统计之后发现最耗token的不是大模型总结而是模型选择技能这一步——候选描述一多系统提示词就膨胀。针对性优化方案有两个一是给候选技能做预过滤根据用户请求里的关键词先排除掉明显不相关的技能再让模型在小范围里选二是把高频Skill做成固定路由不经过模型决策。这两招把技能选择这一步的token消耗砍掉了约60%整体响应时间也降了三分之一。7. 我在实战中踩过的坑五条值得写进备忘录的经验最后这部分是我在多个Agent项目里反复踩过的坑每一条都对应着一段真实折腾经历。写出来希望你不用再走一遍。7.1 别让Skill描述里出现内部黑话我一度在Skill描述里用了我们团队内部的缩写词比如调用DMS下发数据结果模型在选择时完全懵了因为它不知道DMS是什么。解决方案是描述里只能使用模型和用户都能理解的自然语言内部代号一律翻译成业务语言。其实这也变相检验了你的Skill是否真的面向用户任务定义如果某个Skill无法用通俗语言描述清楚说明它本身就不是一个好Skill。7.2 参数校验宁严勿松前期为了省事我让很多Skill的handler自己处理参数容错结果就是问题被埋在深处出错时报错信息完全看不懂。后来把所有参数校验统一前移到调度器用JSON Schema严格做一遍不合格直接拒绝执行并返回参数错误具体缺失项。表面上看多了一步校验实际上调试效率翻倍因为问题都在入口暴露了不用层层扒日志。7.3 所有外部依赖都要有超时和默认值Agent项目里最让人崩溃的失败模式是外部API没返回你的Skill就挂在那儿整个任务卡死。我现在给每个涉及外部调用的Skill都强制要求超时时间显式声明、失败时有明确的降级返回值、外部服务不可用时必须有静态固定答案兜底。比如查天气失败时至少返回天气服务暂不可用让上层模型有机会给出一个诚实的回答而不是编一个天气出来。7.4 记录每一次Skill调用的输入输出我在生产环境里加了一个全过程数据落盘每个Skill的输入、输出、耗时、成功与否全部写入日志存储。后来排查那些偶发问题比如某次数据不对、某次漏了一个文件全靠这些调用记录还原现场。没有这套记录模型中间的自由发挥你根本复现不了。7.5 先跑通三个Skill再谈规模化这是给项目起步的建议。不要一上来就规划二十个Skill的宏大架构先用三个核心Skill把注册-调度-执行-校验-记录这条链路完整跑通。链路通了以后每个新Skill都只是往注册中心里填空的工作难度会直线下降。我自己就吃过先铺量后补框架的亏十几个Skill散落在各种文件里最后不得不花两周重构。8. 结尾做agent-skills这套体系我最深的体会是Agent项目的稳定性不是靠调一个更聪明的模型获得的而是靠把每一个已经验证过的正确流程固化下来让模型只在真正需要判断的地方做判断。Skill就是承载这个思路的设计单元——它把流程、校验、降级、记录都打包在一个清晰模块里让Agent既灵活又可靠。最后再分享一个小技巧给Skill写测试用例时别忘了专门准备几条用户不会这么问但模型就是会这么问的输入。模型在真实使用中的表达方式远比我们预期得奇怪把这些奇怪输入提前暴露在测试集里比你上线后手忙脚乱地修Bug要划算得多。这一步我做得很晚希望你先做。