
做过一阵子LLM Agent应用的人应该都体验过那种Prompt越写越长效果越来越飘的绝望感。我自己的转折点发生在一次多工具任务的开发中把十几个功能说明、业务规则、输出格式全部堆到一个system prompt里后模型的指令遵循率肉眼可见地往下掉改一处需求就得连带调整所有措辞整个项目变成一坨牵一发动全身的面条代码。后来我换成了agent-skills的思路——把能力拆成多个可以被模型动态挑选的技能模块每个技能只吃自己的上下文、干自己的一件事情。最终系统不仅稳定了团队的扩展效率也高了很多。这篇文章不谈框架晒参数而是基于我实际项目中的方案选型和踩坑过程讲清楚Agent技能到底解决了什么问题、技能模块应该怎么设计、工程链路怎么搭以及我在实测里遇到的那些让人抓狂的模型跑偏情况。适合正在做Agent应用、被多任务调度和上下文管理折磨的朋友参考。1. 为什么单个Prompt撑不起复杂Agent从技能化的起因说起1.1 超长Prompt的三大硬伤先说个反直觉的结论Agent能力做加法系统稳定性往往做减法。很多人第一次搭建Agent时会像我一样把所有东西塞进Prompt——工具库有20个函数、业务约束有15条、输出格式有8种要求再配上几个few-shot示例系统提示词轻松超过3000 token。这种做法的硬伤是层层叠加的。第一关键指令会被淹没。Transformer的注意力机制不是均匀分布权重的上下文越长模型越容易把注意力集中在离生成位置最近的文本上。你的核心约束写在prompt开头模型读到后面早就忘了前面说过什么于是频繁出现不动用工具就直接编答案的情况。第二指令之间存在隐性冲突。比如你既要求优先使用检索工具获取最新信息又要求回答必须简洁模型在权衡时往往做出反直觉的选择——它可能跳过检索直接凭训练数据回答因为简洁这个约束更贴近生成末端、权重更高。这种冲突靠调整措辞很难根除因为不同指令之间是隐式相互影响的。第三不可测试、不可复用。整个Prompt是一个黑盒今天改一行字可能全盘崩掉明天加一个新工具又要重写所有示例。业务新增一个功能时你不得不重新评估整套Prompt而不是只加一个模块。1.2 把Prompt拆成技能的本质agent-skills的核心思路其实不复杂把解决某一类任务的完整能力——包括前置条件判断、处理流程、工具调用、输出格式——封装成一个独立的、可命名的模块让Agent根据当前任务动态挑选并调用这些模块。我习惯用一个工具箱类比。过去的做法是把所有工具的说明书、所有业务规则、所有注意事项复印成一本厚手册让一个新手模型临时翻查技能化的做法是给每个工具配一个熟悉它的专职师傅新手只需要看一眼师傅胸前的标签技能描述与触发条件就能决定现在请哪位师傅出马。师傅内部怎么干活、需要什么资料、产出什么格式都封装在内部不需要新手理解全部细节。这个思维转变带来两个关键收益一是上下文锐减模型每次只需要看到与当前任务相关的技能描述和执行上下文不再被无关指令干扰二是能力可组合一个复杂任务可以拆成多个技能的编排调用每个技能独立演进、独立测试就像乐高积木一样。1.3 Skill和Function Call不是一回事这里必须澄清一个常见误区很多人以为Agent技能就是把Function Calling封装一下换个名字其实两者的抽象层级完全不同。Function Call是原子操作对应的是调用一个API查询一次数据库执行一段代码这类不能再拆的动作。而Skill是更高阶的组织单元它内部可以串联多个工具调用、包含判断逻辑和中间推理过程甚至可以在自己处理不了时主动请求外部介入。举一个具体场景假设要实现根据用户描述判断天气是否适合户外活动并给出建议。拆成Function Call你需要让模型自己决定先调天气API、再调地理编码API、再根据结果写建议——模型承担了所有编排和判断逻辑出错点非常多。但如果封装成一个assess_outdoor_activity技能技能内部定义好先解析地点、再查天气、再结合天气阈值给出活动建议的完整流程模型只需要决定要不要用这个技能判断负担大大降低。这也揭示了技能设计的本质把需要推理和判断的逻辑从模型调用的运行时前移到设计时用工程手段固定下来降低模型的不确定性输出范围。2. 技能体系的顶层设计粒度、接口与解耦规则2.1 技能粒度一个技能只做一件完整的事技能粒度是新手最容易踩的坑。拆得太细模型陷入选择困难症每步都要在小技能之间跳转上下文开销和决策失误率反而上升拆得太粗技能内部又变成一个mini版超长Prompt换汤不换药。我的经验标准有三条内化成判断口诀技能边界是否对应一个用户可感知的完整需求。比如翻译文档整理会议纪要查询订单状态都是一句话说得清的完整需求适合作为技能边界。技能内部是否需要多种工具/多步操作协作。如果只需要一步API调用那它就是Function Call的活不该被升级成技能。技能能否独立测试。你能单独给这个技能造测试用例、跑通并断言结果它就是一个合理的技能如果你要搭整套环境才能测说明它耦合了太多外部依赖。拿我自己的项目举例我把商品推荐拆成了search_products、rank_by_rating、generate_recommendation_text三个子技能结果模型在它们之间频繁跳转还在选哪个技能先跑上反复纠结最终输出质量惨不忍睹。后来合并成一个recommend_products技能内部封装检索、排序、文案三个环节成功率直接翻倍。正因为技能内部可以容纳多步骤逻辑这种合并是合法且必要的——模型只负责何时用不负责怎么用。2.2 用JSON Schema定义每个技能的输入输出技能接口必须显式化这是铁律。我给每个技能设计一个结构化的接口卡包含四段信息name技能唯一标识命名用动词开头例如search_order_returnsdescription说明技能用途、适用场景、触发条件、典型输入这是模型选中技能的依据parametersJSON Schema格式声明入参包含类型、必填项、取值范围、约束条件returns声明输出结构让模型知道技能会返回什么格式、哪些字段可用于后续回答接口卡的核心价值是把模型的自由发挥空间压缩到最小。有了严格的参数Schema模型在调用技能时就必须按照既定要求构造入参而不是自由发挥出一堆中间字段有了明确的输出结构后续答案生成的上下文衔接也更顺畅。如果你的技能体系里出现返回值字段随时间变化的接口那这个技能一定还没设计成熟。2.3 技能与技能之间禁止隐式依赖技能设计的另一个重要原则是松耦合。技能A不能假设技能B一定先跑过并且把某状态写入某个公共变量每个技能都应该是无状态的、只吃自己的显式入参、只产出自己的结构化输出。这个设计原则的直接好处是可以随意增删技能而不引发连锁故障。如果一个技能依赖另一个技能的运行状况那么模型选择技能的路径就被锁死了——必须先跑A再跑B本质上又是把编排逻辑塞回模型脑子里和对冲掉的超长Prompt问题如出一辙。跨技能的数据传递正确做法是通过显式的数据流上一个技能的输出结构经过适当裁剪或转换后作为下一个技能的入参。这段转换逻辑可以放在编排层后面第五部分详述而不是让技能间互相读取内部状态。3. 技能仓库、注册与动态检索工程链路怎么搭3.1 构建技能仓库与基础结构理想情况下你的技能目录应该长这样skills/ ├── recommend_products/ │ ├── SKILL.md # 接口卡与说明 │ ├── execute.py # 执行逻辑 │ └── tests.py # 单测 ├── schedule_meeting/ │ ├── SKILL.md │ ├── execute.py │ └── tests.pySKILL.md是技能的门面它决定了模型会不会选中这个技能。它的内容不等于代码注释而是一份写给LLM看的说明书要站在模型的视角写清楚什么情况下该用我、什么情况下不该用我、我的输入需求是什么、我输出什么。我建议在SKILL.md里专门加一个不宜使用的场景小节。这听起来反直觉但实测非常有效模型在决策时往往会高估技能的适用边界明确写出不适用场景能显著降低误调用率。比如一个生成活动文案的技能明确写上当用户仅询问天气时不要调用本技能模型就会更谨慎地触发它。3.2 技能检索先过滤再排序当技能数量超过10个时把所有技能描述都塞给模型既不经济也不明智。我采用两阶段检索策略。第一阶段是粗筛用关键词向量混合召回技能描述文本做向量化入库用户当前任务也做向量化召回TopK候选同时做关键词匹配防止向量召回漏掉专有名词。这一阶段的目标是快速把候选集从几十个缩到3-5个。第二阶段是精排结合任务类型和技能间的互斥关系做一次轻量过滤。比如用户任务是查询订单那么和售后流程相关的技能虽然语义相近也应该在这一步被剔除如果用户任务明确属于某个领域可以优先保留该领域的技能。精排之后把剩下技能的完整接口卡交给模型决策。这套两阶段方案的好处是模型每次看到的技能描述总量可控决策空间小选错技能的概率自然下降。我在最初把所有技能一股脑塞给模型时一个明显的问题是模型会被相似技能带偏——它分不清search_products和search_products_with_filter的边界每次都要碰运气。加上检索过滤之后这种混淆问题缓解了非常多。3.3 调用闭环解析、校验、回填与压缩技能调用的工程闭环分四步解析模型输出模型返回结构化调用指令JSON格式解析出技能名和参数。参数校验用JSON Schema校验参数完整性——必填字段是否齐全、格式是否正确、枚举值是否合法。校验失败时不是直接报错而是把缺失字段的信息回传给模型让它补充后再试一次。执行技能运行execute.py获取结构化结果。结果回填与上下文压缩把执行结果与用户的原始问题一起交给模型生成最终答案同时将当前对话轮次的中间状态做压缩归档避免技能执行过程中的调试日志、中间计算字段灌入下一轮的上下文。第4步是大多数人容易忽略的工程细节。技能执行过程中会产生大量中间数据——API返回的原始JSON可能就是几千token如果全量塞回上下文几轮对话下来又回到了上下文爆炸的老路。我的做法是由技能自己声明哪些字段是最终回答需要的核心信息在returns里标记编排层只把核心信息回填给模型涉及多步技能调用的场景则把每步的产出精简成一条摘要记录而不是完整数据。4. 实测中的翻车现场Agent跑偏的四种典型场景与对策4.1 模型无视技能直接凭记忆回答这是最让人头疼的翻车场景——技能体系搭好了模型却压根不调用直接发挥LLM的记忆脑补能力给出一个看起来合理、但实际数据失真的答案。在一次订单查询测试里用户问我的订单什么时候发货模型直接回复预计3个工作日内发货而实际系统里根本没有这个订单正确动作应该是调用query_order_status技能去查数据库。我排查后定位到两个根因。第一技能描述与用户问法之间的语义连接不够直接模型没有意识到问发货时间属于查询订单状态的触发范围我在描述里补充了典型的用户问法变体用几个具体例句让匹配关系更明显。第二系统的system prompt里有类似你可以直接回答热门问题的宽松设定模型在懒惰模式下先答为敬我删掉了这类鼓励自由发挥的表述改成了未知信息必须通过技能查询获取的硬性约束。4.2 参数幻觉模型编造了不存在的入参即使模型正确选中了技能也可能在构造参数时撒谎。典型场景发生在用户只提供了部分信息时比如用户说帮我订一张明天的机票但没说起飞地和目的地。模型如果自作聪明地填上了from北京、to上海后面整个流程都会在错误数据上运行。对策分两层。第一层是Schema约束在parameters里把from和to标记为required: true同时用描述注明必须在用户明确提供后填写缺失时不得猜测。第二层是对话式补全在解析层检测到必填参数缺失时自动生成一个追问——请问出发地和目的地分别是哪里而不是直接把错误参数传给技能执行器。这里有个细节追问文本也应由模板生成避免模型自由发挥出一堆不必要的解释。4.3 相似技能间的选择抖动有一段时间我的系统里同时存在generate_article和summarize_article两个技能模型经常在新写文章的任务上误调用后者导致输出变成对一篇不存在文章的综合概括。这种相似技能选择抖动的问题在技能库越来越大的时候几乎必然出现。我的解决思路有三个层次。首先是描述差异化在两者的SKILL.md里刻意使用互斥的触发词——generate_article声明只能在用户要求创作新内容时使用summarize_article声明只能在用户提供原文或引用已有文本时使用。其次是保留一两个代表性的few-shot对话作为选择示例帮助模型形成稳定的决策模式。如果还不行第三层就是在精排阶段根据任务意图做硬性互斥检测到创作意图时直接过滤掉summarize_article。4.4 技能内部死循环与无意义重试还有一个隐蔽的问题技能执行器内部如果对某个外部API的返回结果不满意超时、空数据、格式异常可能会自动重试。如果重试逻辑写得太激进就会出现API请求超时-重试-再次超时的死循环白白消耗资源和时间预算。我踩过一次大坑一个天气查询技能在API无返回时默认重试三次而当时API服务整体降级三次重试全部失败后技能又抛出一个异常编排层收到异常后让模型换个方式再试模型再次选择了同一个技能最终整个任务在无意义的循环里跑了将近40秒才被兜底逻辑截断。对策是给技能加两层防护统一设置外部调用超时单次不超过5秒并且重试必须有退避策略和次数上限最多两次第二次间隔拉长在编排层设定调度轮数上限比如最多5轮工具调用超过阈值直接终止流程并向用户返回暂时无法完成请稍后重试的兜底响应。这个兜底文案最好也在模板里写死别让模型自由发挥否则它可能编一个比真实原因离谱得多的失败解释。5. 多技能协作从单兵作战到编排协同5.1 三种编排模式的适用边界当任务横跨多个技能时编排方式直接决定系统质量。我用过三种模式各有各的适用场景。流水线模式适用于阶段性固定的任务比如先查天气再根据天气推荐活动——技能A的输出直接作为技能B的入参顺序固定、流程确定。这种模式实现最简单但灵活性差不能应对分支情况。Supervisor模式让主模型扮演管理者角色先分析用户意图、拆解子任务再分派给不同技能并汇总各技能的结果。这是最灵活的模式适合开放性任务但对主模型的理解和规划能力要求很高而且多轮调度会累积上下文开销。我在客服场景中采用的就是这种模式主模型先判断用户是想查订单、退换货还是投诉再选具体技能执行。协作模式是多个技能并行执行、结果合并。适合任务本身可以拆成几个独立子任务的场景比如同时检索技术资料和行业报告汇总后写一份综述。并行执行能节省时间但要注意合并阶段的冲突问题——不同来源的信息可能矛盾需要额外的仲裁逻辑。5.2 让技能学会向上求助比起技能一定能独立解决所有问题的理想假设我更倾向于在设计技能时给它开一个求助通道。技能执行过程中发现数据缺失、依赖条件不满足、外部服务不可用时不是硬着头皮返回错误而是返回一个结构化的求助信号说明我在这一步无法继续需要补充XX信息或者转人工处理。实测中这类求助信号的价值很大。它把失败从系统性崩溃变成可恢复状态——编排层收到求助后可以生成追问向用户索取信息或者把当前任务转给另一个更合适的技能。没有这个机制之前技能执行失败时模型会尝试各种奇怪的补救动作反而把问题越搅越乱。我在技能输出的统一格式里专门放了一个status字段枚举值包括success、needs_more_info、failed、abort。执行异常时技能返回failed并附上原因摘要无法独立完成但知道缺什么时返回needs_more_info并列出缺失的具体信息。编排层读到这些状态码做对应处理非常直观也方便后续打日志分析。5.3 可观测性技能调用的日志与追踪最后想强调一个容易被忽视的工程环节可观测性。技能化之后系统链路变得多跳且复杂——一个用户请求可能经历了模型决策-技能A-技能B-模型汇总哪一跳出了问题都需要能够快速定位。我要求每个技能执行时都输出结构化日志包含技能名、入参、出参、执行耗时、调用的外部接口、中间关键步骤状态。编排层再记录一条调用链把用户请求ID贯穿所有环节。排查问题时只需要按照请求ID拉出完整的技能调用链立刻能看出是哪一步耗时异常、哪一步返回了错误。这套日志体系在调试阶段帮了我大忙。特别是上面提到的模型选中了技能但参数幻觉的问题如果没有dump出完整入参我根本不知道模型往参数里填了什么离谱值。建立可观测性后这类问题从偶发灵异事件变成了可以在测试阶段就被发现并修复的常规bug。再分享一个和可观测性配合的调试技巧在设计阶段就为每个技能准备一个离线测试命令把技能执行器从Agent链路中独立出来固定输入跑一遍断言输出。做到每个技能可以独立使用命令行复现你才敢在Agent里放心地调度它。毕竟一个连单独跑都跑不稳的技能放进复杂链路里只会制造更多的噪音与混乱。Agent技能化这条路走了大半年的实际体感是它的核心价值不止于提升模型输出的稳定性更是把怎么完成任务这份隐性知识从模型脑海里搬到了工程系统里变成可测试、可维护、可迭代的资产。每个技能都是一个独立的小闭环各司其职、边界清晰Agent主模型只负责调度和权衡。把这句话想通之后再回头看那些吃力的超长Prompt会觉得当初的纠结其实来自抽象层级选错了——不是Prompt没写好而是压根不该靠单个Prompt扛下所有复杂度。后续如果你想往更深走可以继续在这个体系上叠加技能自动生成、基于用户反馈的技能质量评估甚至是技能的市场化共享——不过那就是另一个值得单独复盘的话题了。