1. 从agent-skills这个标题说起一个被低估的工程化命题第一次看到agent-skills这个标题的时候我脑子里蹦出来的不是某个具体框架而是一类很典型的问题当我们在做智能体Agent相关项目时真正卡住进度的往往不是模型能力本身而是技能这一层的组织方式。模型能推理、能生成但它要真正完成一件端到端的事中间需要一连串可复用、可组合、可验证的能力单元这些单元怎么定义、怎么注册、怎么调度、怎么保证稳定输出才是工程上最磨人的部分。agent-skills这个标题本质上指向的就是智能体的技能体系设计。它不是一个单点技术而是一套围绕技能展开的工程结构技能如何抽象成统一接口、如何被智能体按需发现和调用、如何在多轮任务里保持上下文一致、如何对技能执行结果做校验和兜底。这套东西做得好智能体就像一个熟练工知道什么场景该用什么工具做得不好就是一个只会聊天的模型遇到稍微复杂点的任务就开始胡编乱造。这篇文章适合几类人看一是正在做智能体应用、被工具调用不稳定折磨过的开发者二是想把现有脚本、API、内部系统包装成智能体可调用能力的工程同学三是对智能体架构感兴趣、想搞清楚技能层到底该怎么设计的技术负责人。我会从整体设计思路讲到具体实现细节再到踩过的坑和排查方法尽量把agent-skills这件事讲透让你看完能直接对照自己的项目动手改。需要先说明一点下面涉及的具体实现是基于我在多个智能体项目里反复验证过的常见实践做的合理补全不是某个特定框架的官方文档。不同技术栈细节会有差异但核心思路是通用的。2. 技能体系的整体设计与选型思路2.1 为什么技能要单独抽一层很多人做智能体第一反应是把所有能力都塞进一个巨大的提示词里告诉模型你可以做A、可以做B、可以做C。刚开始跑demo没问题一旦技能数量超过十个问题就全出来了提示词越来越长模型开始漏掉某些技能技能之间的边界模糊模型经常选错某个技能改了参数整个提示词都要跟着动维护成本爆炸。把技能单独抽一层核心目的是解耦。技能的定义、注册、调度、执行、校验各自独立模型只负责决策用哪个技能、传什么参数具体怎么执行、执行失败怎么办交给技能层去处理。这样做的好处很直接技能可以独立开发、独立测试、独立上线模型侧只需要一份精简的技能清单出问题时能快速定位是模型选错了还是技能执行挂了。我试过两种极端方案。一种是把技能描述写得极其详细恨不得把每个参数的含义都塞进提示词结果模型反而抓不住重点选择准确率下降。另一种是只给技能名和一句话描述模型经常猜错参数格式。实测下来比较稳的做法是技能描述控制在做什么关键参数返回什么三要素参数细节通过结构化的schema单独传给模型而不是混在自然语言描述里。2.2 技能抽象的三个层次技能抽象我一般分三层来看这个分层直接决定了后面代码怎么组织。第一层是原子技能就是最小可执行单元比如查询某条记录发送一条消息计算两个数的和。原子技能的特点是输入输出明确、无副作用或副作用可控、执行时间短。这一层最容易被复用也最需要严格约束。第二层是组合技能由多个原子技能按固定流程编排而成比如根据用户问题查询数据并生成摘要。组合技能的价值在于把常见流程固化下来减少模型在多步任务里的决策次数。模型决策次数越少出错概率越低这是我踩了很多坑之后最深的体会。第三层是技能编排器负责在运行时根据任务动态选择技能、处理技能之间的依赖、做失败重试和降级。这一层不是技能本身而是调度逻辑但它决定了整个技能体系的上限。三层之间的关系可以用一个简单类比理解原子技能是零件组合技能是预装好的模块编排器是装配线。零件要标准化模块要覆盖高频场景装配线要能灵活应对非标需求。2.3 技能注册与发现的机制选择技能怎么被智能体发现有两种主流做法静态注册和动态发现。静态注册就是在启动时把所有技能加载进一个注册表模型每次决策时拿到的是完整技能清单。优点是简单、可控、调试方便缺点是技能多了之后清单膨胀模型选择压力大。动态发现是根据当前任务上下文先做一轮技能召回只把相关技能给模型。优点是精准、可扩展缺点是多了一层召回逻辑召回不准会直接导致任务失败。我的建议是技能数量在二十个以内用静态注册就够了别过度设计。超过二十个再考虑动态发现而且召回逻辑要做得足够简单可靠比如基于关键词匹配或向量相似度不要一上来就搞复杂的多级路由。我见过一个项目技能召回用了三层模型级联结果召回本身成了最大的不稳定源得不偿失。2.4 技能描述格式的取舍技能描述格式直接决定模型能不能正确调用。我试过纯自然语言描述、JSON Schema描述、以及两者混合三种方式。纯自然语言最灵活但模型对参数格式的理解经常跑偏尤其是嵌套结构。纯JSON Schema最严谨但模型对schema的理解能力参差不齐有时候会忽略schema里的约束。混合方式是我目前最推荐的用一句话自然语言说明技能用途用JSON Schema定义参数结构两者配合。具体来说一个技能描述包含这几个字段技能名英文、下划线分隔、语义明确、用途描述一句话动词开头、参数schema类型、是否必填、默认值、取值范围、返回结构说明、以及可选的示例。示例这个字段特别有用给一两个典型调用示例模型的选择准确率能明显提升。3. 核心细节解析与实操要点3.1 技能接口的统一约定技能接口不统一是后期维护的噩梦。我定过一套约定后来在几个项目里都沿用了效果不错。每个技能必须实现一个统一入口输入是一个字典或对象输出是一个固定结构的结果对象。结果对象至少包含三个字段状态成功/失败/部分成功、数据成功时的返回内容、错误信息失败时的原因。这个约定看起来简单但它强制所有技能在失败时也要给出结构化信息而不是抛异常了事。编排器拿到结构化错误才能决定是重试、降级还是上报。参数校验必须在技能入口处做不能指望模型传对。模型传参出错是常态不是异常。我一般会在入口处做三层校验类型校验、必填校验、取值范围校验。任何一层不过直接返回结构化错误并且错误信息要足够具体比如参数start_date格式应为YYYY-MM-DD实际收到2024/01/01这样模型在下一轮有机会自我修正。注意错误信息不要暴露内部实现细节比如堆栈、数据库表名、内部服务地址。这些信息对模型修正参数没有帮助反而可能被写进最终输出造成信息泄露。3.2 参数设计的几个反直觉经验参数设计有几个坑我是踩过之后才明白的。第一个坑是参数太多。一个技能如果有七八个参数模型传错的概率会急剧上升。我的经验是单个技能参数控制在五个以内超过五个就考虑拆成多个技能或者把部分参数设成有合理默认值的可选参数。第二个坑是参数名太抽象。比如用modetypeflag这种名字模型根本猜不出该传什么。参数名要具体比如query_type就比type好output_format就比format好。名字里带上语义模型的理解成本会低很多。第三个坑是枚举值没有明确列出。如果某个参数只能取几个固定值一定要在schema里用enum列出来不要只在描述里提一句。模型对enum的遵循度远高于自然语言描述。第四个坑是日期、金额这类格式敏感的参数没有给示例。我一般会在schema的description里直接写一个示例值比如格式2024-01-15模型照着抄的概率很高。3.3 技能执行的超时与重试策略技能执行必须设超时这是铁律。没有超时的技能调用一旦底层服务卡住整个智能体就挂在那里用户体验极差。超时时间怎么定我的经验是按技能类型分档纯本地计算类超时设1到2秒单次网络请求类超时设5到10秒涉及多个下游服务的组合技能超时设15到30秒。这个分档不是拍脑袋是根据P99耗时往上留一定余量定的。你可以先跑一段时间收集耗时分布再反过来调整超时值。重试策略要区分错误类型。网络抖动、下游限流这类瞬时错误可以重试一般重试一到两次每次间隔做指数退避。参数错误、权限不足这类逻辑错误重试没有意义直接返回失败。我见过有项目对所有错误都无脑重试三次结果参数错误被重试三次白白浪费时间和资源还让用户多等了好几秒。提示重试一定要设总时长上限不能因为重试把整体响应时间拖到不可接受。比如单次超时5秒、重试2次最坏情况就是15秒这个上限要在设计时就明确。3.4 技能结果的校验与兜底技能返回结果不能直接信尤其是涉及外部数据源的技能。我一般会做两层校验结构校验和业务校验。结构校验检查返回数据是否符合预期结构字段是否齐全、类型是否正确。这一层可以用schema校验工具自动做。业务校验检查数据在业务上是否合理比如查询用户信息返回的年龄是负数结构上没问题业务上明显不对。业务校验需要针对每个技能单独写规则没法完全自动化但关键技能一定要有。兜底策略分几种返回缓存数据、返回默认值、降级到简化版技能、直接告知用户当前不可用。选哪种取决于业务场景。查询类技能可以返回缓存或默认值操作类技能一般只能降级或告知不可用因为操作类技能失败后重试可能造成重复操作。4. 实操过程与核心环节实现4.1 技能注册表的搭建技能注册表是整个体系的地基。我用Python写过一个简化版核心结构就是一个字典key是技能名value是技能对象包含描述、schema、执行函数。class SkillRegistry: def __init__(self): self._skills {} def register(self, name, description, schema, handler): if name in self._skills: raise ValueError(f技能 {name} 已注册) self._skills[name] { name: name, description: description, schema: schema, handler: handler, } def get(self, name): return self._skills.get(name) def list_all(self): return [ { name: s[name], description: s[description], schema: s[schema], } for s in self._skills.values() ]这个结构看起来简单但有几个设计点值得说。注册时检查重名是为了避免技能被意外覆盖这种bug很难查。list_all只返回描述和schema不返回handler是为了让给模型看的清单和执行用的对象分离避免把函数对象序列化进提示词。注册时机我一般放在应用启动阶段所有技能在启动时一次性注册完。这样启动阶段就能发现重名、schema格式错误等问题而不是等到运行时才暴露。4.2 技能调用的完整链路一次技能调用的完整链路我拆成六步模型决策、参数解析、参数校验、技能执行、结果校验、结果回传。模型决策阶段把技能清单和用户输入一起给模型让模型输出用哪个技能、传什么参数。这一步的输出格式要严格约束我一般要求模型输出JSON包含skill_name和arguments两个字段。参数解析阶段把模型输出的JSON解析成结构化对象。这一步要处理模型输出格式不规范的情况比如多打了markdown代码块标记、JSON里有注释、字符串没加引号。我一般会先做一轮清洗再解析解析失败就返回错误让模型重试。参数校验阶段按schema校验参数。校验不通过返回具体错误让模型修正。技能执行阶段调用handler带超时控制。执行结果统一包装成前面说的结果对象。结果校验阶段做结构和业务校验。校验不通过走兜底策略。结果回传阶段把结果转成模型能理解的格式回传给模型让模型生成最终回复。这六步里最容易出问题的是参数解析和结果校验。参数解析的问题在于模型输出格式不稳定结果校验的问题在于业务规则容易漏。我的做法是给这两步都加详细日志出问题时能快速定位。4.3 一个完整技能的实现示例拿一个查询订单状态的技能举例完整实现大概是这样。def query_order_status(order_id: str) - dict: # 参数校验 if not order_id or not isinstance(order_id, str): return { status: failed, data: None, error: order_id必须是非空字符串 } if len(order_id) 64: return { status: failed, data: None, error: order_id长度不能超过64 } # 执行查询带超时 try: result call_order_service(order_id, timeout5) except TimeoutError: return { status: failed, data: None, error: 查询订单超时请稍后重试 } except ServiceError as e: return { status: failed, data: None, error: f订单服务异常{e.code} } # 结果校验 if result is None: return { status: failed, data: None, error: 订单不存在 } if status not in result: return { status: failed, data: None, error: 订单数据格式异常 } return { status: success, data: { order_id: order_id, order_status: result[status], updated_at: result.get(updated_at) }, error: None }这个实现里参数校验、超时控制、异常处理、结果校验都齐了。看起来啰嗦但正是这些啰嗦的检查让技能在真实环境里足够稳。我见过太多技能实现只写了happy path一遇到异常就整个链路崩掉。对应的schema大概是这样{ type: object, properties: { order_id: { type: string, description: 订单编号格式如ORD20240115001 } }, required: [order_id] }schema里的description带了示例格式模型照着传的概率会高很多。4.4 多技能编排的实现要点单个技能跑通之后下一步是多技能编排。编排的核心是处理技能之间的依赖和数据传递。我一般用两种编排方式固定流程编排和动态流程编排。固定流程编排适合高频、稳定的场景比如查订单-查物流-生成回复这种固定三步直接写死流程模型只负责触发。动态流程编排适合不确定步骤数的场景比如根据用户问题决定查哪些数据由模型在每一步决定下一步用哪个技能。固定流程编排实现简单用代码串起来就行关键是每一步的失败处理要明确。动态流程编排复杂得多需要维护一个执行状态记录已经执行了哪些技能、拿到了哪些数据、还剩哪些目标没完成。我一般会给动态编排设一个最大步数限制比如10步超过就强制结束避免模型陷入死循环。数据传递是编排里的另一个难点。技能A的输出要传给技能B中间要做字段映射。我一般用一个共享的上下文对象所有技能的输出都写进上下文技能B从上下文里取需要的数据。这样技能之间不直接依赖耦合度低。5. 常见问题与排查技巧实录5.1 模型选错技能怎么办模型选错技能是最常见的问题排查思路分三步。先看技能描述是否清晰。如果两个技能描述有重叠模型很容易混。解决办法是把描述改得更具体突出差异点。比如查询用户信息和查询用户订单描述里要明确前者返回用户基本资料后者返回订单列表。再看技能数量是否过多。技能超过二十个之后模型选择准确率会下降。解决办法是做技能分组先让模型选组再选组内技能。或者做技能召回只给模型相关技能。最后看是否有示例。给每个技能配一两个调用示例模型的选择准确率能提升不少。示例要覆盖容易混淆的场景让模型看到差异。5.2 参数传错怎么系统性解决参数传错没法完全避免但可以系统性降低概率。第一schema要写全。类型、必填、枚举、格式示例一个都别省。第二错误信息要具体。不要只说参数错误要说清楚哪个参数、错在哪、期望什么格式。第三给模型自我修正的机会。参数校验失败后把错误信息回传给模型让它重新生成参数一般一到两轮就能修正。我做过一个统计加了具体错误信息之后参数错误的自修正成功率从不到三成提升到七成以上。这个投入产出比非常高。5.3 技能执行超时怎么定位超时问题定位先看是哪个技能超时再看是技能本身慢还是下游慢。技能本身慢一般是实现里有性能问题比如循环里做了网络请求、没加缓存、数据量太大。下游慢一般是依赖的服务响应慢需要联系下游排查或者在技能层加缓存和降级。我一般会在技能执行处打点记录开始时间、结束时间、耗时。耗时数据积累一段时间后能看出哪些技能是慢点优先优化。5.4 结果不符合预期怎么排查结果不符合预期分两种情况技能返回的数据不对或者模型对数据的解读不对。技能返回数据不对查技能实现和下游数据源。模型解读不对查回传给模型的格式是否清晰。我遇到过把原始JSON直接回传给模型模型解读出错的情况。后来改成把关键字段提取出来用自然语言描述回传模型解读准确率明显提升。排查这类问题日志要打全。技能输入、技能输出、回传给模型的内容、模型最终输出这四样都要记出问题时能完整还原链路。5.5 常见问题速查表问题现象可能原因排查方向解决思路模型选错技能描述重叠、技能过多、缺示例检查技能描述差异度细化描述、分组召回、补示例参数传错schema不全、错误信息模糊检查schema完整性补全schema、具体化错误信息技能超时实现慢、下游慢看耗时打点优化实现、加缓存、降级结果不符预期数据错、解读错看四样日志修数据源、改回传格式多步任务中断状态丢失、步数超限看执行状态记录持久化状态、调整步数上限重复执行重试策略不当看重试日志区分错误类型、加幂等5.6 几个独家避坑技巧第一个技巧技能上线前一定要做对抗测试。故意传错参数、故意让下游超时、故意返回异常数据看技能能不能正确处理。我见过太多技能在正常流程下没问题一遇到异常就崩。第二个技巧给技能加调用频率限制。某些技能可能被模型在短时间内反复调用比如模型陷入循环。加个频率限制超过阈值就拒绝能避免资源被耗尽。第三个技巧技能版本管理。技能改了之后老版本不要立刻删保留一段时间。万一新版本有问题能快速回滚。我一般用技能名加版本号的方式管理比如query_order_status_v2。第四个技巧定期review技能使用数据。哪些技能从没被调用过哪些技能调用失败率高哪些技能参数经常传错这些数据能指导你优化技能体系。没被调用的技能考虑下线失败率高的技能重点排查参数常错的技能改schema。6. 技能体系的扩展与演进方向技能体系跑通之后往哪个方向演进我分享几个实际用过的方向。第一个方向是技能的组合化。把高频的多步流程固化成组合技能减少模型决策次数。比如查订单并生成物流回复这种固定流程做成一个组合技能模型只需要调用一次。组合技能越多模型决策压力越小整体稳定性越高。第二个方向是技能的自动化测试。给每个技能写单元测试覆盖正常流程和异常流程。技能改动后自动跑测试避免改出问题。测试用例可以从实际调用日志里提取把真实场景变成测试用例。第三个方向是技能的可观测性。给技能调用加详细的指标采集包括调用次数、成功率、耗时分布、错误类型分布。这些指标能帮你快速发现异常也能指导优化方向。第四个方向是技能的权限控制。不是所有技能都该对所有场景开放。敏感操作类技能要加权限校验确保只有授权场景能调用。权限控制做在技能层比做在模型层可靠得多。第五个方向是技能的跨项目复用。把通用技能抽出来做成独立的技能库多个项目共享。这样新项目启动时基础技能直接拿来用不用重复开发。技能库要有清晰的版本管理和文档否则复用会变成负担。我个人在实际操作中的体会是技能体系这件事前期多花时间把接口约定、schema规范、错误处理这些基础打好后期能省下大量排查和重构的时间。反过来前期图快省掉这些后期每加一个技能都要还债。这个投入产出比做过的人都懂。最后再分享一个小技巧技能描述里的动词要统一。要么都用查询创建更新这种标准动词要么都用业务动词不要混着来。动词统一之后模型对技能用途的理解会更一致选择准确率也会更稳定。这个细节很小但实测有效。