做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(default10, ge1, le50) class ExtractSkillOutput(BaseModel): keywords: list[str] Field(description提取出的关键词列表) confidence: float Field(ge0.0, le1.0) extract_skill Skill( namekeyword_extractor, input_schemaExtractSkillInput, output_schemaExtractSkillOutput, 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(reasonresult.reason, suggestionresult.suggested_skill) return Accept()底层原理其实很朴素LLM虽然处理能力强但它缺少一个内置的“边界感”。你给一个技能定义了任务它倾向于把所有请求都往自己这边拉。意图门控就是利用模型自身的判别能力只不过把判断从隐性的、随机的变成了显式的、前置的。做意图门控有三个关键点正负样本示范非常重要。在判别Prompt里给3到5个“应该调用”的例子给3到5个“不应该调用”的例子比你在描述里写“本技能只负责xxx其他一律不响应”有效得多。模型是少样本学习的好手你给它看例子它就知道边界在哪儿。阈值要反复调。阈值设太高正常的请求被拒用户体感就是Agent“变笨了”阈值设太低门控形同虚设跑偏回潮。我一般先用0.7做初始值然后用一批历史请求做回放测试统计拒绝率和错误接受率再微调。拒绝也要给“出路”。这个模式不是让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_retries3): 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到3次超过就返回当前结果并打上标记。否则一个死循环式的自省会让Agent在错误里越陷越深用户等半天得到个更糟的回答。每次重试必须携带上次的失败原因。不然就是同一个错误反复犯。在Skill内部实现里要把上轮的问题拼进新的调用上下文。自省标准必须和用户目标对齐。我在一个项目里搞过“错对”自省让模型检查“写的是否足够权威”结果它把一篇数据详实但语气平淡的报告改成了空洞的官话连篇。检查器的高层标准需要和真实任务目标反复校准。自省重试的价值不仅在于“当场把错改对”更在于它给了我们一个信号如果一个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( conditionreport_depth detail, if_truedetail_report_skill, if_falsesummary_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. 模式组合实战竞品分析Agent3.1 场景与初始设计理论讲完拿一个实战场景把五套模式串起来看。我设计过的“竞品分析Agent”需求是这样输入一个产品名输出一份多维度的竞品分析报告比如竞品名单、各自优劣势、市场定位差异、风险提示。如果走老路直接一个提示词“你是市场分析师”让模型自己编那结果大概率是表面文章甚至一本正经地编造不存在的竞品跑偏得厉害。更合理的做法是用这5个模式组合成一条流水线。3.2 Skill拆解与模式落地第一步原子拆分把整件事拆成五个SkillSkill职责输入输出搜索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(stageqa, max_retries2) )这条流水线每一节点都职责明确任何一步出问题都能直接定位换一个行业场景时只需要替换对应的原子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连不上外部APIPipeline就直接卡死。解决办法有两条一是给每个节点加超时和降级策略超时就返回空结果或缓存结果二是Pipeline的错误处理要灵活允许“跳过失败节点”用上一个节点的输出继续往下走而不是一失败就全盘终止。我把这种做法叫“柔性流水线”在真实项目中救过很多次场。这里也可以给一个简单的排查步骤遇到Pipeline失败先看是哪个Stage出的问题然后用该Stage的输入单独跑一次这个Skill确认是Skill的问题还是编排层的问题。基于契约先行的设计这个“单段试跑”做起来非常方便。5. 最后聊几点个人体会这五个模式从提出到定型我自己也走了不少弯路。有一段时间我恨不得每个Skill全套五种模式结果一个简单功能被包装得极其臃肿不仅开发效率低模型也被各种约束绕晕。后来才慢慢明白设计模式是工具不是信仰。该上模式的地方上不必要的地方坚决不上。如果让我给刚开始做Agent的同行一个建议我不会说“先学全套”而是说“先挑一个最让你头疼的问题”。觉得输出经常乱就先把契约先行做好觉得Agent老答非所问就先加一道意图门控觉得每个项目都在重复造轮子就从原子拆分开始。一个一个来等你把这五个模式都实践过再回头看你会发现自己的Agent开发方式已经和以前完全不同了。还有一个我特别想分享的小技巧给每个Skill维护一个“冒烟测试集”。不需要多3到5条典型输入和对应的预期输出即可。每次改完Skill的Prompt或Schema先跑一遍冒烟测试没问题再放上线。这个习惯花不了多少时间但能帮你避免大量“上线就崩”的尴尬。稳定不是天赋是打磨出来的。这套五模式框架我不敢说是唯一正确答案但它是我在实践中验证过、确实有效的路径。如果你的Agent也常常不稳定、难复用、爱跑偏不妨拿这套模式去试一试结合自己的场景做裁剪。有更好用的设计思路也欢迎多交流这类经验本来就是越聊越厚的。