
别再做一堆没用的工具函数了Agent技能体系应该这样设计。聊一聊我最近在梳理的 agent-skills 这个事直接说几个核心观点Agent的能力上限其实不取决于模型本身有多聪明而取决于你给它配了什么样的技能。一套设计良好的技能体系能让一个普通模型做出惊艳的效果反之技能定义得一塌糊涂再强的模型也会表现得像个只会说废话的呆子。我在实际项目中踩过不少坑一开始也是把各种工具函数堆在一起结果Agent经常调用错函数、参数传错、上下文混乱后来重新整理了技能体系效果提升非常明显。这篇文章就把我在设计和落地 agent-skills 技能体系时的完整思路、操作方法、踩坑记录都分享出来希望能给正在做Agent开发的你一些参考。1. 整体设计思路拆解为什么Agent需要一套“技能体系”1.1 从“工具函数”到“技能体系”的思维转变很多人做Agent应用时第一反应是给模型准备一堆函数。比如要做一个文档助手就写个read_file、write_file、search_keyword、summarize_text……然后一股脑塞给模型。这种做法在Demo阶段没问题但一旦场景稍微复杂问题就全冒出来了。我做过一个模拟项目X是个企业内部的知识库问答助手。最早版本挂了30多个工具函数模型经常选错工具有时候明明该调用搜索它偏偏去读文件有时候两个函数功能重叠模型不知道该选哪个。调试过程极其痛苦后来我才意识到问题不在模型而在“工具”这个概念太底层了。工具是机械的、单一的而技能应该是场景化的、体系化的。技能 工具 场景 约束条件 输入输出规范。它不是一个孤立的函数而是一整套可以被模型理解、调用、协作的完整能力单元。打个比方工具像是厨房里的一把刀、一个锅、一块砧板而技能则是一道菜的完整做法什么时候用刀、什么时候开火、什么时候下料这些决策本身已经内置在技能里。你给厨师的不是一堆散乱的厨具而是一本写清楚“怎么做出一道菜”的菜谱这样厨师才能稳定复现好吃的结果。1.2 技能体系设计的三条核心原则在重构 agent-skills 时我给自己定了三条原则第一技能即接口。每个技能必须有清晰、唯一、无歧义的接口定义包括技能名称、功能描述、参数Schema、返回值规范。模型通过接口理解技能接口定义的质量直接决定模型能否正确调用。第二技能可组合。单一技能解决单一问题复杂场景通过多个技能的组合来完成。就像积木一样小技能可以搭出大能力。比如“生成周报”这个技能可以组合“获取本周工作记录”、“分析完成度”、“生成报告”三个子技能。第三技能可观测。每个技能的执行过程、调用参数、返回结果、耗时、错误信息都应有日志记录这样才能在模型调用出错时快速定位问题。我见过太多团队在Agent出错时根本不知道是模型选错了技能还是技能本身逻辑有bug就是因为没有可观测性设计。1.3 技能体系的分层架构在设计 agent-skills 时我把技能体系分成四层底层是原子技能对应单一、不可再拆分的操作比如“读取文件”、“执行SQL查询”、“调用外部API”第二层是场景技能在原子技能基础上封装特定业务场景比如“查找相似文档”、“提取合同关键条款”第三层是编排技能负责组合调度多个场景技能比如“生成项目总结报告”会依次调用“获取项目数据”、“分析进度风险”、“生成报告文本”顶层是策略层定义Agent在什么业务场景下优先采用哪种技能组合以及技能执行失败后的降级策略。这个分层的价值在于底层技能稳定通用可以在不同项目中复用场景技能贴近业务是团队积累的核心资产编排技能和策略层则让Agent具备了处理复杂任务的能力。我目前维护的 agent-skills 项目就是用这个架构实践证明比平铺一堆工具函数要清晰得多。2. 核心模块拆解技能注册、技能描述、参数Schema与调度引擎2.1 技能注册中心一切技能的统一入口技能注册中心是整个体系的“户口本”所有技能上线前都要在这里登记。每个技能注册时至少包含以下字段字段说明是否必填skill_id技能唯一标识必填name技能名称需简短且无歧义必填description功能描述说明技能用途及适用条件必填parameters_schema参数JSON Schema必填returns_schema返回值JSON Schema必填tags技能标签用于检索分类选填version技能版本号必填enabled是否启用必填这里有个细节值得注意name 和 description 是给大模型看的而不是给人看的。很多开发者习惯用英文命名函数或使用过于简短的描述结果模型理解不到位。我见过一个项目把“查询用户订单”写成qry_ord模型经常把它当作订单状态查询之外的用途后来改成query_user_orders并配了详细的描述误用率立刻降了一半。我建议在注册阶段就建立一套规范description的写法可以套用固定模板“当用户需要[场景描述]时使用此技能。[输入说明][输出说明][特殊注意事项]。”比如当用户需要查询订单物流信息时使用此技能。 输入订单编号order_id必填、手机号phone用于校验身份选填。 输出物流轨迹列表包含时间、地点、状态描述。 注意订单编号不存在时返回错误码404不要尝试自行推断。这种结构化的描述模型命中率会明显高于“查询物流”这种笼统的写法。2.2 技能描述的质量直接决定模型调用的准确率我在实际调试中发现一个规律模型选技能靠的是语义匹配描述写得越精准匹配的准确率越高。这里分享几个实测有效的技巧第一描述中要包含触发场景的关键词。比如“当用户询问天气/气温/降雨概率时”就比“天气查询功能”更能让模型找到对应技能。第二描述中要写清楚边界条件。比如“本技能只能查询已发货的订单未发货订单请调用order_create技能”这样能有效减少模型判断模糊的情况。第三描述中要区分主用途和附带能力。很多技能其实能做多件事但模型理解核心用途就够了。我通常把主用途放在前面附带能力如果跟其他技能有交叉就干脆不提避免给模型造成干扰。举个例子同样是文件处理技能这样写就不合格处理文件。而这样写效果就好很多当用户需要对本地文本文件进行读取、写入或内容追加时使用此技能。支持.txt和.md格式输入为文件路径和操作类型输出为操作结果或文件内容。注意不支持二进制文件和图片文件。第二种写法明确说了“什么时候用”、“支持什么”、“不支持什么”模型几乎不会用错。2.3 参数Schema用规范化定义给模型“填空”参数Schema是技能体系中最容易被忽视、但出错率最高的部分。模型在调用技能时本质上是根据用户的自然语言输入生成一个符合Schema的参数JSON。Schema定义得越清晰模型“填空”的准确率就越高。我有几个常用的Schema设计经验参数名要直观不要用缩写。比如user_phone就不要写成u_ph模型很容易对缩写产生误解。每个参数都要写description说明参数的含义、格式、取值范围。模型不是人它不会自己“猜”参数是什么意思。必填参数和选填参数要严格区分。如果有一个参数大部分情况下存在默认值就尽量设为选填降低模型填错的概率。对于枚举类型的参数把所有可选项和对应含义都写清楚。这里给一个实际的参数Schema示例是某个Agent项目里的“发送定时消息”技能{ type: object, properties: { recipient: { type: string, description: 接收消息的联系人名称必须是用户通讯录中已存在的联系人, minLength: 1 }, content: { type: string, description: 消息正文内容支持纯文本和链接长度不超过2000字, maxLength: 2000 }, send_time: { type: string, description: 消息发送时间格式为YYYY-MM-DD HH:mm:ss不填则立即发送, format: datetime }, priority: { type: string, enum: [high, normal, low], description: 消息优先级high为立即通过强提醒发送normal为普通通知low为合并到摘要中发送, default: normal } }, required: [recipient, content] }注意priority这个字段如果你只写“优先级”三个字模型可能填一个“紧急”进去Schema校验直接报错。但你把每个枚举值的含义和效果写清楚模型就知道该填high而不是“紧急”了。2.4 技能调度引擎让Agent知道“先做什么、后做什么”单体技能容易做难的是让Agent在复杂场景中按正确的顺序调度多个技能。我的做法是给每个技能增加两个元信息字段prerequisites前置技能条件和conflicts冲突技能。比如“生成项目周报”技能prerequisites里可以声明“需要先调用get_project_data获取项目数据”。当模型决定使用该技能时调度引擎会自动检查前置技能是否已经执行如果未执行会先触发前置技能的调用而不是等模型在对话中自行决策。conflicts字段用来声明哪些技能不能同时使用。比如“修改文件”和“发送文件”在同一轮任务中同时出现可能会引发文件内容不一致的问题。声明冲突后调度引擎会提示模型做出选择或先征求用户确认。这两个机制帮我解决了一个老大难问题多步任务中的顺序控制。在没有这套机制之前模型经常“想到哪做到哪”有了明确的前置依赖声明Agent的执行顺序明显稳定了。3. 实操过程从零构建一个技能体系并接入Agent3.1 第一步梳理业务场景划定技能边界我在构建 agent-skills 时第一步不是写代码而是花了两天时间梳理业务的全部使用场景。这个模拟项目是一个面向某公司内部的智能客服需要支持问题解答、工单创建、知识库检索、人工客服转接等能力。梳理场景时我会把用户在对话中可能提出的各种请求列出来然后逐条归类哪些请求可以直接由一个原子技能满足哪些需要编排组合哪些其实不适合当前Agent处理比如涉及隐私或权限的操作。这个过程中自然就划定了技能的边界。这里有个重要的实操心得技能的粒度不是越细越好也不是越粗越好而是要匹配模型的理解能力和业务的需要。粒度过细模型需要在一次任务中调用十几个技能容易乱粒度过粗技能本身变成一个小业务系统复用性又很差。我的经验是让每个技能的输入输出尽量控制在3-5个参数以内这样模型最容易理解和生成。3.2 第二步定义技能清单与优先级场景梳理完之后我整理了一份技能清单并给每个技能标注了优先级。这一步看似简单其实很关键优先级决定了Agent在能力重叠时首选哪个技能。比如我的项目里有两个技能都和“用户订单”相关“查询订单信息”和“修改订单信息”。虽然它们参数类似但在实际客服场景中70%以上的请求都是查询所以我给查询技能的优先级设为P0修改设为P1。调度引擎在处理模糊请求时如果模型无法确定用户意图是修改会默认按查询来处理避免误操作。我当时整理的技能清单大致长这样技能名称优先级类型说明retrieve_knowledgeP0原子技能基于关键词检索知识库answer_common_questionP0场景技能回答高频常见问题create_ticketP0场景技能创建工单并通知对应人员query_order_statusP0场景技能查询订单状态modify_orderP1场景技能修改订单信息需用户身份验证transfer_humanP0编排技能转接人工客服并附上对话摘要3.3 第三步实现技能的注册与核心代码注册中心的实现其实不复杂用Python FastAPI可以很快搭起来。核心逻辑就是一个字典存储技能定义再加一些API用于注册、查询、更新技能元信息。# 技能注册中心核心代码简化版 class SkillRegistry: def __init__(self): self._skills {} def register(self, skill_meta: dict, handler: callable): skill_id skill_meta[skill_id] if skill_id in self._skills: raise ValueError(f技能 {skill_id} 已存在) self._skills[skill_id] { meta: skill_meta, handler: handler } def get_handler(self, skill_id: str): return self._skills.get(skill_id, {}).get(handler) def list_skills(self): return [ {skill_id: s[meta][skill_id], name: s[meta][name], description: s[meta][description]} for s in self._skills.values() ]技能实现本身就是一个普通函数接收参数JSON执行后返回JSON。我建议所有技能函数保持统一的签名def retrieve_knowledge(params: dict, context: dict) - dict: 根据关键词在知识库中检索相关信息。 keyword params.get(keyword, ) top_k params.get(top_k, 5) results knowledge_base.search(keyword, top_ktop_k) return {results: results, total: len(results)}这里的context参数用于传递对话上下文、用户身份信息、权限标识等。技能函数不需要自己管理全局状态统一通过context读写避免多个技能之间产生隐式依赖。3.4 第四步设计系统提示词教会模型使用技能有了一套技能还得让模型知道怎么用。很多人以为把技能列表塞进system prompt就行其实远远不够。我踩过这个坑直接塞列表的结果是模型经常自由发挥编造不存在的技能名。我的方案是在system prompt中明确告诉模型三件事第一有哪些技能可用每个技能一句最简描述 第二调用规则必须严格遵循skill_id参数必须符合Schema定义 第三能力边界当技能无法满足用户请求时不要编造结果明确告知用户。实际使用的提示词模板我简化一下你是某公司智能客服助手你可以调用以下技能 - retrieve_knowledge知识库检索适用于具体问题查询 - create_ticket创建工单适用于需要后续跟进处理的情况 - query_order_status订单状态查询适用于用户询问订单进度 - transfer_human转人工适用于用户明确要求人工或本助手无法解决时 调用规则 1. 每次调用前先评估用户意图是否匹配技能描述匹配才调用 2. 参数必须按技能定义的JSON Schema填写缺失的必填参数要向用户询问 3. 技能调用结果如果无法满足用户需求要如实说明不得编造信息 4. 一次对话中如需多步操作按依赖顺序依次调用这段提示词的写法其实借鉴了“规则引擎”的思想明确、简洁、无歧义。我在多次测试中发现模型对这类指令式的说明遵从度相当高比“你可以自由调用以下工具”这种宽松表述效果好得多。3.5 第五步技能测试与调优闭环技能接入后不能直接上线需要先跑一轮系统的测试。我自己常用的测试方法是准备一批“用户问题样例集”覆盖每个技能的典型场景、边界场景和干扰场景然后自动运行Agent记录每次技能调用的正确性。测试时重点关注三个指标命中率用户问题对应的正确技能被调用的比例参数准确率调用技能时参数填写的正确比例失败恢复率技能执行失败后Agent能否走降级路径或明确告知用户。调优时优先处理命中率低的问题。如果正确技能没被调用先检查技能描述是否足够清晰再检查描述中的关键词跟用户问题中的表达方式是否匹配。有一个案例我印象很深用户的表达是“我的订单什么时候到”技能描述里写的是“查询物流轨迹”模型一开始匹配不到后来我把描述改成“当用户询问订单何时送达/物流进展/快递到哪了时使用”命中率一下子就上去了。这就是为什么我说技能描述是一等公民它的质量直接决定Agent的可用性而不是可有可无的注释。4. 常见问题与排查技巧实录4.1 技能调用失败模型选择技能时“差之毫厘谬以千里”这是最常遇到的问题。现象是用户明明问的是A场景模型却调用了B技能或者直接不调用技能自己编答案。我排查这类问题的思路是先看模型在对话中的思考过程如果模型平台支持输出推理过程确认模型是理解错了技能描述还是理解对了但选择了错误的skill_id还是根本没意识到需要调用技能。针对不同的原因对应的解决办法也不同问题原因特征解决方式技能描述与用户表达脱节正确技能未被调用模型选了语义相近的其他技能重写技能描述融入更多目标的表达说法技能描述过于宽泛多个技能的触发条件重叠模型无法区分明确边界条件在描述中写死“不做”的场景技能没有出现在提示词中模型“不知道有这个技能”检查注册状态和提示词中的技能列表确保包含参数Schema不清晰技能调用成功但参数必定填错重写参数description补充枚举值和格式示例还有一个高频场景模型调用技能后因为参数校验失败报错然后它开始自己“修正”参数重试结果越改越错。我建议在实现技能时对参数校验做两级设计第一级是Schema硬校验参数类型不对或缺少必填字段直接报错第二级是业务软校验比如查询某个不存在的订单ID返回明确错误码。这样模型收到错误码后能根据提示信息进行下一步而不是盲目重试。4.2 上下文污染技能A的结果变成技能B的错误输入这是做多技能编排时很容易踩的坑。在测试模拟项目X时我遇到过一个问题Agent先调用了知识检索技能获取了一堆文档片段接着要调用工单创建技能结果工单的内容字段里被填入了文档片段整个工单正文完全不可读。排查后发现问题出在上下文管理我把所有技能的返回结果都原封不动地丢回了对话历史模型在看到“当前有哪些信息可用”时误把文档片段当作了工单内容。解决办法是在技能返回值中增加metadata字段标记数据类型和使用范围{ data: {content: 工单正文内容}, metadata: { type: ticket_content, source: user_input, usage: for_create_ticket } }同时在提示词里明确告诉模型“不同技能的返回信息归属于不同用途如果下一任务需要的信息来源不明优先基于用户原话生成而不是使用其他技能的输出。”这套规范加上之后上下文污染的问题明显减少。4.3 性能瓶颈技能数量大了之后上下文越塞越多、调用越来越慢技能数量从十几个增长到几十个后你会发现一个问题技能描述和规则占用的上下文越来越多留给对话和业务内容的空间越来越少。而且模型在几十个技能里做决策耗时和出错率也会上升。我的对策是分级“技能提示”常用的核心技能放主提示词低频长尾技能从注册中心动态加载由模型在需要时主动查询技能列表再决定调用。具体来说我实现了一个search_skills技能当主提示词中的技能列表无法匹配用户意图时模型可以调用这个技能基于关键词在注册中心检索对应技能。这个设计有个附带好处长尾技能不会被遗忘。如果你的所有技能都一股脑放在主提示词里模型可能只记得前面的几个后面的几乎不会被使用。而动态检索模式天然保证了技能被“按需唤起”。4.4 安全问题权限校验必须在技能层严格把关技能体系还有一个常被忽略的点安全。Agent可以调度技能就意味着你开放了一个“可执行操作的入口”。如果没有权限校验可能会出现越权操作比如普通用户通过Agent修改了管理员才有的配置。我的经验是三条铁律技能注册时必须声明所需权限等级技能执行前由调度引擎统一做权限校验而不是在技能函数内部自行校验高风险技能如删除、修改、对外发送消息必须在执行前让用户二次确认。这里的第三点尤其重要。实际运营中很多用户习惯性地说“帮我处理一下”如果Agent真的自动执行了删除操作出了问题很难挽回。我见过一个项目因为Agent自动发送了一封内容不完整的邮件导致客户投诉。后来他们给“发送邮件”技能加上了二次确认机制风险就完全可控了。提示二次确认不是让Agent问“你确定吗”这样简单的重复而是要把即将执行的操作内容重新概括给用户确认比如“我将向某地址发送内容为X的消息是否执行”这种形式模型执行起来更可靠用户也更容易感知风险。5. 进阶方向从“能用”到“好用”的几条经验5.1 让技能具备自描述能力让模型自己“发现自己需要什么”传统的技能调用是模型从固定列表里选择但这样受限严重。更进一步的思路是把技能本身当成可检索的知识库让模型先“查找该用什么技能”再“调用该技能”。我在实际项目中测试过这个模式效果很理想。做法是给注册中心增加一个文本索引技能描述和标签被向量化存储。模型在遇到用户问题后先在索引中做相似度检索返回最相关的3-5个技能候选并附带每个技能的详细描述。这样既节省了上下文空间又提高了技能发现的灵活性。这个模式也有代价一次检索多一次模型调用延迟会增加几百毫秒。但如果你的技能库里有大量长尾技能这个代价完全值得。5.2 建立技能反馈闭环每次调用都是一次学习机会技能体系不是一次建完就结束的它需要持续迭代。我在项目里建立了一个简单的调用日志表记录每次调用的用户原始输入、模型选择技能、参数内容、执行结果、用户后续反馈比如是否追问、是否投诉。每周做一次日志分析找出那些“模型选错技能”或“用户对结果不满意”的案例更新对应的技能描述或调整技能的优先级。坚持下来技能体系会越用越顺手。这里再分享一个小技巧条件允许时可以在测试阶段同时跑两套技能描述A/B测试对比同一批用户问题的命中率选择效果更好的那版发布。我就是用这个办法把“查询订单状态”技能的描述改了四个版本最终定下来的是包含“物流/配送/签收/在途”多个同义词表达的那版命中率从73%提升到91%。5.3 围绕“技能市场”思路沉淀团队资产最后说一个团队协作层面的思考。很多团队的项目是短期、可复用的但技能往往和个人经验绑定。我建议项目里把技能定义独立于代码仓库单独维护形成团队内部的“技能市场”。每个技能定义、描述、参数Schema、测试样例、踩坑记录都可以沉淀下来新项目直接复用现成技能而不是重新发明轮子。在维护 agent-skills 项目的过程中我最深的体会是Agent开发重的不是模型调参而是把业务拆解成模型能理解的一等公民。技能描述写得好不好参数Schema定义得规不规范编排逻辑是否清晰这些细节的累积决定了你的Agent是“能用”还是“好用”。如果你正在疲惫于跟模型的对话调来调去不妨回头看看自己的技能体系也许问题根本不在模型而在于你给它的工具和规则还不够清晰。