
接手Agent项目之后我第一个头疼的问题不是模型效果而是技能怎么管。回看这个agent-skills项目核心其实就一句话Agent能做什么不该靠堆提示词而应该靠一套结构化的技能体系来承载。这篇就把我在技能定义、调用链路、编排组合和实际踩坑中积累的东西完整梳理一遍给正在搭Agent能力层的朋友一份能直接抄作业的参考。无论你是做对话助手、自动化工作流还是垂直领域智能体这套思路都适用。1. 为什么Agent需要一套独立的技能体系1.1 技能与提示词、工具调用的边界在哪里很多团队一开始的做法是把能力全写进system prompt比如当用户查询天气时调用天气服务的API参数包括城市、日期注意时区问题。这种方案在小Demo阶段能跑通但一旦技能数量超过10个问题会立刻显现上下文长度被大量工具说明挤占留给真实对话的空间越来越少修改一个技能的描述要重新发版整个Prompt技能之间出现规则冲突时模型的调用准确率直线下降完全无法做技能的复用、版本管理和细粒度观测技能体系要解决的就是这个问题把模型该在什么时候做什么事的判断逻辑与具体怎么做的执行逻辑彻底拆开。技能Skill是封装好的、具有明确输入输出契约的能力单元模型通过技能的描述来决定是否调用、传什么参数运行时负责把模型的结构化请求翻译成真实代码执行。1.2 技能体系要解决的问题清单把Agent的能力层从提示词里的规则重构为可独立治理的技能本质上是在解决四件事选择性模型面对用户请求时能从技能列表中准确选中正确的那一个这依赖技能声明的质量稳定性技能执行成功与否不再依赖模型的自由发挥而是由确定性的代码逻辑保证可观察性每次技能调用的触发原因、参数、结果都有结构化日志而不是藏在模型回复的文本里演进性新增技能不改核心逻辑旧技能下线不影响其他能力团队可以并行开发一个完整的技能体系对应到工程实现上通常包含三块技能注册表所有技能的元数据集合、运行时加载技能声明、接收模型调用请求、执行技能代码、以及编排层负责多技能的组合调度。项目名里的agent-skills之所以走红本质上是大家意识到模型本身不是核心竞争力围绕模型建立的可插拔能力层才是。2. 技能定义的核心结构从接口到语义的设计要点2.1 一个技能声明里应该有哪些字段技能声明是模型判断要不要用你的唯一依据也是运行时执行请求的契约。我建议每个技能至少包含以下字段{ name: query_flight, description: 查询航班实时状态、起降时间、航站楼信息。当用户询问航班是否准点、几点落地、在哪个航站楼时使用。, version: 1.2.0, input_schema: { type: object, properties: { flight_no: { type: string, description: 航班号如 CA1234 }, date: { type: string, description: 航班日期格式YYYY-MM-DD默认当天 } }, required: [flight_no] }, output_schema: { type: object, properties: { status: { type: string, enum: [scheduled, delayed, cancelled, arrived] }, gate: { type: string } } }, timeout_ms: 8000 }关键点来了description是写给大模型看的不是写给用户看的。它需要回答三个问题——这个技能是干什么的、什么场景下触发、与相近技能的区别。很多团队把description写成功能文档式的长段落反而干扰了模型的选择判断。2.2 描述质量决定调用的上限我实测下来的经验是description写得好不好直接决定模型选技的准确率比调推理温度影响大得多。好的description应该具备几个特征用动词开头描述职责查询航班实时状态比航班信息查询服务更清晰显式给出触发条件当用户询问航班是否准点、几点落地时使用主动排除相似场景本技能不处理机票预订预订请用book_flight这是我在项目里反复调整后总结的对比描述写法实际效果航班查询工具输入航班号查询航班信息用户说帮我看看明天从北京到上海有没有航班时模型大概率误调查询指定航班号的动态信息包括准点/延误/取消状态、起降时间、航站楼。只处理已有航班号的查询不负责机票搜索与预订模型能准确区分搜索与查询误调率大幅下降另外我强烈建议在description里加入该技能不能做什么的负向约束这比正向描述更能抑制模型的错误调用冲动。成本极低但效果非常明显。2.3 参数Schema的约束与容错平衡输入参数的设计要遵循一个原则能枚举的尽量枚举能校验的必须校验。拿城市名举例如果你把city定义为自由字符串模型可能输出北京市、北京、Beijing、beijing四种变体下游系统处理起来苦不堪言。正确做法是给参数加format约束或者在技能内部做一层归一化departure_city: { type: string, description: 出发城市中文名如北京、上海, enum: [北京, 上海, 广州, 深圳, 杭州] }当然枚举会牺牲灵活性。实际项目里我倾向于混合策略核心业务参数用枚举兜底辅助参数允许自由文本在技能内部做一次清洗换算。参数是不是必填也要仔细斟酌。原则是模型能力范围内能从对话上下文推断出来的字段设为选填推断不出来且执行必须的字段设为必填。否则模型为了满足必填约束会编造一个看似合理但实际错误的值填进去。3. 技能从触发到执行的完整链路3.1 意图识别与技能匹配是怎么衔接的技能体系跑起来的完整链路可以拆成五步用户输入 → 模型判断意图 → 生成结构化调用请求 → 运行时解析并执行技能 → 返回结果并回填上下文。第二步和第三步是整个链路里最容易出错的地方。以我常用的实现方案为例模型其实不是直接输出我要调用query_flight而是生成一个函数调用对象{ name: query_flight, arguments: { flight_no: CA1234, date: 2025-06-18 } }运行时拿到这个对象后第一步不是立刻执行而是做校验技能是否存在、版本是否匹配、参数是否符合json schema。校验通过才进入真正的执行逻辑。我见过不少团队跳过这一步直接执行结果参数里混进非法值下游服务报了错问题定位成本极高。3.2 参数抽取背后的隐性问题参数抽取由模型完成但你可以用设计手段让它抽取得更准。我在项目里做过几个优化效果都很直接在input_schema的description字段里写明从用户原话中提取不要臆测。比如用户说帮我查个航班没给航班号模型不该自己编一个CA1234出来日期类参数设置好格式示例模型产生格式错误的概率会显著下降对于缺失的必填参数不要强行让模型猜测而应该在技能执行前触发澄清对话请问是哪一趟航班这里的取舍很有意思有些产品的交互设计要求Agent一次搞定不想多问用户。但实测下来参数缺失时主动追问的体验远比模型瞎编参数后返回错误结果要好。让技能返回结构化错误码比如MISSING_PARAM然后在链路层处理成追问是非常稳妥的做法。3.3 执行、反馈与上下文回填技能执行完成后的结果回填直接决定了模型后续回复的质量。我的做法是把技能输出按内容类型分三档处理结构化数据如航班状态、价目表——以JSON形式回填给模型让模型从中提取用户关心的要点长文档内容——先做截断或摘要再回填。否则一次技能调用可能吞掉上万字上下文错误与异常——必须转成模型能理解的自然语言描述不能把堆栈信息直接丢给模型有一个容易被忽视的细节技能返回结果里如果包含大量与用户问题无关的数据模型容易被带偏。比如航班查询返回了餐食信息、行李额、机型用户只问几点到模型可能会把无关信息也组织进回复。所以技能输出的字段越克制越好只返回与用户原始意图强相关的字段其他数据放在调试日志里即可。4. 技能编排与组合单点技能之上的协作逻辑4.1 从单技能到多技能的必然演进技能数量少的时候每条用户请求只用调一个技能。一旦技能库超过20个你会发现很多场景需要多个技能协作。比如用户说帮我订一个后天去深圳的航班然后再订那附近的酒店这就至少涉及航班查询、机票预订、酒店搜索三个技能的衔接。技能编排的复杂度在于单个技能之间是独立的但组合后的数据流必须显式定义。我的做法是把常见组合场景沉淀成固定的编排方案Pipeline。Pipeline里定义好每一步调用哪个技能、前一步输出的哪个字段作为后一步的输入、失败时是重试还是跳过还是整体终止。模型在这个管线里只做两件事判断用户请求匹配哪条管线、在管线执行中处理用户的补充输入。4.2 技能之间的数据传递约定多技能协作最容易出的问题就是数据口径不一致。前面的技能输出一个字段叫arrival_time后面的技能期望输入叫end_time如果每个技能各写各的编排层就要做大量字段映射代码又臭又长。建议团队在定义技能时就约定一套公共数据标准。比如统一的日期时间格式ISO 8601带时区统一的地点表达城市用中文全称经纬度用WGS84统一的金额表达数字币种代码如850.00 CNY具体到编排实现我推荐用显式映射而不是同名隐式传递。也就是说管线配置里明确写清楚prev.output.arrival_time - next.input.end_time。虽然啰嗦但可读性极高任何时候出问题你打开管线配置就知道数据是怎么流的。4.3 失败回退与降级策略编排层另一个必须提前设计的是失败处理。没有编排的情况下一个技能挂了就返回错误给用户有编排的情况下你要考虑的是整条链路的降级方案。我实际用过的策略有三种按优先级排列等价技能替换酒店搜索服务挂了先用备用的另一个酒店数据源技能顶上局部降级航班查询网络超时先返回暂无法获取实时状态但不阻断后续酒店预订流程整体回退链路中关键环节失败且无替代方案时明确告诉用户哪些步骤已完成、哪些未完成而不是给一个笼统的报错很多工程团队只关注技能功能实现不关注失败路径设计。但以我的经验用户对失败后能不能体面退场的感知比对功能细节的关心强得多。这条建议早点落地后面省掉大量线上客诉。5. 实测中踩过的技能设计坑与排查思路5.1 描述模糊导致模型总是选错技能这个坑我印象太深了。项目早期技能库里有两个技能get_stock_price获取股票实时价格和get_stock_historical获取股票历史走势。前者的description写的是获取股票价格后者的description写的是获取股票历史数据。结果用户问帮我看看茅台最近三个月涨了还是跌了模型在80%的情况下调用的是前者。用户拿到当天价格后根本没法判断涨跌趋势。排查过程是这样的我先翻了技能调用日志发现大量请求误命中get_stock_price然后我把两条技能声明并排放在一起才发现两者描述里没有任何关于时间范围的区分线索。修复方式很简单在get_stock_price的描述末尾加一句仅返回当前最新价格不含历史区间数据在get_stock_historical的描述里加一句当用户表述涉及近期三个月涨跌趋势等时间范围时使用。修改后误调率从80%降到了7%以下。这个坑给我们的教训是技能描述不是写给你自己看的是写给模型看的。写完后建议找另外一位不熟悉该业务的同事做一次测试只把技能名和描述给他让他判断某个用户请求会调用哪个技能。如果他选错了模型大概率也会选错。5.2 参数结构不合理导致调用连番失败另一个高频坑出在参数设计上。有一次做个会议纪要技能时我把meeting_transcript设计为必填字符串希望模型把会议转写全文放进来。实测中模型经常传入截断的内容导致下游摘要服务结果质量很差。问题出在会议的完整转写文本往往超长模型在单次调用的上下文里根本不可能把全文塞进参数。正确的设计是参数只传meeting_id技能内部通过会议系统API拉取完整转写。也就是说参数的粒度应该是指向数据的引用而不是数据本身。凡是可能超过几百字的入参都应该改成传ID、传路径、传查询条件让技能代码自己去拉数据。这个坑的排查逻辑也值得说一句调用日志显示的失败原因是下游服务超时但根子是参数设计不合理导致下游收到了不完整数据。所以排查时别只盯着技能内部的报错要把参数值、模型实际传入的arguments、下游收到的数据三者对齐来看。5.3 技能膨胀之后的命名与治理技能数量到四五十个之后下一个危机是命名空间的混乱。我见过团队里出现search、web_search、search_web、search_on_internet这种四胞胎模型在选择时几乎靠猜。我的治理措施有三条命名规范动词前缀统一用query_查询、create_创建、update_更新、delete_删除。比如query_flight、create_booking分组路由按业务域给技能分组先让模型选组再在组内选技能。比如出行组的技能列表只包含航班、酒店、用车相关技能降低选择难度定期收敛每两周Review一次技能描述合并重复技能标记30天内零调用的技能进入待下线清单这些工作听起来跟Agent能力没直接关系但恰恰是决定项目能不能规模化落地的关键。技能库的建设本质上是一个持续治理的过程它不是写完就结束的一次性工作。6. 规模化落地时的一些补充建议6.1 技能质量怎么量化衡量没有度量就没有改进。我在项目里对每个技能维护三个关键指标调用准确率该技能被调用时是否真的是用户意图所需、参数校验通过率模型生成的arguments有多少比例通过schema校验、执行成功率技能代码实际执行成功占比。这三个指标分层定位了链路中的问题准确率盯的是模型选技校验通过率盯的是参数抽取执行成功率先盯的是技能本身稳定性。建议每个指标都设置异常告警。比如某技能调用准确率突然跌破80%大概率是技能描述被改动过或者技能库新增了相近技能导致混淆。这种问题越早发现越好等到用户大面积投诉再反应就晚了。6.2 先小步跑通再追求编排复杂度最后补一句个人体会Agent技能体系的搭建节奏不要一开始就想着把所有能力都编排得花团锦簇。我踩过最大的坑就是过早引入复杂的编排框架结果模型在管线选择上不断出错用户反复抱怨答非所问。更稳的路径是先把单技能调用打磨到高准确率区间确保模型面对明确意图时能稳定选对技能、传对参数然后再逐步叠加两三个技能的固定管线最后才是动态编排。每一步都要观察线上数据确认稳定再往前走。毕竟技能体系再漂亮最终要让用户觉得这个Agent真可靠才算数。而在可靠性这件事上技能描述的多写一句、参数约束的多做一层、失败降级的多备一手每一分的投入都会在线上反馈里得到回报。