
聊到“agent-skills”这个词熟悉LLM应用开发的朋友应该不陌生。这两年Agent的热度一直没降但真正把产品落地的人心里都清楚Agent能不能稳定干活90%取决于你喂给它的那套“技能”。不是模型能力不行而是你压根没把这些能力组织成它能顺畅调用的形状。我最早做Agent项目的时候走了很长一段弯路。当时把一堆函数塞进工具列表模型调用起来经常出错参数乱填、调错接口、链路上的状态也理不清。后来静下心重构参考了社区里几个成熟的开源框架才慢慢摸清“技能”这层抽象的真正价值。我准备把这几年围绕Agent技能系统的设计思路、实操细节和踩坑记录整理出来。这篇文章不只讲概念会把技能到底怎么定义、怎么注册、怎么编排、怎么排查问题讲透。适合正在搭Agent框架的开发者也适合想把自己项目里的工具调用整理规范的产品和技术同学。内容比较多但每一条都是带过线上真实流量的经验照着做能少走不少弯路。1. 先搞清楚一件事技能不是工具也不是提示词模板很多团队对“技能”的理解停留在表面。有人觉得技能就是工具函数的集合有人觉得技能是写好的一段提示词还有人认为技能是Agent链路上的一个节点。这些理解都对了一半但全都漏掉了最关键的中间层。1.1 技能的解剖结构接口、逻辑与状态我给技能下的定义是面向任务的能力封装单元。它有三个组成部分缺一不可接口技能对外暴露的调用面包括技能名、描述、参数Schema、返回格式。逻辑技能内部实际执行的代码或流程可能是调用一个API、执行一段Python、查询数据库也可能是编排多个子技能。状态技能执行过程中涉及的数据上下文包括当前会话状态、中间产物、资源句柄等。用一个生活化的类比一个技能就像一台“自动售货机”。用户投币传入参数按下某个按钮调用某个操作机器内部完成出货执行逻辑最后吐出商品返回结果。Agent就是那个拿着硬币逛商店的人它不需要知道售货机内部的齿轮怎么转只需要知道“这个按钮能买到可乐”。这套抽象最大的价值在于把“能力”和“实现”彻底分离。模型侧只需要理解接口业务侧只需要维护实现两者各自演进互不拖累。1.2 为什么“技能”这一层如此重要先看看如果没有技能层直接堆工具函数会发生什么。原始的工具调用方案里开发者会把所有能力都注册成扁平函数比如get_weather、send_email、create_task。初期工具少这没问题。等工具超过20个、50个灾难就来了模型面对几十个函数选择困难路由准确率直线下降函数之间没有归属关系语义相近的能力无法聚合——比如“发送邮件”可能散落在三个不同的函数里上下文中塞满了几十个JSON Schematoken消耗巨大指令遵循能力被稀释没有版本概念改一个内部实现可能导致所有依赖它的流程同时挂掉。技能层恰好解决了这些问题。技能像是一层“编目”把散落的能力装订成有章节的工具书Agent不再逐条翻字典而是先翻目录再翻到对应章节调用。这大幅降低了路由难度也减少了上下文负担。1.3 技能演进的三阶段观察不同团队的Agent项目技能系统通常要经历三个演进阶段第一阶段函数直调。没有技能概念全是扁平工具。适合Demo不适合生产。第二阶段技能封装。把相关能力归并成技能技能内部可能包含多个操作。比如一个“日程管理”技能内部提供“创建日程”“查询日程”“取消日程”三个操作。此阶段已经能支撑复杂业务。第三阶段技能编排。技能之间存在依赖和组合关系Agent可以根据任务拆解动态编排多个技能协作完成目标。这是目前多数团队正在努力的方向技能从“被调用”变成了“可参与决策”。判断自己团队在哪个阶段有个简单标准当你开始在技能命名上费心时说明你至少到了第二阶段。2. 技能设计的核心方法论从接口到描述技术实现从来不是技能系统的瓶颈设计才是。这里说的设计包括参数Schema、技能描述、命名规范和粒度划分。每一项做不好后续都是连锁灾难。2.1 接口设计参数Schema是契约技能的参数Schema是整个系统里最重要的静态资产它定义了模型和代码之间的契约。我见过太多团队随手写参数名最后模型理解不了或者开发自己都忘了某个字段是干嘛的。几个关键原则参数名语义化用natural_language_query而不用q用start_time而不用st。限制枚举值凡是取值有限的字段必须给出枚举别让模型自由发挥。必填参数最小化只把真正必要的设成必填其余全部可选降低模型的填充负担。提供示例值每个参数都配上示例模型参考示例填参准确率能飙升不少。一个典型的技能接口应该是这样{ skill_name: schedule_booking, operation: create_meeting, description: 创建一条新的日程安排支持设置时间、参与人和会议地点, parameters: { type: object, properties: { title: { type: string, description: 会议标题例如产品周会、需求评审, examples: [产品周会] }, start_time: { type: string, format: date-time, description: 会议开始时间ISO 8601格式例如 2025-06-01T10:00:00, examples: [2025-06-01T10:00:00] }, end_time: { type: string, format: date-time, description: 会议结束时间ISO 8601格式必须晚于开始时间, examples: [2025-06-01T11:00:00] }, participants: { type: array, items: { type: string }, description: 参与人邮箱列表可为空, examples: [[zhangsancorp.com]] } }, required: [title, start_time, end_time] } }这份Schema再配上模型基本能保证日程创建类请求的稳定执行。如果你发现模型经常填错某个参数优先检查这个参数的描述够不够清晰、示例够不够典型。2.2 描述即路由技能描述决定模型能不能找到你模型选择哪个技能最大的依据是技能描述。很多人把这一项随便写两句结果就是模型根本不知道这个技能能干什么。技能描述不是给程序员看的是给模型看的。我的经验是技能描述需要包含四类信息技能的职责边界这个技能干什么不干什么。适用场景什么样的用户请求应该调用我。使用约束调用前必须满足什么前置条件调用后有什么副作用。关联提示如果用户的需求不在本技能范围内应该引导到哪里。举个例子一个“会议室查询”技能的描述当用户需要查询可用会议室、查看会议室设备配置或预约空闲时段时使用本技能。支持按建筑、楼层、容量和是否配备投影仪筛选。本技能仅用于查询和展示信息不执行预约。如果用户明确要求预订某个会议室引导用户确认信息后调用schedule_booking技能。这段描述的核心作用是把路由边界讲清楚减少模型误调用的概率。你给模型一个模糊的描述它就只能给你模糊的路由结果。2.3 粒度控制拆得太碎和揉得太整都是病技能粒度是设计环节里最凭经验的部分。拆太细技能数量爆炸路由困难揉太整技能内部逻辑过于庞大模型又搞不清具体走哪条分支。我建议按“业务操作”作为基本划分单位。一个技能就是一个可独立完成的业务操作闭环技能内部可以有多个细分动作但对外应该呈现一个完整能力。举个例子“邮件处理”技能内部可以包含“收件箱摘要”“邮件搜索”“发送回复”“邮件分类”。这些操作共用一套鉴权和邮箱连接状态但它们都是独立闭环应该在一个技能下作为多个operation存在而不是拆成四个独立技能。实际操作里我判断粒度的标准很简单如果两个操作必须共享同一组状态或资源就把它们归到一个技能里如果两个操作完全独立各自维护各自的资源就拆成两个技能。2.4 命名与注册最容易烂却最影响命名的环节技能命名直接影响模型的路由效果但国内团队往往不重视习惯用什么get_data_utils、func_processor这类毫无语感的命名。模型不是编译器它没耐心揣摩你的缩写。技能名用英文但一定要清晰order_management好过order_mgmtcustomer_support好过cs_support。在技能名里加上领域前缀避免重名冲突crm_contact_search、erp_invoice_check。注册时做冲突检测两个技能名完全一样要报警。允许技能有别名尤其是用户口语化表达的对应关系。比如schedule_booking的别名可以是meeting_arrange、calendar_add。技能体系一旦上线命名调整的代价极高。因为已有的会话上下文可能会缓存旧名称模型日志里的路由记录也都基于旧名你很难精确评估改动的影响范围。所以命名这种事宁可在上线前多花一天讨论也不要上线后再改。3. 实操从零构建一套可用的Agent技能系统理论说多了容易飘直接落地。以一个通用开源框架为例演示技能系统的搭建路径。下面的代码和目录结构可以直接拿到自己项目里改。3.1 最小可用的技能目录结构技能不应该散落在业务代码里必须有一个统一的组织形态。推荐这样的目录skills/ __init__.py registry.py # 技能注册中心 base.py # 技能基类 schedule_booking/ # 日程管理技能 __init__.py schema.json # 技能接口定义 executor.py # 技能执行逻辑 email_processor/ # 邮件处理技能 __init__.py schema.json executor.py每个技能模块自带schema和executorschema是给模型看的executor是给代码跑的。技能模块之间不互相引用所有依赖通过注册中心注入。这样每一个技能都能独立测试、独立部署、独立回滚。3.2 技能基类定义技能基类约束了每个技能必须实现的关键方法避免团队成员各自天马行空from abc import ABC, abstractmethod from typing import Any, Dict class BaseSkill(ABC): 技能基类所有技能必须实现validate、execute、describe三个方法 skill_name: str skill_version: str 1.0.0 abstractmethod def validate(self, params: Dict[str, Any]) - Dict[str, Any]: 参数校验不通过的返回错误信息并建议修正 pass abstractmethod def execute(self, params: Dict[str, Any], context: Dict[str, Any]) - Dict[str, Any]: 技能执行主逻辑context中注入会话级上下文 pass classmethod def describe(cls) - Dict[str, Any]: 读取schema.json向模型返回接口描述 passvalidate和execute分离是核心设计。模型填的参数经常是不完整的先校验再执行顺便在失败时给出模型可读的错误提示让模型自行修正参数反复几次就能从回调成功率上看到明显提升。3.3 技能注册中心注册中心是技能系统的枢纽。它维护一张“技能路由表”把技能名映射到具体的执行器实例同时给上层提供能力发现接口。一个高度简化的版本class SkillRegistry: def __init__(self): self._skills: Dict[str, BaseSkill] {} self._aliases: Dict[str, str] {} def register(self, skill: BaseSkill, aliases: list[str] None): self._skills[skill.skill_name] skill for alias in aliases or []: self._aliases[alias] skill.skill_name def resolve(self, skill_name_or_alias: str) - BaseSkill: if skill_name_or_alias in self._aliases: skill_name_or_alias self._aliases[skill_name_or_alias] return self._skills.get(skill_name_or_alias) def list_skills(self) - list[Dict[str, Any]]: return [skill.describe() for skill in self._skills.values()]启动的时候把所有技能模块加载进来然后注册到中心。之后要加新技能新增模块、注册两步搞定完全不动主流程代码。from skills.schedule_booking import ScheduleBookingSkill from skills.email_processor import EmailProcessorSkill registry SkillRegistry() registry.register(ScheduleBookingSkill(), aliases[meeting_arrange, calendar_add]) registry.register(EmailProcessorSkill(), aliases[email_reply, mail_search])3.4 技能执行链路与异常处理技能执行的完整链路不是execute函数内部的事它涉及调用前后的所有保障。我给每个技能套了一层层中间件参数补全模型传参不完整时根据上下文自动填充默认值。超时控制技能执行超过指定时间直接中断并返回用户友好的提示。失败重试对网络类、瞬时性错误自动重试一到两次但不可盲目重试写操作。执行日志每个技能的入参、出参、耗时、错误都记录后续做评测和优化全靠这批日志。执行器入口的伪代码def run_skill(skill_name: str, params: Dict[str, Any], context: Dict[str, Any]) - Dict[str, Any]: skill registry.resolve(skill_name) if not skill: return {status: error, message: f技能 {skill_name} 不存在} # 参数校验 validated, errors skill.validate(params) if errors: return {status: error, model_action_required: True, errors: errors} # 带超时执行 try: result execute_with_timeout(skill.execute, validated, context, timeout10) return {status: ok, skill_name: skill_name, result: result} except TimeoutError: return {status: error, message: 技能执行超时请稍后重试或提供更精确的条件} except Exception as e: log_error(skill_name, params, e) return {status: error, message: f技能执行失败: {str(e)}}需要特别说明的是失败信息里带上model_action_required: true等于告诉Agent“这次失败是参数问题你可以修正后重试”。这个标记极大提升了模型的自纠错能力我在实际项目里对比过开启这个机制后终态成功率提升大约12个百分点。4. 多Agent协作下的技能编排让技能不再单打独斗当系统里只有一个Agent和几个技能时技能设计再糟糕也出不了大乱子。真实生产环境往往是多个Agent并存各自负责不同职责技能之间还有调用关系。这时候技能编排就成了新的复杂度来源。4.1 路由决策主Agent到底该不该做技能编排主Agent做全局路由在意图识别阶段就要判断任务是单一技能能解决的还是需要多个技能协作我的经验是给主Agent一份“编排提示”明确定义两类情况单技能直达用户请求清晰正好命中某个技能职责边界直接调用不引入额外环节。多技能编排请求包含多个子任务或者主Agent发现单次调用无法闭环需要拆解步骤。举例用户说“帮我订明天下午三点的会议室然后给参会人发一封日程确认邮件”。这明显是两个操作会议室查询预订、发送邮件。主Agent需要拆成两个子任务先后调度两个技能。4.2 技能组合的执行模型多技能组合有两种典型模型顺序执行和并行执行。顺序执行场景常见于有依赖关系的技能链路。比如“要做一张销售报表”第一步从数据仓库提取数据第二步根据数据生成图表第三步把图表嵌入PPT模板。每一步的输出是下一步的输入并行执行毫无意义必须串行。并行执行场景更常见于信息聚合类需求。比如“汇总一下今天的天气、日程和交通信息”三个技能之间毫无依赖同时发起三个调用等齐结果后统一汇总整体响应时间从三者之和缩减到最慢的一项。在实际实现上我不建议自己写多线程调度逻辑直接用asyncio.gather这类原生并发能力就够了。但要特别注意并行执行只对纯查询类技能开启涉及写入或状态变更的技能一律串行否则并发写会把数据搞乱。4.3 编排层要记的账追踪一次任务的完整链路技能编排引入一个老问题当任务失败时到底失败在哪一步我推荐引入任务追踪IDtrace_id在任务入口生成一个唯一ID整个编排过程中的每个技能调用都带上它。日志系统按trace_id归类一旦任务失败顺着链路把每一步的入参、出参、耗时拉出来看几分钟就能定位问题环节。这个trace_id同样可以用来做成本分析。Agent任务消耗的token、API调用次数、耗时全部归因到一个任务ID上月底复盘时直接按链路统计“哪个技能最贵、哪个技能成功率最低、哪个技能最拖时间”给后续优化提供精确的数据支撑。4.4 多Agent协作的权限边界技能编排必然涉及权限问题。当主Agent把子任务分配给其他Agent技能的执行权限跟着谁走我实践下来最稳的方案是技能权限不跟随Agent跟随用户会话。也就是说用户在主会话里发起的请求无论最终由哪个Agent执行哪个技能权限校验都以当前用户的权限为准。绝对不能让技能系统形成一个“高权限通道”否则用户A发了请求被编排到了用户B的Agent上下文就可能越权访问。技术上实现不复杂把用户身份放进context上下文里每个技能执行前从context里取身份做权限校验。这块省事后面出事就是大事。5. 我踩过的坑Agent技能系统面向生产环境的六个典型问题技能系统搭起来容易服务稳定难。这些问题基本上每个团队都会遇到提前看到能省不少排障时间。5.1 技能膨胀与命名冲突这是Agent项目长大后的第一道坎。技能数量超过50个模型的选择准确率显著下降技能名冲突逐渐暴露。我的应对措施是定期整理“技能路由日志”看看哪些技能长期没被调用哪些技能频繁被调用。三个月内零调用的技能进入冻结状态先从注册中心移除不进模型上下文。活跃技能保留冷技能归档。这个机制我习惯叫“技能剪枝”效果立竿见影模型路由准确率能回升一大截。5.2 上下文污染与技能描述超载所有技能的描述加起来不要超过上下文可视范围的三分之一。技能太多时模型根本看不过来而且互相干扰。解决办法是分组。按领域把技能分成组模型先路由到技能组再在组内选择具体技能。比如“办公协作”组包含日程、邮件、会议、待办“数据分析”组包含查询、报表、可视化。上下文里只展示当前相关组的技能描述把其他组排除在外。5.3 幻觉参数模型填了明摆着不存在的值模型在参数填充上最典型的问题有三个日期格式错2025/06/01写成20250601、枚举值乱编传入根本不存在的会议室类型、数字单位错容量50当作50人而不是50%。我在参数校验层加了一个“语义纠偏”模块针对高频出错字段做规则校验自动修正。比如时间格式解析不了就尝试常见的几种替代格式枚举值非法就查找相近映射。能自动修正的不要报错返回实在修正不了的再丢给模型调整。这一个处理方式让技能的一次性执行成功率提升了至少20%强烈建议各位做类似兜底。5.4 僵尸技能悄无声息地烂掉技能内部依赖的第三方API可能悄悄发生变化上游接口字段改了技能仍然注册在册执行却持续失败。这个过程经常没人发现因为没人会主动去测一个冷门技能。建议建立一个定时巡检机制对核心技能做健康检查。每周跑一次冒烟测试对每个技能的最短路径执行一遍最小量级的真实调用把成功率报表按时推给负责人。另外技能要打版本号内部实现变更后升版本旧版本保留一段时间用于异常回滚。5.5 日志缺失导致排障全靠猜很多团队技能执行日志只有一行print连入参都没打全。出了问题根本无从下手。我要求的日志规范至少四行请求头trace_id、技能名、操作名、用户ID、入参完整JSON、出参完整JSON超长截断、错误信息含堆栈。这四行日志组装好几乎能还原所有异常现场。日志的价值怎么强调都不为过没有日志的技能系统就是盲人开车。5.6 串行执行拖累整体响应多个独立技能逐个调用总耗时等于各技能响应时间之和。用户对着一个Agent等待十几秒体验极其糟糕。处理办法是前文提到的并行分组。先把技能里可并发的查询类操作挑出来在编排层面做并发分组把总时长压到最短的那条路径上。同时给高频技能做缓存比如会议室状态、公共数据查询这类短时有效但频繁使用的能力加一层10秒到30秒的缓存效果非常明显。6. 技能系统的评估、迭代与团队协作机制技术实现到位之后真正拉开团队差距的是持续运营能力。技能系统不是上线就完事它需要一套评估与迭代的闭环。6.1 建一套技能评测集技能系统的核心指标是“模型正确选择技能并成功执行闭环”的比率。这个指标不能靠拍脑袋得靠评测集。评测集可以这样建从过去的真实用户对话中采样把那些“技能正确调用且结果正确”的样本收编成正样本把“技能调用错误”的样本收编成负样本。这些样本平时可以用来做回归测试每当技能定义有改动就把评测集跑一遍看正样本的通过率有没有下降。规模上初期每技能收20到50条评测用例就够用。技能越多评测集越要持续累积半年后你就是坐拥几千条黄金用例的团队模型层或者技能层的任何改动都敢放手去改。6.2 灰度发布与技能回滚技能变更是线上风险的高发区。不要直接全局替换建议做灰度先在5%的流量里运行新技能版本观察成功率、耗时和用户反馈确认稳定后再逐步放开。回滚策略需要在注册中心里就设计好。每个技能实例保留上一个版本的执行器一旦灰度期间发现异常把路由切回旧版本秒级生效不需要重新发布代码。这个回滚能力是生产环境的基本要求讲究点的话还可以做成自动回滚——指标跌过阈值系统自动切换。6.3 技能维护的团队协作模式技能系统的长期健康依赖清晰的职责分工。每个技能必须指定一个明确的owner负责该技能的接口、实现、评测集和线上指标。Owner可能是个人也可能是两三个人组成的小组但绝不能出现“谁都碰过、谁都不负责”的状态。建议团队建立一个技能变更卡点流程任何技能改动都需要更新schema版本号、评测集和日志导出格式。这看起来增加了一丁点流程成本但相比线上出事后稳定半小时才能定位这点流程投入太划算。还有一个小实践定期组织“技能评审会”让各技能Owner把近期的路由成功率、用户反馈、评测集变化拿出来过一遍统一讨论是否要调整技能边界、合并技能或拆分技能。这种周期性回顾让技能体系能持续演进而不是上线后慢慢腐化。根据我自己的项目经验技能系统的建设成果不是以“上了多少个技能”衡量的而是看“模型真正把这些技能用对了多少次”。与其铺一堆花哨但调不准的能力不如把二十个核心技能做到高成功率、高稳定性。先把那套基础架构打好——统一的注册中心、清晰的接口标准、强力的日志体系、科学的评测闭环技能数量再多也不会乱。最后给一个小建议动手做技能系统前先花一周时间把已有工具按“技能”重新审视一遍哪些能力该合并、哪些应该拆开、给每个技能写一段真正能引导模型路由的描述这比急着堆新代码重要太多了。