news 2026/9/23 4:54:57

从提示词堆叠到可复用技能体系:Agent技能层设计实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
从提示词堆叠到可复用技能体系:Agent技能层设计实战

从给智能体“塞指令”到建一套可复用的agent-skills,我大概花了三个月才彻底转过弯来。

如果你也正在做Agent相关的东西,大概率经历过这个阶段:一开始觉得让模型会做事,就是把能力说明往系统提示词里堆;堆到几千字以后,行为开始漂移,指令互相打架,调一个bug会带出另外三个bug。后来我意识到,问题不在提示词写得好不好,而在没有一个“技能层”——也就是把能力拆成可命名、可注册、可调用、可编排的独立模块。这个技能层,社区里一般就叫agent-skills。

这篇文章就把我这段时间在技能体系设计上的完整思考、踩坑和落地方案写出来。适合正在做Agent应用的开发者、想从单轮对话进阶到复杂任务编排的产品经理,以及所有对“怎么让模型稳定地做成一件事”感兴趣的人。

1. 为什么需要一套技能体系:从“会说话”到“会做事”的那道坎

很多人的Agent场景是从聊天机器人起步的。用户问一句,模型答一句,偶尔从文档里检索一下。这个阶段不需要技能体系,提示词写好就够了。但一旦要让Agent去完成真实业务动作——查库存、发工单、调接口、跑分析脚本——事情就变了。

1.1 “把所有能力写进提示词”为什么走不通

我见过不少团队的初版方案都是这样的:把工具说明、使用规则、注意事项全部塞进system prompt。在小规模、工具数量少于五个的时候,这个方案是能跑的。但到二十个工具以上的时候,开始出现几类非常典型的问题。

第一是上下文膨胀。每个工具描述平均按200个token算,二十个工具就是4000个token。加上对话历史、检索片段,一次请求的输入可能冲到一万多token,延迟和成本都在肉眼可见地涨。第二是行为不稳定。工具一多,模型经常在“该用哪个工具”上犹豫不决,甚至出现幻觉工具名。第三是调试地狱。行为不对的时候,你根本说不清是提示词没写清楚、工具本身有bug,还是模型理解偏了。三者搅在一起,排查效率极低。

1.2 技能层和工具调用的本质区别

你可能要说,这不就是function calling吗,有什么新鲜的。这里我想明确一个区别:工具函数是“单个动作”的抽象,而技能是“解决一类问题”的能力封装。

举个例子,一个名为get_weather的function是一个工具,它做一件事:调一个接口拿天气数据。但一个技能可能叫plan_travel_route,它内部要判断当前城市、调用天气接口、查询交通方式、结合用户偏好综合成一段建议文本返回。前者是原子操作,后者是原子操作的组合。在传统系统里这叫业务流程编排,在Agent体系里,我倾向于把“具备完整输入输出契约、能被模型识别和选择、内部可以拆成多个步骤或子调用”的模块统称为技能。

从代码组织的角度看,技能层带来的直接好处是关注点分离。提示词只负责说清楚“目标、约束和风格偏好”,技能负责把所有领域逻辑封装在黑盒里。模型不需要关心这个技能内部是调了三个API还是跑了一个模型推理,它只需要知道《在什么场景下、传什么参数、能得到什么结果》。

1.3 技能体系要解决的四个核心问题

我把技能体系的目标收敛成四个,后面所有设计都围绕这四个展开。

第一,可复用性。同样的“提取结构化信息”这个能力,应该能被财务分析场景和电商评论分析场景共用,而不是各写一套提示词。

第二,可测试性。每个技能都应该能脱离完整Agent上下文单独跑通,这样至少可以在单元层面验证它对不对。

第三,可编排性。复杂任务不是靠单个技能完成的,而是多个技能按某种流程协作完成。技能体系要支撑串行、并行、条件分支这些基本组合方式。

第四,可治理性。一个Agent接入了三十个技能之后,谁负责的、什么版本、调用了多少次、失败率多少,这些都要能看见,不能是一团黑。

这四个问题任何一个解决不好,Agent项目一上规模就会返工。

2. 技能的最小构成:一个技能的解剖实验

把“技能”这个词落到具体的工程实现上,它到底应该长什么样?我参考了不少开源项目里的skills写法,也自己折腾过几版,最后沉淀下来的最小构成是五个部分:元信息、描述、参数契约、实现体、验证规则。

2.1 元信息:给技能一个身份

元信息是技能的身份证。我常用的字段包括nameversionauthortagspermissionscost_hint。其中name必须全局唯一且语义清晰,version建议遵循语义化版本规范,permissions用来声明这个技能需要访问哪些资源,cost_hint给的是这个技能运行一次的大致代价等级(比如低/中/高),供模型在多个可选技能之间做平衡。

给技能打tags是一个容易被忽略但实战价值很高的习惯。没有标签体系之前,我找一个技能要去翻目录;有了标签之后,按“finance”“web”“data”这样的维度一筛就出来了。它还方便做技能路由,模型可以先按标签筛掉一大批不相关的技能,再在剩下的里面做精确选择,对减少误调用很有帮助。

2.2 描述:决定模型什么时候想起它

描述是整个技能定义里最关键、也最容易被写烂的部分。模型不会像人一样把技能文档从头读到尾,它是靠描述里的语义匹配来决定要不要调用某个技能的。写得太笼统,类似“获取信息的高级函数”,模型根本不知道什么时候适合用;写得太具体,把所有边界情况都罗列进去,token开销大,模型反而抓不住重点。

我推荐一种写法,叫“触发场景+输入要点+输出承诺”三段式。

触发场景:当用户的问题涉及查询本周订单状态、物流轨迹或预计送达时间时使用。 输入要点:需要订单号;若无订单号,可尝试用用户手机号后四位匹配。 输出承诺:返回结构化的订单状态JSON,字段包含status、timestamp、description。

这样写,模型比较容易建立“场景到技能”的映射。相比之下,“这个技能可以帮你查询订单”,这种描述基本等于没写。

2.3 参数契约:和模型约定输入格式

参数契约决定了模型能往技能里传什么。我推荐直接用JSON Schema来定义,不管是Python、TypeScript还是其他语言,都有成熟的库去校验和生成类型。一个典型的参数定义长这样:

{ "type": "object", "properties": { "order_id": { "type": "string", "description": "订单号,通常以ORD开头", "pattern": "^ORD[0-9]{10}$" }, "include_detail": { "type": "boolean", "description": "是否返回商品明细,默认false", "default": false } }, "required": ["order_id"] }

参数设计里最容易翻车的点是“过度宽容”。如果你允许order_id为空、然后在技能内部做一堆兜底逻辑,模型就会越来越懒,经常不传关键参数。我现在的原则是:关键参数一律必填,宁可让技能报参数错误让上层去追问用户,也不要默默猜一个默认值继续跑。猜错的代价远大于多一轮澄清的代价。

2.4 实现体:不止是函数,还可以是子流程

技能的实现体可以有两种形态。第一种是最常见的“函数式实现”,就是一个函数,里面写业务逻辑,调用API或者查数据库。第二种是“提示词模板+工具链”的实现,技能内部还有小模型调用或者子工具的串联。这种复合式技能是agent-skills比较有特色的地方,它在技能层封装了一段可复用的“思路”,而不仅仅是“动作”。

我举一个例子。一个叫“从会议记录里生成待办事项”的技能,它的实现体可以是:先用一个抽取模板从原始文本里抽任务条目,再调用一个日历服务的API检查冲突,最后用一个小模型把任务格式化成带优先级的清单。对上层Agent来说,它只需要传meeting_transcript进去,拿到todo_list出来,内部多啰嗦都不用管。

2.5 验证规则:没有校验的技能就是定时炸弹

最后是验证规则。技能返回给模型的数据,在进上下文之前一定要过一层校验。实际中我遇到过太多次:接口明明返回了数据,但字段名和技能文档里写的不一样,模型直接把原始JSON丢给用户,用户体验极差。

校验规则至少包含两部分:结构性校验和合理性校验。结构性校验看返回值是否符合声明的Schema,字段是否有缺失、类型是否对。合理性校验看数值范围、时间先后逻辑之类的业务约束。校验不通过的处理策略也要定好:是重试一次、降级到另一个技能,还是直接返回错误说明。不定义兜底策略,这个技能就不算完整。

3. 技能库的组织方式:从“有技能”到“好用的技能库”

单个技能设计好了,接下来就是规模化的问题。我在项目里管理的技能最多的时候到了六十多个,如果没有一套清晰的库组织方式,光找技能和维护技能就能消耗掉大量精力。

3.1 文件系统与注册中心的取舍

技能库的载体有两种主流做法:一种是文件系统优先,每个技能一个目录,目录里放定义、实现、测试;另一种是注册中心优先,技能在运行时注册到一个中心服务里,通过API动态拉取。

文件系统派的好处是透明、易读、适合团队协作,代码评审直接看diff就行。注册中心派的好处是灵活,支持动态上下线、灰度、权限控制,适合大规模平台化场景。

我的建议是:项目早期用文件系统就够了,在目录结构上把规范定好;等到技能数量超过五十个、或者要支持多个Agent共享一套技能库的时候,再上注册中心。直接从注册中心起步往往是在给自己的项目加不必要的复杂度。

我常用的目录结构是这样:

skills/ travel_planner/ skill.json main.py tests/ README.md order_query/ skill.json main.py tests/ README.md

每个技能目录里,skill.json存放元信息和参数契约,main.py是实现,tests放测试。这个结构足够简单,又能保证每个技能自包含。多个人同时开发互不干扰。

3.2 命名规范与冲突处理

命名这事看似琐碎,踩坑之后才知道重要。我之前有个同事给技能起名叫query,结果项目里三个地方各有一个query,一个查订单、一个查库存、一个查积分。模型按名字匹配的时候经常串,日志里看到query被调用,你根本不知道是哪家的。

我现在的规范是“领域-动作-对象”三段式:domain_action_object。比如commerce_query_orderinventory_check_stockloyalty_get_points。这样做的好处是,从技能名就能看出它属于哪个领域、干什么用、对象是什么,基本不需要额外查文档。

冲突处理上,除了依赖命名规范避免语义冲突,系统层面还要保证技能名的全局唯一性。注册的时候做一次查重,重复的直接拒绝注册,不要搞“同名覆盖”这种机制——同名覆盖在调试阶段非常坑,你以为是A行为,实际跑的是B逻辑,排查一圈回来发现是注册顺序导致的。

3.3 版本管理:技能也有语义化版本

技能是会被多个上层Agent引用的,改了它的行为,影响面和改一个微服务接口差不多。我建议每个技能都带上version字段,遵循MAJOR.MINOR.PATCH的语义化规则。

行为向后兼容的修改升MINOR,比如加了新的可选参数、优化了返回结果里的某个描述。不兼容的修改升MAJOR,比如改了必填参数、改了返回字段结构。修bug没有改接口的升PATCH。升级策略上,上层Agent默认锁定MINOR版本段内的小版本,只有显式升级才会跨MAJOR。这样可以在稳定性和迭代速度之间取得比较好的平衡。

我还养成了一个习惯:每个技能在skill.json里写明changelog,哪怕只是简单几行,也能在排查问题的时候省下大量考古时间。

3.4 技能的可观测性:让每次调用都有迹可循

技能库规模一大,没有可观测性等于盲飞。我给每个技能都埋了一套标准的调用日志,包含:技能名称、版本、入参摘要、出参摘要、耗时、成功标记、失败原因、调用方标识。集中存到日志平台里,按技能名和时间维度做聚合查询。

有了这些数据之后,能直接回答几个关键问题:哪些技能被高频调用、哪些技能几乎没人用、哪个技能失败率超标、哪次版本升级之后失败率上升了。我建议每个技能至少做到:失败时能追踪到具体的入参和异常栈,成功时能知道它产出了什么、耗了多少资源。这是技能治理的基础,也是我做技能淘汰和优化最重要的依据。

4. 技能编排的几种模式:把技能组合成真正的“行为”

单个技能只能做一件事,Agent要完成复杂任务,必须把技能编排成工作流。这一章讲三种基本编排模式,外加一种兜底策略,都是我在实战里真跑通过的方案。

4.1 串行管道模式:上一站的输出是下一站的输入

串行是最常用的编排模式,适合流程固定的强流程任务。比如“员工入职办理”这个场景:先验证员工身份信息,再创建账号,再开通权限组,最后发送欢迎邮件。每一步都依赖前一步的结果,顺序不能乱。

工程实现上,我倾向于把串行流程写成一个显式的流程配置或者用一个编排框架控制,而不是让模型自由发挥选择下一步。因为强流程任务里,步骤是确定的,让模型去编流程反而增加了不确定性。比较稳的写法是这样:

flow = SkillFlow( steps=[ ("identity_verify", {"employee_id": "{input.employee_id}"}), ("account_create", {"verified_info": "{steps.identity_verify.output}"}), ("permission_assign", {"account": "{steps.account_create.output.account_id}"}), ("welcome_email_send", {"to": "{steps.account_create.output.email}"}), ] ) result = flow.run(context)

这里的关键点是显式声明依赖关系,并把上一步的结构化输出有选择地传给下一步。注意“有选择”这三个字,不代表把上游输出整个塞给下游,那样上下文和参数都会膨胀,我一般会在每步之间做字段映射和裁剪。

4.2 条件分支模式:让模型只做决策,不做执行

不是所有流程都线性,很多任务中间有分叉。比如用户想“帮我处理一笔退款”,那得先判断订单状态:未发货的走取消退款流程,已发货的走退货退款流程,已完成的走售后申请流程。这个判断逻辑如果让模型自由发挥,结果可能每次都不一样。

我的做法是拆分:用一个“意图/状态识别”技能,它的输出是几个离散的结论选项;然后由一个确定性路由逻辑,基于结论选项选择后续分支。模型只负责它擅长的事情——理解自然语言、做分类判断;它不擅长的事情——在多个分支里做精确控制流——交给确定性代码。

条件分支的配置看起来类似这样:

flow = SkillFlow( steps=[ ("order_status_detect", {"order_id": "{input.order_id}"}), ], branches={ "unshipped": [("refund_cancel", {})], "shipped": [("return_apply", {})], "completed": [("after_sale_apply", {})], } )

这个模式的精髓在于:把模型的能力边界画清楚了。不要让模型直接输出“我应该走哪个分支”,而是让模型输出一个结构化的状态,然后由代码决定分支走向。确定性交给系统,智能性留在技能内部,两者各司其职。

4.3 并行扇出模式:做信息收集和聚合的最优解

有些任务天然并行。比如用户问“帮我整理一下这几个城市本周的天气,并推荐适合出行的目的地”,就需要同时查多个城市的天气。串行查一遍太慢,效率低,并行才能体现价值。

并行扇出模式适合的场景有这些特征:子任务之间互不依赖、子任务的结果可以独立产出、最终只需要一个合并结果。工程实现上,可以用异步任务池把每个子技能作为独立任务分发出去,等所有任务完成后,再交给一个汇总技能做合并。

results = await asyncio.gather( weather_query.run(city="上海"), weather_query.run(city="杭州"), weather_query.run(city="南京"), ) summary = summary_skill.run(results)

并行模式里比较需要注意的是资源控制和失败处理。全部并发可能导致外部API限流,我通常会给并发度设一个上限,比如最多同时跑五个。失败处理上,并行任务里如果有一个失败了,是整体重试还是只重试失败的那一个?我现在的策略是:允许部分任务失败,失败的任务单独重试一次,如果还不行,就在汇总结果里标记该子项失败,避免整条流程因为单个子任务挂掉而全部无法返回。

4.4 失败恢复与回退:编排层必须有的逃生舱

再好的技能编排也会遇到failure。外部API超时、返回数据格式不对、模型调用报错,多种多样。编排层如果没处理失败,Agent就会卡死或者给用户一段莫名其妙的错误信息。

我建议在流程设计阶段就定义好每个步骤的失败策略,至少包括重试、降级、中止三类。重试适合瞬时的网络抖动和限流,要注意退避策略。降级适合有备用方案的场景,比如主查天气接口失败,换备用接口。中止适合强依赖步骤,直接返回给上层,给出明确错误说明。

一个不可忽视的问题是部分成功状态的处理。比如并行扇出里,三个子任务成功了一个、失败了两个,流程应该怎么走。我在很多项目里看到的做法是“有失败就整体失败”,这对用户体验很不友好。更合理的做法是把成功和失败的结果都传给汇总步骤,汇总技能基于全部信息生成最终回复,失败的子项明确告知用户“这部分暂时拿不到数据”,而不是整段任务直接失败。

5. 我在实际项目中打磨技能的几条心得

前面讲的是方法论和结构,这一章聊一些更偏“手感”的东西。这些心得不是从教科书上看来的,是真实项目里踩坑踩出来的。

5.1 技能的粒度:大则僵,小则碎

技能粒度是设计时最让人纠结的问题。定得太粗,一个技能里塞了太多逻辑,稍微换个场景就没法复用,只能在技能内部加一堆条件判断,最后变成一个“什么都做但做不精”的大泥球。定得太细,技能数量爆炸,模型做技能选择时也容易眼花缭乱。

我现在的经验标准是:如果一个技能内部开始出现“如果需求是A,走这里;如果需求是B,走那里”的分支逻辑,而且分支之间差异不小,那大概率就是粒度太粗了,应该拆成两个技能。反过来,如果一个技能的主逻辑代码不到二十行,又只有一个入口,那可能太细,需要看它是否和其他技能经常一起出现——如果是,就考虑合并成一个复合技能。

5.2 description的措辞直接决定调用率

同一个技能,description写法不同,模型的调用率可能差出一倍。我做过一个对照实验,同一个天气查询技能,A版本写“查询天气信息”,B版本写“根据城市名查询实时天气、温度、湿度、风速和空气质量指数,当用户提到天气、温度、要不要带伞、适不适合出门时使用”,结果B版本的调用准确率明显更高。

但这不等于描述越长越好。描述长到一定程度后,收益递减,而且会挤占上下文。核心还是把触发条件写具体,把输出写明确。我在定稿每个技能的description之前会问三个问题:用户说什么样的自然语言,模型应该想起这个技能?这个技能绝对不能处理什么场景?它输出什么东西,对用户或下一个技能有用?答案写清楚就够了。

5.3 输出裁剪:不要把大块文本直接倒进上下文

技能执行完,返回结果是要传给模型继续推理的。如果技能返回了两百行的JSON或者一万字的报告全文,模型处理起来很吃力,上下文被占满,后续对话质量也会下降。

我处理这个问题的办法是在技能里内置一个“模型消费视角”的输出设计。即技能不只返回原始结果,而是同时产出一份“精简摘要版”和一份“结构数据版”。摘要版供模型快速理解含义,结构数据版供下游流程精确处理。比如爬虫技能抓完网页,输出给模型的,我会让它先做一个清洗和概括,把页面里的正文提取出来、压缩到一定字数,再进上下文。原始内容可以存起来,等用户需要细节的时候再调。

5.4 技能缓存:重复问题别让模型反复算

我观察过实际情况,相当大比例的技能调用是在处理重复或高度相似的问题。同一个结果查了一遍又一遍,成本和时间都浪费在重复劳动上。我在技能层加了一层可选的缓存机制,以“技能名+参数哈希”作为缓存键,把技能的结构化输出缓存一段时间。

缓存能带来非常直观的优化,但也伴随一个风险:数据实时性。订单状态这种场景,缓存五分钟可能就过时了。我的经验是给每个技能显式声明一个cache_ttl字段,值可以为零(不缓存)。实时性要求高的技能一律不缓存,数据变化不频繁、查询成本高的技能可以设置一个合理的过期时间。再配合缓存命中日志,定期看命中率,就知道哪些技能适合继续开缓存、哪些技能该把TTL调短。

5.5 日志里看到的真相:技能设计失败的信号

最后说一个让我印象深刻的反思。我之前一直觉得某个“综合查询技能”设计得挺好,参数灵活、覆盖场景多,直到看调用日志才发现,这个技能的失败率高达百分之三十五,而且大部分失败发生在参数解析环节。模型经常传不齐必填参数,或者传了根本不在Schema里的字段。原因是这个技能的必填参数有五个,对模型来说心智负担太重了。

后来我把这个技能拆成三个更聚焦的技能,每个技能的参数不超过三个必填字段,失败率立刻降到了百分之七以内。这个例子给了我一个非常重要的原则:如果某个技能的调用失败率长期偏高,优先怀疑技能设计本身,而不是责备模型不够聪明。多数时候,是技能的契约设计得不够好,超出了模型稳定处理的能力范围。观察日志、收集失败样本、迭代技能定义,应该是一个持续进行的循环,而不是做完一版就不管了。

5.6 技能心智负担的量化估算

顺着上面那个案例,我后来总结出一个粗略的“心智负担”估算方法。单个技能的必填参数每增加一个,模型准确填写的概率就会明显下降一截。我的经验数据是:三个以内必填参数,模型基本稳定;五到六个必填参数,出错概率陡增;超过八个,基本就是设计有问题,必须靠分流或重组来拆解。

这个规律本质上是模型在短上下文里做结构化输出的能力边界。要降低出错率,与其在提示词里反复强调“请准确填写参数”,不如从技能契约本身做减法。能通过上下文自动推导出来的字段,就不要让用户提供;能拆成两个子技能解决的复杂输入,就不要在同一个技能里堆参数。复杂度不会消失,但把它切到模型能力的舒适区里,整体效果会好很多。

基于这套规律,我在组件技能库的时候还会定期做一次“技能体检”,把调用频率、失败率、平均耗时、上下文占比排个序。低频高维护成本的技能,要么合并,要么下架;高频高失败率的技能,优先重构;高频低失败率的技能是核心资产,后续做任何改动都要加倍小心,避免打破原本稳定的平衡。

等到你手头的技能库开始形成这样的良性循环,Agent的稳定性、复用性和可维护性都会有一个质的变化。那也是我从“写提示词的人”转变成“设计技能系统的人”的分水岭。如果你正准备把Agent能力往产品化的方向推进,早一点把agent-skills作为一等公民来对待,后面会省掉很多折腾。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/23 4:54:34

视频驱动虚拟角色动作生成:从姿态估计到骨骼动画的系统设计

1. 项目概述与核心需求解析1.1 这个题目到底在做什么先说结论:这是一个典型的“CV(计算机视觉) 计算机图形学 动画驱动”交叉方向的系统设计类题目。它要解决的核心问题其实非常朴素——怎么让普通摄像头拍到的视频,直接变成虚拟…

作者头像 李华
网站建设 2026/9/23 4:51:41

ONNX模型对比分析:性能差异定位与优化实践

1. ONNX模型对比分析的价值与挑战在工业级AI应用部署中,模型格式的兼容性直接决定了算法落地效率。ONNX(Open Neural Network Exchange)作为当前最流行的跨框架中间表示格式,其标准化程度直接影响着模型从训练到部署的迁移成本。但…

作者头像 李华
网站建设 2026/9/23 4:51:40

泉州卫生间防水维修电话|淋浴区渗漏排查处理|欧米到家咨询电话

📝 文章简介泉州住宅、商铺和办公场所常见的漏水问题,包括卫生间渗水、阳台积水、屋顶漏水、外墙返潮、厨房墙面发霉、窗边渗水、地下室潮湿等。欧米到家提供泉州多区域防水补漏、漏水点排查、局部修补、卫浴及水电相关维修服务。遇到雨后渗水、墙顶水印…

作者头像 李华