1. 项目概述agent-skills到底解决什么问题如果你最近在做Agent相关的东西一定遇到过这种场景大模型本身能力很强写Prompt让它“帮我总结一下这周客户反馈里的负面问题”也能跑但一旦任务变成“先查CRM导出再清洗去重然后按产品线分类最后生成一张带趋势的报表”单纯靠提示词驱动就会漏洞百出。要么模型漏步骤要么中间某一步格式不对后面全崩。我去年开始重写一个内部项目名字就叫“agent-skills”核心是把Agent需要完成的各种能力拆成一棵技能树用“定义、注册、调度、编排”一套流程管理起来。整个过程做下来之后我最大的感受是Agent能不能稳定干活拼的不是模型聪明程度而是你有没有帮它把“会做的事情”结构化地准备好。这个项目解决的三个核心痛点正好也是大多数Agent应用从Demo走向生产时绕不开的三座大山任务完成质量不稳定同样的任务换一段输入结果时好时坏业务方不敢真用。能力不可复用A对话机器人里写好的“查询库存”逻辑B周报Agent想用还得重新写一遍。跨场景迁移困难换个模型供应商或换一个Agent框架所有流程几乎要推倒重来。“agent-skills”的应对方式很朴素把每个能力封装成一个独立的、自带说明书的技能包Agent通过技能清单知道自己“会什么”通过技能描述知道“什么时候该用”通过输入输出契约知道“调用后能得到什么”。适合谁来参考我认为是三类人正在用LangChain、Dify、Coze这类框架搭建应用的LLM应用开发者被“Agent总是乱调工具”折磨的AI产品经理以及做AI Infra、想在企业内部搭建可复用能力中台的工程师。下面我按整个项目的搭建路径把关键设计和实操细节展开说。1.1 从一个让人抓狂的真实场景说起先说一个让我决定做这个项目的导火索。当时团队接了一个“选品分析Agent”的需求希望给电商运营同事用输入一个品类比如“露营灯”Agent自动完成比价、算毛利、看差评聚类的动作最后产出一份选品建议报告。第一版我们没做任何技能化封装就是写了一个超长Prompt把所有步骤、字段格式、判断规则全塞进去让大模型直接干。结果上线第一周就翻车了有几次模型把“线上下单”当成了“线下经销商供货价”来算毛利有几次生成报告的Markdown表格列数不对运营同事复制到飞书文档里直接错位还有一次模型“自作聪明”跳过了数据清洗步骤把重复的SKU当成了不同商品。问题的根源很清楚大模型“知道”怎么做但Agent运行时不“保证”怎么做。Prompt写得再好也只是软约束模型为了追求语义连贯性随时可能偏离流程。我当时就想如果把这些能力拆成一个个可以独立调用、独立验证、独立说明的模块让模型像查API文档一样去选择模块而不是让它凭记忆在脑子里编排整个流程稳定性会好很多。这就是“agent-skills”的起点。所谓技能Skill不是一个函数也不是一段提示词而是一个包含执行逻辑、使用说明、输入输出契约、错误处理策略的完整能力单元。工具响应的是“你能做什么”技能响应的是“你在什么场景下、用什么输入、按什么流程、交出什么结果”。1.2 技能不是工具也不是提示词很多朋友会问Function Calling不已经在做这件事了吗还需要单独搞一套技能体系这里我必须讲清楚区别。函数调用Function Calling解决的是“模型决定调用某个函数并生成参数”的问题它粒度很细通常只面对单个动作。比如“get_weather(city北京)”就是一个函数。工具Tool通常指带副作用的可执行单元比如发邮件、写数据库、调第三方API。但这两者都没回答三个更关键的问题什么时候不该用调这个工具之前是否需要先做某件事调完之后怎么衔接下一个动作举一个生活化类比你招了一个新实习生给他一份“API手册”相当于工具清单他看完知道系统里有查询接口、有导出接口。但光有清单他并不知道“处理退款”这件事要分几步先查订单状态、再调退款接口、最后写审计日志。你需要再给他一份“操作手册”写清楚每一步在什么条件下触发、参数怎么填、出错找谁。在Agent的语境里这份操作手册就是技能的描述层函数是技能的原子执行层两者叠加才构成一个完整的技能。所以我在这个项目里做了一个硬性约定任何能力如果没有写清楚“适用场景、边界条件、失败处理”就不允许注册为技能。这条规矩看起来很死板但正是它让后面的调度和编排有了能依赖的元数据基础。1.3 这个项目适合谁如果你是我的同行正在做以下几类事情那这个项目的设计思路可以直接抄作业业务型Agent比如客服助手、数据分析助手、运营报表生成器任务流程相对固定需要高稳定性。多Agent协同系统多个Agent共享一套底层能力库需要防止功能重复建设。企业内部AI中台你希望不同业务线的Agent能复用同一个“查库存”“算价格”能力而不是各做各的。反向来说如果你只是做一个个人玩具项目、调用一两个API、对稳定性没有硬要求那这套体系确实偏重了直接用Prompt加Function Calling更轻快。技术选型没有银弹关键是识别清楚自己处在哪个阶段。2. Agent技能体系的整体设计思路整个“agent-skills”的架构按三层来分原子技能层、复合技能层、策略技能层。这个分法不是拍脑袋而是在反复试错中沉淀下来的。原子技能对应“一个函数调用”比如读文件、查数据库、调一个外部接口复合技能对应“一组有序的原子操作”比如“从工单系统导出数据并做分类汇总”策略技能则负责决定“什么时候用哪个复合技能”它是技能的调度者常常体现为一段路由逻辑或一个决策模型。为什么要分这么多层因为Agent任务的复杂度差异太大了。让模型直接从一个300个技能的清单里选“最小可用单元”检索精度和上下文消耗都会失控。分层之后顶层调度的候选集缩小到几十个复合技能检索和决策压力小了一个数量级。2.1 为什么需要“技能分层”没有分层的世界里是什么样子假设你给Agent注册了80个工具每次任务到来模型要把80个工具的description全部塞进上下文做选择。OpenAI的token成本先不谈模型在长列表里注意力会大幅稀释经常选错。更麻烦的是如果某些任务需要“先做A再做B才能用C”模型即使找到了C也没有能力预判“A和B还没做”。分层之后一个客户投诉分析任务在Agent眼里长这样策略层根据用户意图选择“投诉归因分析”这个复合技能。复合层该技能内部定义流程为“调用数据导出技能 - 调用清洗技能 - 调用聚类技能 - 调用报告生成技能”。原子层每个子技能都是可独立测试的最小函数。这很像代码里的模块化设计思路。你写一个下单功能不会把“扣库存、算运费、发短信”全写在一个函数里。每个子动作独立成函数既容易单元测试也方便在别的流程里复用。技能分层本质上是把这种软件工程常识搬到了Agent能力组织上。2.2 一套核心设计原则在“agent-skills”里我总结出五条设计原则后面对每个技能做评审时也按这五条打分单一职责一个技能只做一件事。如果技能描述里出现“同时”和“并且”大概率要拆。契约先行先定义输入输出的JSON Schema再写执行逻辑。没有契约的实现一律不接收。自描述description要写到让一个陌生LLM只看描述就能判断“该不该用我”。写清楚业务场景、典型触发说法、明确不做的事。失败可见技能要定义失败类型和兜底行为不允许静默失败或者只抛“调用失败”这种无信息错误。可回滚技能要有版本号升级时保留旧版本出了回归能秒切。这五条原则里面我认为最容易踩坑的是第三条“自描述”。很多人写技能描述喜欢写成“查询用户数据”六个字就完了。实际上换个模型换种问法这个技能基本处于“失联”状态——模型根本不知道什么时候该选它。一个合格的技能描述至少要回答五个问题这个技能是干什么的典型触发场景是什么不该用它做什么输入数据从哪里来输出给谁用2.3 与Function/Tool/Workflow的对比在接触过各种方案后我给它们做了一个对比也建议你按这个视角去理解自己的Agent架构维度FunctionToolSkillWorkflow粒度单个动作带副作用的函数工具使用说明流程定义固定流程分支有限决策主体模型决定是否调用模型决定调用模型做路由技能内部自行编排运行时引擎固定执行可复用性低中高低可变性流程调整无无中低典型场景简单问答查天气、发邮件业务能力封装财务月结、定时任务工具解决“能做什么”Workflow解决“必须怎么做”而技能是中间地带它既保留了模型的决策空间又通过结构和契约把不确定性压缩到可控范围。如果只靠Workflow业务规则变化一次就要改一遍流程如果只靠Tool每次对话质量全看模型当时的心情。“agent-skills”选择的路径是把能标准化的部分标准化把该让模型决策的部分留给模型。3. 核心实现技能定义、注册与调度进入实操环节。这一节我会从一个具体技能的诞生过程切入讲清楚技能包的文件结构、注册表的实现方式、以及调度器如何做技能选择。我用Python实现但你用TypeScript、Java甚至纯YAML配置都能理解同一套逻辑。3.1 技能包的结构设计先看一个技能包在仓库里的目录结构。每个技能独占一个目录散落在skills/下面skills/ sentiment_analyze/ skill.yaml implementation.py description.md tests/ test_cases.json golden_set.json report_generate/ skill.yaml implementation.py description.md tests/ email_send/ skill.yaml implementation.py看到这里你可能会问为什么不直接把所有技能注册在一个大Python文件里答案是管理成本。当技能数量超过50个时集中式注册会让三个问题迅速恶化——描述维护互相干扰、版本管理纠缠不清、权限配置没法细化。独立目录让每个技能的“所有物”都在自己眼皮底下迁移、扩展、灰度、回滚都变成了目录级别的操作。skill.yaml是技能的注册凭证也是调度器判断“要不要用你”的第一依据。我们看一个真实例子name: sentiment_analyze version: 2.1.0 description: | 对一段客服对话文本判断用户情绪倾向输出为正向/中性/负向及置信度。 当用户表达中包含投诉、退货、强烈不满等信号时优先调用本技能。 本技能不处理图片、音频不做多语言翻译。 tags: [customer_service, sentiment, nlp] runtime: python3.11 timeout: 10s inputs: - name: dialogue_text type: string required: true description: 客服与用户的对话全文UTF-8编码 - name: language type: string required: false default: zh enum: [zh, en] outputs: - name: sentiment type: string enum: [positive, neutral, negative] - name: confidence type: float minimum: 0.0 maximum: 1.0 error_handling: - error: empty_input action: return_result_with_status从调度的角度description和tags是两个最关键字段。description服务于大模型的语义匹配tags服务于规则过滤。比如一个“客户投诉分析”任务进来embedding召回了sentiment_analyze、report_generate、database_query三个候选tags可以快速过滤掉没有customer_service标签的技能缩小语义排序范围。这个组合检索策略效果好于只用其中任何一种方式。3.2 技能注册表与召回策略技能的存放解决了接下来要解决“Agent怎么知道有这些技能”。这一步我称之为技能注册表Skill Registry。它的实现不复杂定时扫描技能目录、解析每个skill.yaml、把元数据和description文档写入索引库。索引库可以用Elasticsearch也可以直接用Chroma/FAISS这类向量库取决于你已有的技术栈。实际的查询链路分为两段粗召回把用户请求的文本做embedding在技能索引里做TopK检索。精筛选把召回结果按照“标签匹配、输入参数可用性、权限范围、技能版本状态”做规则过滤最后返回给LLM的候选技能列表一般控制在10个以内。我在项目里记录过一组数据只用embedding召回时准确率在85%上下经常出现“语义相近但实际功能不符”的候选混进来。加上tags和输入Schema的过滤之后准确率能到95%以上。这说明调度不能纯依赖语义相似度规则层是刚需。这里有个容易被忽视的细节技能的description写入索引库之前要做“意图改写”。所谓意图改写就是把描述里的被动语态、模糊业务术语转换成用户可能使用的说法。例如原始描述是“对客服对话文本判断情绪倾向”改写后加上“用户生气了、客户投诉、表达不满”这类等价触发表达。这一步看似费工夫但它直接决定了用户说“客户好像很生气”时能不能召回到这个技能。3.3 调度器的两种实现路径技能注册表相当于“目录”调度器则是“决策者”。在“agent-skills”里我试过两种调度实现各有优劣方案A让Agent一次选一个技能Agent在处理任务时根据当前上下文和技能候选列表先选一个技能执行完拿到结果再决定下一步。优点是实现简单、Token消耗低、逻辑透明缺点是Agent缺乏全局规划可能在多步任务里做出短视决策。方案B让Agent先看全部候选再规划路径先把所有候选技能的description交给Agent让它生成一份执行计划再按计划逐步调用。优点是适合复杂任务缺点是候选过多时对模型的摘要能力和长上下文支持要求很高。我的建议是分场景混用简单任务步骤少于等于三步用A复杂任务用B其中B的规划结果会缓存下来供后续同类型任务复用避免每次重复规划。这套混用策略让一个客服数据报表任务的平均执行时间降低了37%。4. 技能编排让多个Skill协同工作的工程化方案单个技能封装好之后真正拉开项目差距的是“编排”这层功夫。很多团队做到技能注册和调度就收了结果Agent能做到的只是“单点问答”一旦遇到“分析完数据再发邮件给领导”这类跨步骤任务就歇菜。编排层的三个核心点我逐一展开讲。4.1 编排引擎的三个核心点第一是上下文管理。多个技能串联在一起最难的不是调用顺序而是数据如何在技能之间流转。我见过很多失败的实现把上一个技能的全部输出文本直接塞进下一个技能的Prompt里结果90%的无关字段全变成了噪声模型的指令跟随能力迅速稀释。在“agent-skills”里我们做了一个统一的“工作内存Work Memory”每个技能执行完只把声明过的输出字段写入内存下游技能按字段名按需引用。第二是数据契约传递。一个技能的输出能不能作为另一个技能的输入不靠“看起来很配”而是靠Schema匹配。我在项目里实现了一个轻量校验器下游技能声明自己需要的字段上游输出必须满足类型和约束才能传进去。校验失败的连接会在编排阶段直接报错而不是运行到一半才炸。第三是错误处理与重试策略。这是Agent工程里最容易偷懒的地方。技能调用外部API失败、数据格式异常、超时等等都要有明确的SLA定义。我在项目里给每个技能配置了统一的重试策略幂等操作重试三次采用指数退避间隔分别为1s、2s、4s非幂等操作不自动重试直接转人工兜底。这个策略一直在生产环境用效果相对稳定。4.2 用一个真实编排配置串联起来下面是一段简化的编排配置任务是“对昨日客户反馈做情绪分析并生成日报发送给运营负责人”flow_name: feedback_daily_report version: 3.2.0 steps: - id: export_feedback skill: database_query inputs: table: customer_feedback date_range: yesterday output_mapping: raw_data: query_result - id: analyze_sentiment skill: sentiment_analyze inputs: dialogue_text: ${export_feedback.raw_data} output_mapping: sentiment_result: analyze_result - id: generate_report skill: report_generate inputs: data: ${analyze_sentiment.sentiment_result} report_type: daily output_mapping: report_file: final_report - id: send_email skill: email_send inputs: attachment: ${generate_report.report_file} recipients: ops_managerexample.com注意到inputs里使用了${步骤ID.输出字段}这种引用方式这就是工作内存的落地形态。每个步骤只需面对内存中已经声明好的数据不需要感知技能的原始输出有多少个字段。这让每个技能保持充分独立替换、删减、增加技能都不会影响其他步骤的调用契约。这一步操作带来的稳定性提升是我在项目里最深的体感技能的“内部实现”和“对外接口”被彻底分开了。查数据库的技能从MySQL换成ClickHouse下游的情绪分析技能完全不用改。而Agent整体的任务成功率在我自己的实测里从重构前的58%提升到了83%。4.3 几个关键的实测参数建议运行一段时间之后我沉淀出几个可复用的参数经验列在这里给后来者参考技能总数控制在300个以内。超过这个量级embedding召回和规则过滤的准确率都会显著下滑需要引入技能分域比如按照业务线、地域再建立一层命名空间。技能description长度50到200字之间表现最好。太长模型注意力被稀释太短召回的意图改写会很困难。候选技能数量返回给LLM的列表不要超过10个。这是从调度器的上下文消耗和决策准确率之间折中出来的值。编排步骤上限单条编排建议不超过10步。超过10步时中间某步失败导致重做的概率会指数级上升更合适的方式是拆成子编排。重试次数网络类技能重试3次业务类技能订单创建、数据删除不重试直接走人工运维流程。5. 常见问题与排查技巧实录技能化改造落地以后日常工作并没有消停只是问题的形态从“模型乱来”变成了“技能体系本身的工程问题”。我把这一路踩过的坑整理成几个典型问题附上排查思路希望能帮你少走弯路。5.1 Agent总是选错技能问题出在描述上这是频率最高的问题。现象很典型用户说“帮我把这个客户的反馈整理一下”Agent召回了report_generate而不是sentiment_analyze生成了一堆表格而不是情绪分析。原因是技能的description里出现了“整理”“反馈”这一类万金油词汇和召回查询撞了语义。排查思路第一看召回阶段的TopK里有没有目标技能如果没有就是意图改写覆盖不够如果有但排序靠后就是description里“非典型触发词”压过了“典型业务词”。解决办法也很直接在description里加两三种用户真实会说的话语样本比如“用户这段话是开心还是生气”“客户在抱怨什么”同时把“整理”“汇总”这类泛义词删掉。我给所有技能都立了一条规矩描述里只允许出现“业务术语典型用户说法”不允许写“这是一个强大的功能”之类的废话。5.2 多技能串联时上下文被污染下游输出明显变差另一个典型问题发生在技能编排超过三步之后前一步的格式化输出全塞进下一步下游技能接收了大量无关字段处理任务的能力下降。原来准确率有80%的技能串联后掉到60%。我最后的解法是前面提到的工作内存机制每个技能的输出只有声明在outputs里的字段才允许被下游引用编排引擎在传递时自动把非声明字段剥离。这个机制上线后串联场景的准确率恢复到了单技能水平的95%。值得强调一句这个问题在开发环境不容易触发因为开发时数据量小、场景固定但一上生产数据字段多了几十个问题立刻爆出来。5.3 技能升级后表现回退怎么兜底技能描述和实现都不是一次写对的迭代是常态。但头疼的是有时候版本升级后新技能的宏观指标没有变化具体场景却出了回归——比如sentiment_analyze升级后对“阴阳怪气”的文本不再识别为负向情绪。由于整体指标被大量普通样本平均掉了这类回归很难被发现。我在项目里引入了一个“金样本集”Golden Set的概念每个技能维护20到50条覆盖边界情况的测试用例每次发布新版本先跑金样本集回归任何一条从通过变成不通过版本不允许上线。实践下来这是性价比最高的兜底手段比单纯盯着线上指标靠谱得多。5.4 一份避坑清单不要追求一次把整套流程塞进一个技能。拆细一点独立测试后面重组的成本会低很多。技能描述别写“万金油”。宁可让某些长尾场景召回不到也别为了覆盖率让高频场景选错。测试不能只看“任务完成率”还要看“因果正确性”。给Agent布置“为了完成X而做Y”的验证题能发现很多指标看不出的逻辑问题。编排配置必须做版本管理。流程和代码一样出了线上故障要能一分钟回滚别靠人工记忆去改。给技能调用加追踪日志。全局trace ID加上每个技能出入参快照是排查线上问题的救命稻草。最后说点个人体会做Agent技能化这套东西几个月下来我最大的体会是这行的难点已经从“模型会不会”转移到了“工程稳不稳”。真正花时间的地方不是把功能写成函数而是想清楚每个技能的边界、使用场景和失败策略。我经常跟团队说技能库里那一百多份skill.yaml里的description比模型选型重要得多它们才是Agent能不能干活的隐形天花板。最后分享一个小技巧在项目上线初期我每周会跑一次“全技能召回矩阵”——把技能库里所有技能和一百条典型任务做交叉验证看哪些任务召回到了不该用的技能哪些任务漏掉了该用的技能。这能提前发现大量描述层面的问题比等问题出现在用户面前再补救高效得多。当前这套体系下一步的计划是把企业知识库也接成技能以及把技能做成可跨Agent共享的“技能市场”。这个方向我们还在试后面有结果了再单独写一篇聊聊。