
1. 从“Agent 玩成 Prompt 堆砌大赛”说起先交代一下背景。我之前做过不少 LLM 应用最早的版本特别简单把所有能力、规则、人物设定、工具说明全部塞进一个 8000 字的 system prompt 里靠模型自己去“理解”。一开始效果还行但随着功能变多问题一个个冒出来上下文被无关内容挤占关键指令反而被稀释改一个业务规则要动整个 prompt改完这轮正常、下轮莫名失效Token 成本更是肉眼可见地涨。后来我换了个思路不再试图让模型“天生就知道一切”而是把 Agent 的能力拆成一个一个独立的 skill每个 skill 管一件事查天气一个、算运费一个、写周报一个。模型只负责判断“现在该调用哪个技能”具体怎么执行交给技能内部完成。这套做法我整理成了一个小项目就叫 agent-skills。说白了agent-skills 不是什么神秘框架它是一套把 Agent 能力工程化的方法论加落地代码定义技能、注册技能、触发技能、编排技能。只要你的应用是基于 LLM 的 Agent不管是做客服、做数据分析、做内容生成这套思路都能帮你把混乱的 Prompt 逻辑收敛成可维护、可测试、可观测的模块。下面我按实际搭建的顺序把我踩过的坑、验证过的参数、最终跑通的方案都写出来东西不复杂但细节不少希望能给准备动手的人省点时间。2. 技能库的三个核心抽象意图描述、参数契约、执行体我在最开始想得很简单以为 skill 就是一个函数模型调用函数就完事了。真做下去才发现如果只给模型一堆函数签名效果很差模型经常不知道该选哪个或者选对了但参数传错。后来我把每个 skill 拆成三部分才算真正稳定下来。2.1 为什么要拆成三件套先说结论Agent 的技能调用本质上是一次“意图识别 参数绑定的决策过程”。你只给模型一个函数名和参数列表模型是在“盲选”你给它一份结构化的技能说明它才能“有依据地选”。这三件套分别是意图描述intent用自然语言说明这个技能是干什么的、什么时候该用它、什么时候不该用它参数契约schema定义调用这个技能需要的字段、类型、约束条件执行体executor真正干活的代码可以是本地函数、外部 API 封装、甚至是另一个子 Agent我在 agent-skills 里用一个 YAML 文件来声明技能结构大概是这样的skills: - name: order_query description: 查询订单状态和物流进度 intent: | 当用户询问订单是否发货、物流到哪了、预计什么时候到达时使用此技能。 当用户只是闲聊或询问商品推荐时不要使用此技能。 parameters: type: object properties: order_id: type: string description: 订单编号通常是一串数字 required: - order_id executor: functions.order_query这个声明文件是给“人”看的也是给“模型决策层”看的。我在接入模型之前会把这段 YAML 转换成模型厂商兼容的 function calling 格式转换逻辑很简单就是把 description、parameters 原样透传但 intent 字段会放到系统提示词里的技能索引区因为模型看长文本说明的效果通常比只看函数签名更好。2.2 意图描述怎么写才有效这部分我调整过很多次最开始的写法是“本技能用于查询订单信息”太笼统模型经常误用。后来参照搜索引擎的查询扩写思路把意图写成“正例 反例 边界条件”三段式。正例一定要带动词和对象比如“查询订单状态”“修改收货地址”“生成周报”。反例要写清“不要做什么”比如“当用户只是表达不满但没有明确要求退款时不要调用退款技能”。边界条件要说明数据范围比如“只支持查询最近三个月的订单”。一个写得好的意图描述能把技能误触发率从百分之二三十降到百分之五以下。这个数字不是拍脑袋我在后面加了一组对照测试同样的测试集宽泛描述的误触发率明显高很多。所以写 intent 时千万别偷懒正例反例边界都写全模型才能少犯错。2.3 参数契约的取舍参数契约是我踩坑最多的地方。一开始我照着 OpenAPI 规范写每个字段都加 constraints还设了各种 enum。结果模型调用失败率很高经常因为传了一个不在枚举里的值而报错。后来我把规则收敛成三条必填字段尽可能少能只留一个 order_id 就绝不多要一个 user_id枚举值只在业务硬性要求时才设置给 description 写明取值示例模型参考示例填参的准确率比我预想的高很多另外有个小技巧如果某个参数可以从上下文推断就不要让模型猜。比如用户在一段多轮对话里已经说过“我是会员”后续的查询技能就不需要再要求传会员等级直接在编排层把上下文信息注入进去。2.4 执行体最不被重视但最容易出问题的地方执行体不建议直接写太重的业务逻辑。我的习惯是执行体只做三件事拼接口请求参数、调外部服务、把结果整理成适合 LLM 阅读的摘要。这里要注意的是给模型返回的内容不要是原始 JSON 堆砌最好先做字段筛选只保留模型回答用户问题时真正有用的信息。比如查天气的接口返回一百多个字段你全丢给模型它虽然能读懂但会浪费 token 而且容易抓住次要信息。我在执行体里会预处理成一句话摘要当前温度 23 度多云东南风 2 级未来两小时无明显降水。这一步直接决定了后续生成回复的质量。3. 触发链路设计让 Agent 在合适的时机选中合适的技能技能定义好了接下来要解决“什么时候触发哪个技能”的问题。这是 agent-skills 里最核心的消息通路也是我最想分享的部分因为这里面的参数调节直接决定整个系统的可用性。3.1 三种触发方式的路线对比我试过三种触发路线各有优劣。第一种是硬编码规则路由比如用户消息里包含“订单”两个字就查订单表实现简单但泛化差换个说法就漏。第二种是纯语义检索把所有技能的 intent 描述向量化用户消息实时算 embedding取相似度最高的技能。第三种是混合式先用语义检索召回 Top-3再用一个轻量分类 Prompt 从候选里做最终决策。我最终用的是混合式。原因很简单纯语义检索会偶发“高相似度但事实不对”的情况比如用户说“我不想查订单了”语义上跟订单查询技能高度相关但意图是取消查询。加一层轻量分类 Prompt 之后它会先判断用户当前是否真有查询意图再决定是否调用误判率降了一个数量级。3.2 语义检索的嵌入模型与向量库选型嵌入模型我用过通用型文本向量模型也用过针对短文本优化的开源模型。实测下来技能匹配场景大多是短查询对短描述用视中文效果波动的通用模型反而容易跑偏最后换成了一款针对搜索优化的嵌入模型匹配准确率提升明显。向量库方面中小规模技能库几十到几百个技能用成熟的开源向量数据库完全够用不需要上大规模分布式检索服务。检索时最重要的参数是两个召回数量 top_k 和相似度阈值。我最终设置在技能数量五十个左右时top_k3、相似度阈值 0.35 效果比较均衡。下面是我在测试集中记录的一组表现对比能够看出阈值设太高虽然精确率高但很多该触发的情况被拦下了设太低则乱七八糟的技能都会被召回来相似度阈值精确率召回率实测体感0.2042%95%大量无关技能混入决策层负担重0.3071%88%有少量噪声但能靠决策层兜住0.3586%79%均衡推荐起始点0.4594%51%漏触发明显用户常见说法被拦表格里这个“实测体感”只有你真跑了才会懂阈值低的时候问题不是模型傻而是语音干扰太多阈值高的时候问题变成用户换个句式技能就罢工了。所以我不建议你照抄我的 0.35而是拿自己的测试集跑一遍看清自己的技能描述分布再调。3.3 决策层的兜底校验逻辑召回层之后决策层不能只是简单“选相似度最高”我加了三道校验当前用户消息是否承接了上文的否定或撤销意图技能意图描述里是否包含与当前消息冲突的边界条件必填参数是否齐全不齐时是否已经在上下文中能补齐只有三道校验全部通过才真正执行技能。这样做会多一点额外 token但带来的稳定性提升非常明显。特别是第一道它解决的“不想做某事”问题是单一 Embedding 无法判断的加一道规则级校验最高效千万别省。3.4 触发失败时的降级策略最后是降级。技能匹配不上时不能直接回“我无法处理”我准备了三个梯度第一梯度是反问澄清比如用户问“那我的东西到哪了”没有提到具体订单就反问订单号第二梯度是通用技能兜底比如默认的闲聊响应第三梯度是人工转接。这三个梯度按顺序走用户体验比僵硬报错好得多。4. 多技能编排从单技能调用到一串任务的调度单技能的触发跑通之后真正的复杂度在多个技能之间的编排。我在 agent-skills 里实现了几种编排模式实际使用频率最高的是动态编排和串行流水线各有适用场景。4.1 串行编排与并行编排的适用边界串行编排适合任务步骤明确、前一步输出是后一步输入的场景。比如“查天气后生成穿衣建议”必须先查完天气才能写建议天然串行。并行编排适合多个独立查询互不依赖的场景比如用户问“北京和上海明天都什么温度”两个技能调用互不影响并行执行可以节省整体耗时。我实现串行时给每个节点加了输入输出字段映射前一个技能的输出摘要会被写入后续技能的上下文前缀。实现并行时的技巧是控制并发后的合并节奏所有分支返回后再统一汇总给上层决策。并行如果某个分支超时要能单独降级而不影响其他分支结果。4.2 上下文在技能之间如何流转这是做多技能编排时最容易乱的地方。我在 agent-skills 里的做法是引入一个会话级 Memory Buffer但 Buffer 不是无脑全存而是分三层全局层存用户身份、历史摘要、长期偏好技能层存每个技能上次调用的结果摘要临时层存当前这个完整请求内部的中间产物技能执行时只能读取全局层和自己对应的技能层临时层只在前端编排链路里存在技能结束后按需合并进技能层而不是丢进全局层。这个隔离设计我是在一次故障之后才想明白的最初所有技能共享一个大 context结果查天气的技能把上一轮“用户想退款”的临时信息也读进来了生成的引导话术直接跑偏。4.3 多个技能都想接管同一个请求时怎么办技能数量到几十个之后一定会出现“一个请求触发两个技能都很合理”的情况。比如用户说“帮我推荐一部电影顺便告诉我附近哪家影院最近”电影推荐和影院查询两个技能都被命中。我的解决思路是给技能加上优先级和互斥组声明在决策层做冲突裁决。互斥组的逻辑比较简单同一批技能共享一个互斥标志同时命中时按业务预设的优先级取最高者优先级相同时再走一个轻量决策 Prompt。优先级这件事建议从写技能声明时就规划不然后期加技能容易顾此失彼。4.4 Token 成本与延迟的平衡技能编排跑起来之后第二个现实问题是成本。每个技能调用都要消耗输入 token如果编排层级深一个请求的 token 消耗可能是单技能的好几倍。我做了两个措施中间结果摘要化技能返回的完整数据只保留 200 字以内的摘要进入下一环节完整数据存本地日志需要时再查全局 Token 预算在每个请求入口设一个阈值比如 8000 token超过时高级编排自动退化成“只做一次主技能调用”这两个措施相结合整体成本大约下降了 40%而效果几乎没有可见性损失因为模型只要拿到摘要就能生成合理回答真正需要完整数据的场景大多数在本地就能解决。5. 技能系统的工程底座持久化、可观测与回归验证技能多了之后最怕的就是“这次改了一个技能的 description另一个技能的触发率掉了”。不把观测和回归做起来你根本无法定位这种隐性回归。这一部分是我认为 agent-skills 从玩具变成工具的分水岭。5.1 技能调用日志应该记哪些字段我用的日志结构和传统的接口请求日志不太一样重点关心的是链路本身。每条技能调用记录包含这样几个字段session_id会话唯一标识request_id用户当前这一条消息的唯一标识一次消息可能触发多次技能调用skill_name实际调用的技能名matched_score召回层给的相似度分数decision_source是语义命中、规则命中还是降级兜底llm_round决策层第几轮生成latency_ms技能执行耗时input_tokens / output_tokens消耗 token 数raw_input / result_summary原文和返回摘要每个字段都能回答一个具体问题缺哪个都会在排查时抓瞎。比如 matched_score 字段能帮你发现某技能误触发是因为召回层分数虚高还是决策层没拦住latency_ms 能暴露执行体里某次外部 API 超时的规律。日志我建议直接落到本地日志服务别只存在内存里否则复盘时没数据可用。会话维度再用一张表做聚合reduce 出每个技能调用成功率和平均耗时。5.2 可观测性一次跨技能请求的全链路追踪当一次请求触发了“查订单 → 查物流 → 生成进度说明”三个技能时我需要能一次性看到整条链路。我的做法是引入 trace_id一次用户消息生成一个 trace_id所有技能调用记录都挂在这个 trace_id 下面同时记录 parent_skill_id 表达父子关系。排查问题时我直接按 trace_id 拉出整条链路的时间线一眼就能看出哪一段慢、哪一段失败。有一次线上问题就是物流查询技能偶发超时单看技能日志只看到 latency 波动很大拉出链路才发现是并发场景下多个请求同时打同一个外部接口触发限流。这类问题不靠链路视图根本看不出来。5.3 技能级回归测试集我建了一个精简但有效的回归测试集大概一百多条测试用例每个用例包含用户输入、期望触发的技能名、期望不触发的技能名。每次修改技能声明或者调了检索参数之后跑一遍测试集对比通过率。这个测试集的价值在于它能比较客观地告诉你“这次改动有没有影响其他技能”。我遇到过最典型的情况是给退款技能加了新的意图描述之后发票技能在测试集里的误触发率到了 8%。如果没有回归测试这个问题在线上可能要好几周才能被发现。测试集的编写也有技巧不要只写正例要大量写反例特别是那些“看着该触发但其实不该触发”的句子。反例比例我建议至少占三成这样才能真正起到约束作用。6. 实战中的踩坑记录这些问题不调试你是看不出来的最后集中写几个调试过程中让人抓狂的问题。这些问题都很有共性如果你也在做类似的事大概率会遇到其中一个。6.1 意图描述过细带来的检索塌陷我一开始觉得意图描述写得越细越好于是把正例写了一大堆结果发现召回率反而下降了。原因是描述太长之后向量被各种细节拉偏简短的用户查询反而匹配不上。后来我改成“核心语义 边界反例”两边分开正例只保留最典型的三种句式反而效果更好。6.2 参数契约太严格导致技能“看得见摸不着”这个问题出现在一个“导出报表”技能上。我要求必填参数包括报表类型、时间范围、格式、是否抄送邮箱结果模型经常漏传或者传错格式。后来我把必填项砍到只有时间范围一个其他参数全部改成可选调用成功率立竿见影。多数场景下模型能少猜一个字段就少一次出错机会。6.3 技能数量膨胀后的检索衰减技能从十来个增长到五十多个后我明显感觉匹配变差了误触发和漏触发同时增多。五十多行 intent 描述挤在一起彼此语义距离变近确实容易互相污染。处理方法有两个一是把技能做成分组命名空间比如订单类和售后类分开描述前缀二是把相似意图描述合并成一个大技能内部再做二级分发。效果都很明显五六个组命名空间下去测试集通过率又回来了。6.4 Agent 自主编排与固定流程之间的平衡最后一个想提醒的是不是所有流程都该让 Agent 自主决定。我曾经尝试让模型自己决定技能编排顺序结果它偶尔会跳过必要的前置步骤比如直接调用“生成售后工单”而没有先调用“查订单归属”。后来我改成关键业务流程用显式 DAG 固定路径只有非关键分支允许动态决策。一下子既保留了灵活性又卡住了业务底线。这套平衡原则基本没什么例外核心链路越短越固定用户体感越一致只有旁路分支才适合交给 Agent 发散。我在 agent-skills 上反复调试最大的体会是Agent 能力工程化没有银弹但把意图、参数、执行、触发、编排、观测这些环节逐个理顺之后系统的稳定性会一天比一天好。你如果也在做类似的事建议先从最小可用的三个技能跑通全链路再逐步加量过程中的日志和测试集从一开始就建好。后面加技能只会越来越顺不会越来越乱。