
1. agent-skills到底是什么先回答“为什么需要技能”这两年做大模型应用的人应该都有同感模型能力越来越强但真正把一个Agent落地到业务里卡点往往不在模型本身而在“Agent会不会用工具、会不会按流程干活”。你让GPT-4o去查天气、订会议室、写周报、发邮件单看每一步它都会但把它扔到一个真实工作流里它就经常表现得像个“什么都懂一点、但什么都干不利索”的新人。问题出在哪出在缺少一套结构化的、可复用、可管理的技能体系。agent-skills这个概念说白了就是给Agent建立一本“操作手册”。它不是简单的函数列表也不是一堆Prompt模板而是把Agent在特定场景下需要的能力——调用外部API、操作内部系统、处理特定格式的数据、执行多步流程——封装成标准化的技能单元让Agent知道“什么时候该用什么技能、怎么用、边界在哪”。这套东西能解决的问题非常实际不再每次都用自然语言描述“你去调一下天气接口参数是城市名称”而是注册一个名叫weather_query的技能Agent按Schema传参就行。不再让Agent自由发挥乱调工具而是通过技能编排约束它的行为路径减少不可控。不同业务线沉淀的技能可以复用新项目不用从零开始Prompt。适合看这篇文章的人也比较明确正在做Agent应用开发、被“工具调用不稳定”“Agent经常选错工具”“提示词越写越长但效果不升反降”折磨的工程师还有想系统化设计Agent能力的企业技术负责人。接下来我会把我自己从零搭建技能系统过程中沉淀的思路、代码、踩坑记录全部拆开来讲。2. 技能体系的演进逻辑与核心概念拆解2.1 把Agent技能和“插件”“工具”放在一起看很多人在第一次接触agent-skills时会问这不就是Plugin或者Tool吗我的回答是概念上有重叠但侧重点完全不同。工具Tool是原子能力比如“发送HTTP请求”“读取文件”“执行Python代码”。它强调的是“能做什么”。插件Plugin通常是工具的集合往往带有一套独立的配置和UI逻辑比如浏览器插件、IDE插件。而技能Skill在Agent的语境下强调的是一整套“做事的方法”它包含工具调用、参数约束、前置条件、输出规范、甚至失败兜底策略。举一个我实际做过的例子做一个“自动生成销售周报”的技能。底层工具只有一个call_llm和read_database但技能层需要定义清楚先查哪几张表、用什么SQL模板数据拿到后按什么维度做聚合生成周报时模型的temperature设多少、输出格式是什么如果数据异常比如某区域销售额为零周报里怎么写备注。这些内容如果全散在Agent的System Prompt里每次调整都要改Prompt而且不同场景之间互相污染。把它封装成一个Skill之后Agent面对“帮我写周报”这个请求时会先意识到“这属于weekly_sales_report技能的管辖范围”然后严格按照技能内部定义的流程执行。2.2 技能的核心属性意图、输入、流程、输出设计技能体系之前先把技能的四个核心属性想清楚。我习惯用一句话概括一个技能在什么意图下、接收什么输入、执行什么流程、产出什么结果。意图Intent技能适用的场景描述。这是给Agent做路由用的决定“什么时候该用这个技能”。比如weekly_sales_report的意图是“用户要求生成销售数据相关的周期性报告”。输入Input技能需要的外部参数必须有明确的Schema。参数名、类型、是否必填、取值范围一个都不能含糊。流程Flow技能执行时的内部步骤。可以是纯代码逻辑比如先查库再调模型也可以是让Agent一步步执行的带约束的流程描述还可以是两者的混合。输出Output技能返回结果的结构化定义。Agent拿到技能输出后是直接展示给用户还是作为下一步决策的依据在输出设计时就要定清楚。这四件事里面最容易翻车的是“意图”定义。很多初版技能系统意图写得太笼统比如“处理数据相关请求”结果Agent面对“帮我把数据导出成Excel”和“分析一下数据趋势”这两个完全不同的诉求时全往同一个技能里塞最终表现就是每个技能都不好用。2.3 为什么不能把技能逻辑全塞进System Prompt会有朋友说我直接把这些逻辑写在System Prompt里不也能让Agent按流程走吗为什么非要搞一套技能体系我自己在早期确实这么干过而且短期看效果不差。但随着技能数量增加问题集中爆发Prompt从2KB涨到8KB每次请求的token消耗暴涨响应速度明显变慢不同技能之间出现“互相干扰”Agent在处理A任务时突然记起B任务的约束行为变得不可预测调整一个技能的逻辑要动整份Prompt版本管理基本靠“另存为一个新文件”技能无法做自动化测试——你没法对一段Prompt写单元测试但你可以对技能的执行逻辑写测试用例。把技能从Prompt里抽离出来变成一个结构化的、可注册、可检索、可版本化的实体本质上是在“让模型按套路做事”和“让套路本身可维护”之间取得平衡。这也是agent-skills近两年在一些开源社区里讨论度上升的核心原因。3. 设计一份可落地的技能定义从Schema到注册3.1 技能定义的基本结构在我现在维护的项目里每个技能就是一个独立的目录里面带一份YAML格式的技能描述文件和对应的Python实现文件。以“查天气”这个最小技能为例技能定义如下name: weather_query description: 查询指定城市当前天气情况包含温度、湿度、风力、空气质量。 intent: - 用户询问某地今天/现在/未来几小时的天气 - 用户要求查看某城市的温度或降水信息 parameters: city: type: string required: true description: 城市中文名如“北京”“上海”不支持英文。 date: type: string required: false description: 查询日期格式YYYY-MM-DD默认当天。 flow: - step: 校验城市名称是否在支持列表中 - step: 调用第三方天气API获取数据 - step: 按固定模板整理为结构化文本 output: format: json fields: - name: city type: string - name: temperature type: number - name: humidity type: number - name: wind type: string - name: aqi type: number error_strategy: - condition: 城市不在支持列表 action: 返回明确错误信息提示支持的输入范围 - condition: 第三方API超时 action: 重试一次间隔3秒这里面有几个关键细节description字段决定了Agent能不能正确路由。建议写得“像用户会说的话”而不是像API文档。如果你写成“获取天气数据的函数接口”大模型在意图匹配时经常会漏判写成“用户询问某地今天/现在/未来几小时的天气”匹配率会明显提升。这个细节我对比测试过同样的模型、同样的技能数量描述风格改成用户口吻之后正确路由率从71%提升到了89%。parameters必须做严格限制。很多技能出问题不是执行逻辑错了而是Agent传了一堆非法参数进来。比如城市名传了“BeiJing”日期传了“明天”如果没有校验后面流程全崩。所以参数定义里把format约束写清楚同时在flow里加校验步骤双保险。3.2 技能注册表让Agent知道“你会什么”技能定义好之后还要有一个地方让Agent“看见”这些技能。这就像新员工入职得先把岗位职责录进系统否则你再有能力也没人知道找谁派活。我用的方案是维护一份集中式的技能注册表启动时全部加载进上下文。注册表本质上是一份汇总了所有技能名称、描述、参数Schema的索引文件SKILL_REGISTRY { weather_query: { description: 查询指定城市当前天气情况包含温度、湿度、风力、空气质量。, parameters_schema: { city: {type: string, required: True, description: 城市中文名}, date: {type: string, required: False, description: 查询日期} }, handler: WeatherSkill(), version: 1.2.0, }, # ... 其他技能 }Agent每次收到用户请求时先用意图匹配模块去扫描注册表筛选出Top-3可能相关的技能然后把候选技能的定义注入上下文让大模型做最终选择。这一步非常关键——不是把所有技能全塞给模型那会重蹈Prompt过长的覆辙而是做一次粗筛。粗筛匹配我用的是Embedding相似度加规则兜底用户请求先转成向量与每个技能的意图描述算余弦相似度同时用一组正则规则覆盖高频明确触发词比如出现“天气”直接锁死weather_query。这两个结果做加权合并。实测下来粗筛召回率能到95%以上最终模型在3个候选项里选对的概率也稳定在92%左右。3.3 技能实现层把流程写清楚比“让模型自由发挥”可靠技能的执行逻辑我分成两类来设计对应不同可靠度需求。第一类是确定性流程逻辑固定不需要模型参与决策。比如“查天气”“查数据库”“调内部API”全部走纯代码实现。这类技能要的就是稳定100次调用100次同结果。第三方的Function Calling能力其实就覆盖这一层。第二类是半自主流程内部包含多个待决策节点。比如“生成销售周报”这个技能查哪些表是固定的但数据出来后周报的措辞、重点提炼、异常说明都需要模型现场生成。我的做法是把流程框架写死在技能里把模型“自由发挥”的范围框在特定节点内def run(self, params): # 1. 从配置中读取固定SQL模板不允许模型改动 sql build_sql(sales_weekly, params.get(date)) data self.db.query(sql) # 2. 数据校验异常走预设逻辑 anomalies detect_anomalies(data) # 3. 调用模型生成周报只允许模型处理“措辞层”的决策 report self.llm.generate( prompt_template根据以下销售数据生成周报重点突出异常和趋势变化, datadata, anomaliesanomalies, temperature0.3 # 固定低温减少随机性 ) # 4. 输出格式清洗 return format_report(report)这里的关键是模型的角色被定位成“按照给定框架内容做表达”、而不是“决定流程怎么走”。我自己在无数次测试里验证过一件事——让模型凭感觉决定“先做什么后做什么”的任务失败率远高于把步骤固定下来只让模型填内容的方案。技能的“流程感”就应该体现在实现代码里而不是体现为Prompt里的“请你一步步分析”。4. 手把手搭一个agent-skills最小系统理论说太多容易飘直接上整个可运行的骨架。下面的结构来自我自己项目的精简版去掉业务依赖后剩一个最小闭环技能定义、技能注册、意图匹配、调用执行。4.1 项目结构my_agent/ ├── skills/ │ ├── __init__.py │ ├── weather_query/ │ │ ├── skill.yaml │ │ └── handler.py │ └── weekly_report/ │ ├── skill.yaml │ └── handler.py ├── registry.py ├── router.py ├── agent.py └── main.py目录结构的设计原则是一个技能一个文件夹。好处很直接加新技能不用改动现有代码把文件夹丢进去、在注册表里加一行就完事出问题时定位也快逻辑都在自己的handler里不会跟别的技能纠缠。4.2 技能处理器基类先定义一个抽象基类让所有技能遵循同一接口# skills/base.py from abc import ABC, abstractmethod class BaseSkill(ABC): name: str version: str 1.0.0 abstractmethod def run(self, params: dict) - dict: 执行技能逻辑返回结构化结果 pass def validate(self, params: dict) - dict: 参数校验返回清洗后的合法参数或抛异常 return params每个具体技能的handler继承这个基类。注意validate和run分离这是我在生产环境里撞了几次墙之后补上的设计——没有参数校验层Agent乱传参数导致的脏数据会一直污染到下游而且错误提示极其难查。4.3 参数解析与校验器参数校验这块我直接用JSON Schema的思路做了一层轻量封装不引入额外重框架# skills/validators.py import json def validate_with_schema(params, schema): from jsonschema import validate, ValidationError try: validate(instanceparams, schemaschema) return True, params except ValidationError as e: return False, str(e)在技能定义里加一行schema引用就好schema: schemas/weather_query_schema.json实际执行时Agent给出的原始参数先过schema校验不合格就返回“参数错误期望格式说明”。这样Agent下轮生成时就能自我修正参数实测能把一次调用成功率提升一大截。很多初版系统忽略了这步把出错责任全推给“模型不听话”其实模型往往是被脏参数害的。4.4 实现意图路由路由是整个技能系统里我反复调整最多的部分。早期用过特别复杂的方案意图识别模型单独训练、意图树、多轮对话状态跟踪。后来发现在技能数量没超过30个时一个轻量匹配器就够了。我的路由函数核心逻辑很简单# router.py import numpy as np from sentence_transformers import SentenceTransformer model SentenceTransformer(paraphrase-multilingual-MiniLM-L12-v2) def retrieve_skills(query, registry, top_k3): query_vec model.encode(query) scores [] for name, skill in registry.items(): desc_vec model.encode(skill[description]) scores.append((name, float(query_vec desc_vec))) scores.sort(keylambda x: x[1], reverseTrue) return [name for name, _ in scores[:top_k]]注意这里用的是向量点积而不是余弦距离因为预训练句向量批量做点积的速度更快且模型本身已经做过归一化。top_k我固定为3太低会漏召回太高会把无关技能塞给模型造成误选。4.5 Agent主循环最后是Agent调用主逻辑把请求、路由、执行串起来# agent.py class Agent: def __init__(self, registry): self.registry registry def handle(self, user_query): # 1. 粗筛可能相关的技能 candidates retrieve_skills(user_query, self.registry) # 2. 让大模型做最终选择只看到候选技能 selection_prompt f 用户请求{user_query} 可选技能{candidates} 返回你选择的技能名以及按Schema填好的参数JSON。 只输出JSON不要额外解释。 llm_result call_llm(selection_prompt, temperature0) skill_name, params parse_llm_json(llm_result) # 3. 校验参数并执行 skill self.registry[skill_name][handler] cleaned skill.validate(params) result skill.run(cleaned) # 4. 把结果组织成最终回复 return self._compose_reply(user_query, skill_name, result)从代码可以看出来整个Agent的核心编排并不复杂复杂在每个技能内部的实现细节。骨架本身越简单越好让复杂度都收敛在技能层内是我这一年多调Agent最深的体会。5. 技能设计的三条铁律与常见坑5.1 粒度原则一个技能只做一件完整的事“完整的事”怎么定义我的判断标准是用户的一句话能不能直接触发它且完成后用户不用再补一句“然后呢”。比如“查天气”是一个好技能因为用户说“北京今天什么天气”就能直接触发结果也闭环。“分析天气数据并生成出行建议”在这个标准下就太粗它不是一句话说得清的任务中间应该拆成“查天气”“生成建议”两个技能。反过来同样要避免把一个技能拆得过于原子化。我有一次把“发送邮件”拆成“获取收件人列表”“检查邮件正文敏感词”“调用SMTP发信”三个技能结果Agent经常漏掉中间某个技能发出去的邮件没做敏感词检查。技能粒度对系统整体准确率的影响非常直接。5.2 容错设计每个技能都要想好“出错了怎么办”我见过太多的技能实现只写了happy pathAgent一遇到异常情况就整个流程崩掉。技能设计阶段就要把出错路径写清楚我习惯在每个技能定义里增加error_strategy字段参数非法返回明确的错误提示让Agent直接转述给用户还是自动用默认值重试外部服务超时重试几次间隔多久数据为空直接说“暂无数据”还是继续执行下游逻辑这套设计对Agent实际体验的影响极大。用户面对“对不起出错了”和面对“目前上海的空气质量数据暂时没有更新你可以稍后再问我或者查看北京的数据”后者才像一个能用的产品。5.3 我用过的技能系统落地场景参考我自己在不同项目里应用过这个体系举两个对比明显的场景一个是企业内部工单助手。技能库包括“查历史工单”“建新工单”“催办”“改优先级”“查SLA”等12个技能。每个技能的流程固定、权限边界清楚Agent只要不出错地路由和填参就行。上线之后人工二次处理率降低了40%效果立竿见影。另一个是开放域知识问答机器人。一开始恨不得塞50个技能进去什么“查百科”“查新闻”“翻译”“摘要”全堆上。结果路由准确率掉到6成多技能之间频繁互抢。后来砍到12个核心技能每个技能的作用域收窄准确率回到了90%以上。这两个场景的共同教训是技能系统是“少而精”的架构不是“多而全”的资源堆砌。新增技能前先问自己这个技能是不是覆盖了高频率、高价值的场景如果只是偶发需求让Agent直接基于通用能力处理可能更稳。5.4 常见问题速查表问题现象排查方向解决方案Agent频繁选错技能技能描述与用户表达习惯不匹配把description改成用户口吻增加同义触发词参数传错、格式反复参数Schema不够严格增加jsonschema校验返回错误时附期望格式示例技能之间互相抢活意图定义重叠收窄每个技能的intent范围必要时合并技能流程步骤被模型跳过流程约束太弱把必须步骤移入确定性代码模型只剩填槽新技能上线后老技能变差注册表索引互相干扰粗筛时增加重叠检测确保候选列表不包含功能重复项响应变慢、token消耗变大候选技能塞得太多top_k从5减到3技能描述精简到一句话5.5 一个最容易被忽视的问题技能版本与实验对比你会给代码做版本管理但技能描述的改动往往被忽略——一个description改了可能让路由准确率浮动5到10个百分点。我现在把每个技能的description、intent、参数Schema统一纳入Git管理每次改动都配套记录一版路由准确率测试结果。没有这个习惯之前经常出现“这次发布后效果变差了但找不到是谁改的”的尴尬。如果你要快速验证一个新技能的效果我建议不要边调边上线而是先跑一周的离线回放拿真实用户请求的历史日志跑一遍旧技能系统和新技能系统对比路由准确率、执行成功率、用户最终满意率几个指标。这一步能筛掉大部分不成熟的技能设计。6. 从demo到生产技能治理需要补齐的几块拼图6.1 技能可观测性技能上线后如果只能看到“Agent调用了某个技能然后返回了结果”这对调试毫无帮助。我给每个技能的执行加了一层轻量级的结构化日志{ event: skill_invocation, skill_name: weather_query, params: {city: 北京}, latency_ms: 230, status: success, model_used: gpt-4o-mini, user_query: 北京天气怎么样 }日志进入统一的查询管道里后续可以按技能名、返回状态、参数值做检索能很快定位是哪一个环节出问题。这一步在demo阶段可以不做但技能一多、人一多就必须上不然排查问题的成本会吞掉技能化带来的效率红利。6.2 技能质量评估不要只看“调通没有”给技能做评估时除了调通率我更在意三件事准确率输出结果与正确结果的一致性。天气技能的“正确结果”是API返回数据本身这个可以直接比对生成类技能的“准确”需要标注集成本高一些。兜底成功率遇到异常场景时有没有按预设策略处理。这里我建了一组异常测试集专门塞非法参数、空数据、第三方超时这些边角情况。用户反馈最简单也最容易被忽略的在Agent回复末尾加一个隐式的交互按钮“这个结果有用吗”用户反馈数据直接回流到技能评估看板里。有些技能日志看起来调通了百分之九十几但用户满意度只有六成这时候就要回去看输出内容是不是“正确但没用”。6.3 技能的生命周期管理技能也有生命周期试用、发布、下架、替换。没有这套管理技能库会慢慢腐化。我把项目里的技能状态都显式标注experimental刚开发只对内部测试开放。stable已经跑通评估阈值可以使用。deprecated即将被替代不再接受新的路由。这个状态字段不只是一个文档标记而是会参与路由逻辑的处于deprecated状态的技能直接从检索结果里排除避免Agent选到一个马上要下线的技能。从一个人的demo到一个多团队协作的平台差距不在Agent选型、不在Prompt技巧而在于这套围绕技能的工程化治理能力。这也是agent-skills这个方向最容易被忽视、却最能拉开差距的部分。我个人在实际项目里体会最深的还是那句老话Agent是骨架技能是血肉。模型再怎么聪明没有一套精心设计、严格管理的技能系统托底落地时都会变成一地鸡毛。反过来说只要技能体系设计得清晰、克制、可观测哪怕你用的基础模型不是最顶级的最终效果也足够稳定可用。这是我踩了一年多坑之后最想分享给你的一句话。