☰
Agent Skill设计模式:五种模式提升稳定性与可复用性
2026/10/19 11:05:39 网站建设 项目流程

做Agent开发有一段时间的同行,大概率都经历过这种场景:同一个功能的Skill,今天能跑通,明天换了批数据就开始胡说;这个项目里写得挺顺手的Skill,换个项目想复用,改了半天发现跟新需求根本对不上;还有更气人的,Agent执行任务的时候,明明给的是A目标,走着走着就拐到B方向上去了。

这些都是我在实际项目里反复踩过的坑,后来我总结出一个核心应对思路:把Software Engineering里的设计模式理念,平移到大模型Agent的开发里来,针对Skill做体系化的设计约束。这套方法我用下来,效果很直接——稳定性和可复用性明显提升,跑偏概率也低了很多。

这篇文章把这套方法论整理成5种Agent Skill设计模式,全部来自我在多个项目里的实操总结,不是那种停留在概念层的空中楼阁。每种模式都讲清楚它解决什么问题、怎么落地、有哪些坑,最后还给一个实战案例做组合演示,以及高频问题排查。不管你是刚开始接触Agent开发、还是会写一点但总觉得自己的Agent不可控,这套模式应该都能帮上忙。

1. 为什么要给Agent技能做设计模式

1.1 Skill是什么、为什么要“设计”

先说清楚一个基础概念。Skill(技能)在大模型Agent世界里,指的是一个可独立调用、能完成某一类具体任务的能力单元。它不是一个提示词就完了,而是一个完整包:目标描述、输入输出定义、调用逻辑、处理策略,有时候还包含底层的工具和API。

我见过不少人写Agent,就是把所有指令塞进一个大System Prompt里,然后靠模型“自觉”去完成各项任务。这种做法的结果,要么是互相矛盾的指令打架,要么是某一个功能特别依赖特定的表达方式,换个说法模型就理解不了。更麻烦的是,这种大胖子式的设计完全没办法测试——你根本不知道是哪个环节出了问题。

给Skill做设计,本质上是把“让模型自由发挥”变成“在约束下发挥”。模型确实有很强的生成能力,但这种能力如果不加引导,就像方向盘没有限位器的高速跑车,直线加速没问题,过弯全凭运气。Skill设计模式,就是把限位器装上去,让每个能力单元都在明确的边界里稳定跑。

1.2 三个痛点决定了模式的方向

我在项目复盘时总结过,Agent开发最频繁出现的三类问题:

第一个是不稳定。同样一个输入,今天返回的结果字段叫keyword,明天叫keywords;今天能给出结构化的JSON,明天给一段Markdown。下游程序一解析就报错,整个流程卡死。

第二个是不可复用。一个Skill在项目A里写得很好,到了项目B,因为输入的数据格式变了,输出需求也变了,只能推倒重来。说白了,就是Skill没有形成清晰的接口边界,和具体业务耦合得太深。

第三个是跑偏。Agent拿到一个任务后,在不该调用的场景调用了Skill,或者在正确的Skill里加入了不该有的推理步骤。典型的例子是:一个总结类Agent被问“列出这篇文章里的所有数字”,它不提取数字,反而给了一篇概括。

这五个模式,恰好就是围绕这三个痛点来设计的。契约先行解决“不稳定”,原子拆分和组合编排解决“不可复用”,意图门控和自省重试解决“跑偏”。我没有一上来就讲模式,是因为如果你不清楚它要解决什么问题,很容易在落地时生搬硬套,最后搞成四不像。

2. 五种Agent Skill设计模式逐一拆解

2.1 模式一:契约先行模式(IO Contract)

这个模式的核心一句话:调Skill之前,先定义好输入和输出的边界,再用代码强制校验。

我在早期写Skill的时候,习惯在提示词里写“请返回JSON格式,包含关键词列表和置信度”。看着没问题,但模型对“置信度”的理解千奇百怪,有的返回0.85,有的返回“高”,有的干脆不返回这个字段。后来我把输入输出定义成严格的结构,存到代码里,同时同步给模型,然后程序的解析不再“信任”模型,而是以校验结果为准,这个问题就根除了。

落地时,我推荐用Pydantic这类工具来定义Schema。示例是这样:

from pydantic import BaseModel, Field class ExtractSkillInput(BaseModel): text: str = Field(description="原始文本内容") max_keywords: int = Field(default=10, ge=1, le=50) class ExtractSkillOutput(BaseModel): keywords: list[str] = Field(description="提取出的关键词列表") confidence: float = Field(ge=0.0, le=1.0) extract_skill = Skill( name="keyword_extractor", input_schema=ExtractSkillInput, output_schema=ExtractSkillOutput, prompt="你是关键词提取专家...", )

代码里这个Schema,和给模型看的那个“接口文档”,必须严格保持一致。我在一个项目里就吃过亏:代码里校验keywords是数组,提示词里却写的是“返回用逗号分隔的字符串”,结果模型照提示词返回了字符串,每次校验都挂。

这个模式的本质,是给LLM的生成过程加上结构化的“栅栏”。语言模型擅长生成符合人类阅读习惯的内容,但在程序化接口面前,它需要的是严格匹配格式。我们通过Schema把“自由度”锁死,让它的输出只能在这些字段、这些格式里做选择。这就是稳定性的来源。

实操中还有几个要点:

  • 输入侧要做一层“转译”,用户说“帮我看看这篇文章”,需要先转成text字段,再传给Skill,这层转译通常由Agent的主控逻辑或意图路由来做。
  • 输出侧的校验必须“硬”:不合格就拒绝接受,反馈给模型让它重新生成,而不是做个“尽力而为”的解析。
  • Schema别设计得太复杂,超过五六个字段的大输出,模型很容易顾此失彼。如果业务确实需要很多字段,考虑拆成多个子Skill。

这里得特别说明:契约先行模式是后面所有模式的基础。没有它,原子拆分后节点之间的数据流转就是一句空话,组合编排时一个Skill的输出没法直接喂给下一个Skill。

2.2 模式二:意图门控模式(Intent Gate)

契约先行解决的是“输出格式”问题,接下来要解决的,是这个Skill到底该不该被调用。

设计多Skill Agent的时候,最容易出现的问题就是技能打架。比如我有一个“内容总结Skill”和一个“要点提取Skill”,用户说“总结一下这段话的重点”,理论上两个Skill都能干,但结果要是千差万别。调用错了,就是跑偏。

意图门控模式的做法,是给每个Skill装上一道“门”。所有想进入这个Skill的请求,先经过一道判断:这个任务是不是本Skill的菜?是,放行;不是,拒掉,并且给出建议转到哪个Skill。

这里给一段核心逻辑的示例:

async def intent_gate(skill, user_request): judge_prompt = f""" 判断以下用户请求是否属于技能“{skill.name}”的职责范围。 技能描述:{skill.desc} 用户请求:{user_request} 请返回JSON: {{ "belongs": true/false, "confidence": 0.0~1.0, "reason": "简短理由", "suggested_skill": "如果不属于,建议转给哪个技能" }} """ result = await call_llm(judge_prompt) # 阈值可以根据实际效果调整 if result.confidence < 0.7: return Reject(reason=result.reason, suggestion=result.suggested_skill) return Accept()

底层原理其实很朴素:LLM虽然处理能力强,但它缺少一个内置的“边界感”。你给一个技能定义了任务,它倾向于把所有请求都往自己这边拉。意图门控就是利用模型自身的判别能力,只不过把判断从隐性的、随机的,变成了显式的、前置的。

做意图门控,有三个关键点:

  1. 正负样本示范非常重要。在判别Prompt里给3到5个“应该调用”的例子,给3到5个“不应该调用”的例子,比你在描述里写“本技能只负责xxx,其他一律不响应”有效得多。模型是少样本学习的好手,你给它看例子,它就知道边界在哪儿。
  2. 阈值要反复调。阈值设太高,正常的请求被拒,用户体感就是Agent“变笨了”;阈值设太低,门控形同虚设,跑偏回潮。我一般先用0.7做初始值,然后用一批历史请求做回放测试,统计拒绝率和错误接受率,再微调。
  3. 拒绝也要给“出路”。这个模式不是让Agent拒绝任务,而是把任务转给对口的Skill。如果所有门都拒绝了,最后的兜底逻辑要能接住,比如交给通用对话Skill或者询问用户澄清。

我个人的经验是,意图门控给Agent带来的“不跑偏”效果,有时候比调主模型提示词更明显。因为你不再指望模型在开放式思考里自觉选择正确的分支,而是把“选不选”变成一个独立的、可测试的决策节点。

2.3 模式三:原子拆分模式(Atomic Skill)

如果说契约先行和意图门控更多是在“防守”,原子拆分就是真正的“进攻性设计”。它的原则也一句话:一个Skill只做一件能被一句话说清楚的事,能力复杂的就拆成多个Skill,让它们协作。

我见过最极端的反面案例,是一个同事把“撰写市场分析报告”做成了一个Skill,里面涵盖了数据收集、竞品识别、趋势判断、报告生成等一大堆环节。结果就是:每次调用回来,你要花很大力气去判断它到底在哪步出了问题;想在一个新场景下只复用其中“竞品识别”的能力,发现根本拆不出来。

原子拆分是应对这两种困境的标准做法。拿“数据分析报告”举例,不做一个巨型Skill,而是拆成这样:

  • 数据清洗Skill:输入原始数据,输出干净的结构化数据
  • 统计计算Skill:输入干净数据,输出统计指标
  • 结论生成Skill:输入统计指标,输出分析结论
  • 报告排版Skill:输入结论+格式要求,输出最终报告

每个Skill都独立可测、独立部署、任意替换。这个模式的价值,在我做第二个项目的时候才真正体会到。当时一个新需求只需要“统计计算”那部分能力,我直接把之前的统计Skill拿过来,配好输入输出契约,一个小时就接完了。复用,是因为拆得够小。

原子拆分的粒度如何把握,是最考验经验的地方。拆得太粗,回到老路;拆得太细,一次任务调用十几个Skill,延迟和成本都会飙升。我自己的经验法则是:

  • 如果一个Skill的描述需要用两句话以上才说得清楚,大概率要拆
  • 如果一个Skill的输入输出字段超过5到6个,考虑能不能拆开
  • 如果一个Skill里有“根据情况选择A或B”的逻辑,把A和B拆成两个Skill,用编排逻辑来选

原子拆分不是目的,组合才是。每一个原子Skill,都是给后面组合编排模式准备的一块乐高积木。积木质量不好,拼出来的东西就不牢靠;积木够纯、接口清晰,拼出来的流程就灵活多样、稳定可靠。

2.4 模式四:自省重试模式(Reflection & Retry)

契约、门控、拆分都到位了,Agent的Skill已经比较可靠了。但LLM毕竟是概率模型,总会有那么几次,它的输出不够好。自省重试模式,就是给Skill装上“质检员”,先把输出检查一关,不合格就退回去重做。

这个模式的灵感来自我日常写代码的习惯:写完一段代码,先跑一遍测试,不过就改,改完再跑,直到通过。那给Skill加测试校验,逻辑上完全一样。

实现上有两种搭配方式:

第一种是轻量自省,让模型自己对输出做检查。在执行完主任务后,再让它回答“你生成的结果是否满足这些要求?哪些不满足?”这适合定性判断,比如“报告是否覆盖了所有要点”“摘要是否做到了不偏离原文”。

第二种是独立验证器。用一个更便宜的模型,或者一条规则、一段正则,来硬校验输出。适合定量或结构性检查,比如“JSON格式是否正确”“是否包含必填字段”。

一个完整的自省重试循环,看起来是这样:

async def run_with_retry(skill, user_input, max_retries=3): output = None for attempt in range(max_retries): output = await skill.run(user_input) issues = await skill.checker.check(output) if not issues: return output, attempt + 1 # 把问题反馈给Skill,让它带着上次的错误继续修正 user_input.retry_feedback = issues return output, max_retries # 最后做个兜底返回

这个模式能显著提升稳定性,但要注意三个容易翻车的地方:

  1. 重试次数要限制。我一般限制在1到3次,超过就返回当前结果并打上标记。否则一个死循环式的自省会让Agent在错误里越陷越深,用户等半天得到个更糟的回答。
  2. 每次重试必须携带上次的失败原因。不然就是同一个错误反复犯。在Skill内部实现里,要把上轮的问题拼进新的调用上下文。
  3. 自省标准必须和用户目标对齐。我在一个项目里搞过“错对”自省,让模型检查“写的是否足够权威”,结果它把一篇数据详实但语气平淡的报告改成了空洞的官话连篇。检查器的高层标准,需要和真实任务目标反复校准。

自省重试的价值不仅在于“当场把错改对”,更在于它给了我们一个信号:如果一个Skill你反复触发自省重试还是失败,说明这个Skill本身设计有问题,要么边界不对,要么提示词不清楚。它为后续迭代优化提供了精确的靶子。

2.5 模式五:组合编排模式(Pipeline)

前四个模式都是在打磨单个Skill,组合编排模式则是把它们串起来成“流水线”。这也是可复用性的最高体现:一个技能流程,被模板化成一条 Pipeline,新场景来了直接复用流程框架,替换其中个别节点。

做组合编排,心里要有三件套:顺序、分支、并行。

  • 顺序最简单:A跑完跑B,上一个Skill的输出是下一个的输入
  • 分支让Pipeline有了决策能力:根据中间结果走不同分支
  • 并行适合互不依赖的任务:比如同时拉取多渠道信息,最后汇总

一段编排逻辑的示意:

pipeline = ( Pipeline() .add_stage("clean", data_clean_skill) # 顺序 .add_stage("analyze", stat_skill) # 顺序 .branch( condition="report_depth == 'detail'", if_true="detail_report_skill", if_false="summary_skill" ) # 分支 .add_stage("format", format_skill) # 汇总后格式化 .parallel( "check_a", quality_check_a_skill, "check_b", quality_check_b_skill, ) # 并行质检 )

编排层解决的一大问题,是数据格式的对齐。因为每个Skill都遵循契约先行模式,A的输出Schema就是B的输入Schema,节点之间无需额外转换就能流通。这也是为什么我把契约先行称为“基础中的基础”——没有统一的数据契约,Pipeline里每两个节点之间都要写转换代码,那这流水线还不如写传统程序来得快。

组合编排的其他好处,还包括:

  • 节点可替换:同一个Pipeline里,把“summary_skill”换掉,就能适配另一个领域的生成需求,其他节点完全不动
  • 可插拔:新增能力时,只需开发新Skill并在Pipeline里注册,不需要改其他节点
  • 可观测:每个节点有独立的输入输出,出问题能快速定位到阶段,而不是面对一坨整体逻辑

但也要提醒一点:Pipeline并非越复杂越好。层数多、节点多,带来的延迟和故障面也大。如果一个任务用一个Skill 2秒能跑完,你用20个环节的Pipeline跑20秒,那这个编排设计就是过度设计。我自己的习惯是,一个Pipeline的节点控制在3到7个之间,超过这个数就考虑分层或重新组织。

3. 模式组合实战:竞品分析Agent

3.1 场景与初始设计

理论讲完,拿一个实战场景把五套模式串起来看。我设计过的“竞品分析Agent”,需求是这样:输入一个产品名,输出一份多维度的竞品分析报告,比如竞品名单、各自优劣势、市场定位差异、风险提示。

如果走老路,直接一个提示词“你是市场分析师”,让模型自己编,那结果大概率是表面文章,甚至一本正经地编造不存在的竞品,跑偏得厉害。更合理的做法是用这5个模式组合成一条流水线。

3.2 Skill拆解与模式落地

第一步,原子拆分,把整件事拆成五个Skill:

Skill职责输入输出
搜索Skill根据关键词搜索相关产品信息查询词候选网页链接列表
摘要Skill将网页正文压缩成要点网页正文结构化摘要
竞品判定Skill判断某产品是否是真正的竞品摘要+目标产品是否竞品+理由
报告生成Skill汇总信息生成分析报告结构化的竞品列表和摘要完整报告
质检Skill检查报告完整性和一致性报告问题列表

第二步,每个Skill都用契约先行模式定义输入输出,这样它们能顺畅衔接。

第三步,给“竞品判定Skill”挂上意图门控模式。因为搜索结果不一定都是竞品,有很多相关但非竞品的页面,比如产业链上游、行业新闻之类。门控让它学会拒绝非竞品内容,避免把错误信息带进报告。

第四步,在报告生成之后接上自省重试模式。质检Skill检查报告是否覆盖了必答维度,比如“竞品名单”“优势劣势”“市场定位”等,缺了就退回去重写。

最后,用组合编排模式把五个Skill串起来:

analysis_pipeline = ( Pipeline() .add_stage("search", search_skill) .parallel("summarize", summary_skill, summary_skill, summary_skill) # 多个链接并行摘要 .add_stage("verify_competitor", competitor_gate_skill) # 意图门控在这里拦截非竞品 .add_stage("generate_report", report_skill) .add_stage("qa", quality_check_skill) # 自省重试 .with_retry_policy(stage="qa", max_retries=2) )

这条流水线每一节点都职责明确,任何一步出问题都能直接定位;换一个行业场景时,只需要替换对应的原子Skill和契约,框架不用动。这就是模式的组合红利。

3.3 选用矩阵速查

实际项目里到底怎么选模式,我给一个自己常用的速查矩阵:

目标核心模式辅助模式
提高输出稳定性契约先行、自省重试原子拆分
提升Skill复用率原子拆分、组合编排契约先行
防止Agent跑偏意图门控、契约先行自省重试
复杂任务落地组合编排全部四种
延迟成本敏感契约先行慎用自省重试和过深Pipeline

没有一套模式能解决所有场景,但多数情况下,契约先行是所有项目的基础,其他四种视目标按需组合即可。

4. 常见问题与排查经验

4.1 契约校验频繁失败,模型的输出总对不上Schema

这是契约先行模式落地时最常遇到的问题。原因通常有两个:一是提示词里的Schema描述和代码里的Schema不是同一份,模型照着提示词生成,程序按代码校验,自然对不上;二是Schema设计太复杂,模型顾不过来。

排查时,先把两个Schema做一次严格比对,确认字段名、类型、描述完全一致。然后降低输出字段数量测试,比如先只要求返回一个字段,成功后再逐步加字段。经验是字段越少,模型遵守得越好。这个排查逻辑很好用。

4.2 意图门控把正常请求也拒了,Agent变“呆”

遇到过好几次,加了意图门控之后,本来能做的任务它开始说“这不在我的职责范围内”。观察后发现是阈值设太高,加上判别Prompt里的反例示范太多,模型变得过度保守。

解决办法是把置信度阈值从0.7降到0.5,或者把反例去掉一两个。另外,少样本示范里增加一些“边界模糊但仍应接受”的用例,能有效帮模型理解“靠近边界的应该化归到我们这里”。这是一个反复校准的过程,多跑几次回归测试就稳了。

4.3 Skill拆得太细,一次任务调用十几次,延迟翻倍

原子拆分的理想是“小”,但过度拆分会导致编排节点过多,每个节点都有一次或多次模型调用,累计延迟非常可观。

我的处理办法是引入“聚合Skill”概念:把多个原子Skill在一个内部流程里打包,对外依然暴露为一个Skill。相当于在“原子”和“巨型”之间加了一层组合。这样外部调用方不用感知内部细节,延迟得到控制,内部的原子性又得到保留。

4.4 自省重试循环越改越差,甚至和用户目标背道而驰

这个问题的根源是自省标准定歪了。比如质检器检查“语气是否正式”,把一篇简洁明了的回答硬改成了面目全非的官腔。模型为了满足检查器的要求,反而牺牲了真正重要的内容质量。

正确做法是:自省检查器的标准和用户目标的一致性要拉通测试。最简单的方法是拿历史成功案例做“回归集”,每调整一次检查器Prompt,就用回归集跑一遍,看是否破坏成功案例的效果。不破不立,破了就得改回去。

4.5 Pipeline中途节点故障,整个流程失败

组合编排最怕单点故障。比如搜索Skill连不上外部API,Pipeline就直接卡死。解决办法有两条:一是给每个节点加超时和降级策略,超时就返回空结果或缓存结果;二是Pipeline的错误处理要灵活,允许“跳过失败节点”,用上一个节点的输出继续往下走,而不是一失败就全盘终止。我把这种做法叫“柔性流水线”,在真实项目中救过很多次场。

这里也可以给一个简单的排查步骤:遇到Pipeline失败,先看是哪个Stage出的问题,然后用该Stage的输入单独跑一次这个Skill,确认是Skill的问题还是编排层的问题。基于契约先行的设计,这个“单段试跑”做起来非常方便。

5. 最后聊几点个人体会

这五个模式从提出到定型,我自己也走了不少弯路。有一段时间我恨不得每个Skill全套五种模式,结果一个简单功能被包装得极其臃肿,不仅开发效率低,模型也被各种约束绕晕。后来才慢慢明白,设计模式是工具,不是信仰。该上模式的地方上,不必要的地方坚决不上。

如果让我给刚开始做Agent的同行一个建议,我不会说“先学全套”,而是说“先挑一个最让你头疼的问题”。觉得输出经常乱,就先把契约先行做好;觉得Agent老答非所问,就先加一道意图门控;觉得每个项目都在重复造轮子,就从原子拆分开始。一个一个来,等你把这五个模式都实践过,再回头看,你会发现自己的Agent开发方式已经和以前完全不同了。

还有一个我特别想分享的小技巧:给每个Skill维护一个“冒烟测试集”。不需要多,3到5条典型输入和对应的预期输出即可。每次改完Skill的Prompt或Schema,先跑一遍冒烟测试,没问题再放上线。这个习惯花不了多少时间,但能帮你避免大量“上线就崩”的尴尬。稳定不是天赋,是打磨出来的。

这套五模式框架我不敢说是唯一正确答案,但它是我在实践中验证过、确实有效的路径。如果你的Agent也常常不稳定、难复用、爱跑偏,不妨拿这套模式去试一试,结合自己的场景做裁剪。有更好用的设计思路,也欢迎多交流,这类经验本来就是越聊越厚的。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询