ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Agent技能设计实战:从Prompt到可维护技能库的完整指南

Agent技能设计实战:从Prompt到可维护技能库的完整指南 做了几个月的 Agent 项目一直跟agent-skills这个词打交道。我发现很多人对技能的理解还停留在给大模型写一段好 prompt上真正到了落地阶段才发现Agent 能不能稳定干活拼的不是模型多聪明而是技能设计多扎实。这篇文章我想把这段时间踩过的坑、总结出来的套路完整梳理一遍技能到底是什么、怎么拆、怎么写、怎么调试以及怎么让技能库长期可维护。不管是正打算入场 AI 应用开发的工程师还是已经在用 LangChain、Function Calling 之类方案做 Agent 的开发者应该都能从中找到有用的东西。1. agent-skills 到底是什么先分清概念再动手1.1 从会聊天到会办事技能层的位置大模型原生能力是文本预测你跟它说一句话它回一段话。但真实业务要的不是对话是结果查库存、发邮件、算价格、调接口。Agent 就是夹在模型和外部世界之间的那层转化器。agent-skills就是这层转化器里的最小功能单元。一个技能通常包含一段清晰的意图描述、一组结构化参数、一段可执行的逻辑以及一个标准化的返回结果。模型负责判断现在该调用哪个技能、参数填什么技能本身负责真正把事情做掉。这套设计把两件事解耦了模型不需要知道业务细节技能不需要理解对话。你换个模型技能照用你改个技能模型不用重训。这也是 Agent 工程和普通 Prompt 工程最本质的区别——Prompt 是把知识塞给模型技能是把能力交给系统。1.2 一个技能应该长什么样四段式结构我见过很多团队写技能写着写着就变成了一个大函数里面塞满了判断逻辑。这种技能表面能用实际上极难维护。标准的技能结构应该分成四段元信息技能名称、一句话描述、调用场景。这部分是给模型看的目的是让模型在正确的时候选中它。参数定义用 JSON Schema 描述每个参数的名称、类型、是否必填、取值范围。这部分是给模型参考的决定它能不能填对。执行逻辑真正的业务代码。参数进来之后校验、调接口、算逻辑、生成结果。返回协议统一的结构化返回至少包含状态码、描述、数据三块。这样 Agent 才能根据结果决定下一步是结束还是继续调别的技能。这个四段式不是拍脑袋定的它对应了 Agent 运行时的完整链路意图识别 - 参数抽取 - 工具执行 - 结果反馈。每一段都是独立可测的哪一段坏了就修哪一段不会牵一发动全身。2. 设计一套技能库的完整思路选型与拆解2.1 先列任务清单再抽象技能边界动手写代码之前我习惯先把业务里所有需要 Agent 完成的任务列出来。这一步别急着考虑技术实现就写人话用户想知道订单状态、用户想退掉一个商品、运营想批量改价格、风控想冻结异常账号。列完清单之后逐个找共性。你会发现查订单查物流查用户资料本质上都是查询类技能执行逻辑高度相似改价格改库存改状态又都属于写操作类技能。共性的部分抽出来做成公共组件差异的部分留在技能配置里。我举个例子同样一个查询技能订单域和用户域的表结构完全不同但执行逻辑都是接收一个 ID查数据库返回一条记录。那就可以抽一个retrieve_entity的公共执行器把表名、字段映射、返回模板作为参数传入。这样一个执行器能覆盖几十个查询类技能而不是每个技能都重写一遍 SQL。2.2 技能粒度怎么定太细和太粗都是坑技能粒度是 Agent 工程里最容易走极端的问题。有人把整个业务流程写成一个技能参数列表几十个字段模型经常填错有人把发送 HTTP 请求都单独做成一个技能导致模型面对一堆低级工具完全不知道怎么组合。我的经验是技能粒度应该对齐真实世界的一个动作。什么叫一个动作用户申请退款算一个动作查询退款进度算一个动作给退款单打上已处理标记也算一个动作。但把退款单金额从 A 改成 B 再发起审批再通知财务就不算一个动作这已经是流程编排的范畴了。判断粒度是否合适有个土办法看模型能不能仅凭技能名和一句话描述就做对选择。如果你发现描述里必须加一堆仅当……才调用的限定条件说明技能拆得太大如果你发现模型经常在两个功能差不多的技能之间犹豫说明拆得太细。理想状态下一个技能只回答一个问题你要做什么事给我什么参数我还你什么结果。2.3 工具调用与技能编排的配合很多框架里工具和技能是混着叫的但在工程上它们有分工。我的习惯是工具是最底层的能力技能是面向业务场景的封装。举个例子获取商品评价是个工具它负责调第三方接口拿原始数据评价分析技能则包含情感分类、关键词抽取、摘要生成这些后续步骤。Agent 直接调技能技能内部再去编排工具。这样做的最大好处是模型侧的决策负担被大幅降低——它不需要想先调评价接口再写个 prompt 做情感分析再把结果整理成 JSON它只需要说调用评价分析技能。如果模型本身能力很强比如用了足够大的上下文那确实可以直接让它编排工具。但只要你的场景稍微复杂一点技能编排的价值就会显现业务逻辑被固化在代码里可以测试、可以版本控制、可以配置权限而不是依赖模型临场发挥。3. 从零实现一个技能核心环节与实操记录3.1 搭建最小可用的技能框架我不打算在这里绑定某个特定框架因为技能设计思想是通用的。下面这套骨架是我在项目里用顺手的结构你完全可以平移到 LangChain 的tool、OpenAI 的 function schema 或者其他任意 Agent 框架上。先定义技能基类from typing import Any, Dict, Optional, Type from pydantic import BaseModel class SkillResult(BaseModel): 统一返回协议 code: int # 0 成功非 0 失败 message: str # 状态描述 data: Optional[Dict[str, Any]] None # 业务数据 class BaseSkill: 技能基类所有技能继承它 name: str # 技能名模型通过它识别 description: str # 一句话描述说明什么场景调用 parameters: Type[BaseModel] # Pydantic 模型定义参数结构 def execute(self, params: BaseModel) - SkillResult: raise NotImplementedError def run(self, params: Dict[str, Any]) - SkillResult: try: validated self.parameters.model_validate(params) return self.execute(validated) except Exception as e: return SkillResult(code500, messagef执行失败: {str(e)})这里有一个很关键的细节run和execute分开。run负责参数校验和异常兜底execute只写业务逻辑。这样任何技能都不会因为参数格式问题把整个 Agent 打崩任何异常都会包装成标准结构返回给模型模型看到code ! 0就知道这个技能失败了可以尝试换一种方式。3.2 编写第一个技能参数定义与执行逻辑我拿一个最常见的查天气场景做个完整案例。注意技能本身不关心用户是不是说今天冷不冷它只接收city和date两个结构化参数然后返回天气数据。from pydantic import BaseModel, Field from datetime import date class WeatherParams(BaseModel): city: str Field(description城市名中文) date: Optional[date] Field(defaultNone, description日期不传则默认今天) class WeatherSkill(BaseSkill): name get_weather description 查询指定城市在指定日期的天气情况当用户询问天气、温度、降雨、风速时使用 parameters WeatherParams def execute(self, params: WeatherParams) - SkillResult: # 这里对接真实天气 API省略实现 data query_weather_api(params.city, params.date or date.today()) return SkillResult(code0, message查询成功, data{ city: params.city, date: str(params.date or date.today()), temperature: data[temp], condition: data[condition] })参数定义里一定要写Field(description...)。这个描述是给模型看的不是给人看的。模型抽取参数时会参考这些描述比如用户说北京明天出门带不带伞模型就需要从这句话里提取出city北京date明天。描述写得越具体抽取准确率越高。3.3 注册与加载让Agent真正用上技能技能写完之后要注册到一个技能注册表里Agent 启动时加载技能列表然后通过某种机制function calling、tool use、prompt 注入让模型知道有哪些技能可用。# 技能注册表 SKILL_REGISTRY: Dict[str, BaseSkill] {} def register(skill: BaseSkill): SKILL_REGISTRY[skill.name] skill register(WeatherSkill()) register(RefundSkill()) # ... 注册所有技能 def get_skill_schemas(): 生成模型侧需要的函数定义列表类似 OpenAI function calling 的 format schemas [] for skill in SKILL_REGISTRY.values(): schemas.append({ type: function, function: { name: skill.name, description: skill.description, parameters: skill.parameters.model_json_schema() } }) return schemas这一步看起来简单但实际项目里要小心两个问题。第一技能太多时模型会选择困难超过 30 个技能后准确率明显下降这时候需要给技能分组或者引入路由技能而不是直接全量塞给模型。第二技能注册后要有一个可观测的指标比如每个技能被调用的次数、失败率这能帮你判断哪些技能描述有歧义、哪些技能稳定没用。4. 技能调试与排查高频问题速查4.1 模型不调用技能先查这四件事遇到Agent 明明有技能却不用硬是在那瞎编答案的情况别急着怪模型按下面的顺序排查技能描述写清楚了没有描述里不能只说查询天气要说清楚什么场景下用。比如当用户提到天气、温度、降雨、风速、出行建议时使用。模型本质上是靠语义匹配来做选择的描述和目标场景越贴近命中率越高。参数定义是否让模型看得懂如果参数 description 写着city字段但没有说明格式模型可能传成拼音或者带上市字。最好在 description 里给出示例城市名中文例如北京。技能是不是被 Prompt 淹没了你如果让模型先做一堆意图分析、身份设定、格式要求再在最后附上一长串技能列表模型很容易只顾着前面的任务而忽略后面的工具。技能列表应该放在显眼的位置或者用框架原生的 tool 注入机制。返回结果是否有足够信息如果技能返回{status: ok}这种空泛结果模型不知道下一步该干嘛就会自己编。返回数据一定要带全让模型能直接引用。我排查时经常用的招数是在每次请求里把get_skill_schemas()的完整输出打到日志里跟实际对话请求对照。看模型是否真的收到了技能列表还是走了别的系统 prompt 掩盖了工具信息。日志是最快的定位方式。4.2 参数错位与JSON Schema问题参数出错是我遇到最多的问题典型的有三种第一模型多传参数。定义了三个字段模型额外塞进去一个未知字段。解决办法是校验时用 Pydantic 的extraforbid配置或者干脆忽略多余字段。推荐前者因为多余字段往往说明模型理解出现了偏差宁可让它失败重来也别容忍脏数据。第二类型错误。date类型模型容易传给字符串必须靠 Pydantic 自动转换或者自定义校验器。日期格式尤其坑模型可能给明天、下周一这种自然语言时间必须由你写一个时间解析函数在入参前处理不能指望模型直接输出标准化日期。第三描述里没说明枚举值。如果某个字段只允许特定取值比如退款原因只能是质量问题七天无理由其他你必须在Field里加enum[...]否则模型会天马行空。加了枚举后模型在绝大多数情况下都会正确选择。我通常会在基类的run方法里加上一段日志记录原始参数和校验后的参数。这样每次参数解析失败都可以看看模型实际给了什么输入比事后猜高效得多。4.3 执行失败后如何优雅降级技能执行不可能永远成功。外部接口挂了、数据库超时、权限不足、参数校验失败这些情况如果直接崩溃返回给用户肯定不行。我的降级策略有三层第一层是技能内部兜底。比如天气 API 挂了尝试从缓存读一次昨天的数据同时在message里注明数据更新于一小时前。第二层是让 Agent 感知失败并重新规划。返回code500后Prompt 里要告诉模型“如果技能执行失败你可以尝试调用备用技能或者把错误信息原样告知用户但不能编造结果”。第三层是安全阀。同一个技能连续失败三次就暂停调用这个技能避免模型陷入死循环反复触发失败接口。这里有个容易漏的细节你返回的message本身会被模型读取。所以不要在 message 里写内部堆栈、数据库表名、内网 IP 这些敏感信息一方面有安全风险另一方面模型会被这些技术噪声干扰。就写人话比如查询超时请稍后重试。5. 进阶玩法让技能库持续进化5.1 技能复用与组合编排当技能数量上来之后你会发现很多技能内部有重复逻辑。比如发通知这个动作在退款技能里要用、在订单处理里要用、在风控提醒里也要用。这时候就可以把发通知做成底层技能其他技能通过技能编排的方式调用它。技能编排有两种常见方式。一种是代码编排在某个技能的execute里直接调用另一个技能的run方法。这种方式串行可靠适合流程固定的场景。另一种是模型编排暴露多个原子技能让模型自己选组合。这种方式灵活但不可控需要加足够的约束和 eval 验证。我做项目时通常是先代码编排把主干流程跑通再逐步把可变的部分开放给模型。比如退款流程里查订单和更新状态是固定动作就代码编排但是判断能否退款这种需要业务知识的环节就开放一个读取退款规则的技能让模型基于规则自己判断。5.2 技能评估与回归测试技能库跟普通代码库一样需要一个可持续的验证机制。我每次上线新技能都会先跑一遍离线评测集。评测集里是几十条真实用户问题每条都标注了期望调用的技能和期望的输出关键字段。跑完之后看两个指标技能选择准确率和参数抽取完整率。前者衡量模型能不能在多个技能中选对目标后者衡量它能从原话里抽对多少字段。两轮迭代下来这两个指标会明显上升。我现在强制每个新增技能都配至少 3 条回归用例放在独立的测试集里。现在 Agent 技术上看起来欣欣向荣个人或团队持续积累的这些工程细节才是真正拉开差距的地方。5.3 版本管理与技能仓库最后说一个容易被大伙儿无视的环节技能版本管理。Agent 的业务逻辑会不断变比如退款规则变了、新增了第三方物流接口、字段改名了这些都会造成技能行为变化。如果技能没有版本出问题之后回滚根本无从谈起。我在项目里给技能加了version字段包含在元信息里每次上线都会用 Git tag 标记。同时做一份技能变更表记录每次修改了哪个技能、改了什么、谁改的、影响面是什么。质量不够的话就多打磨细节一张干净的技能版本表能让后面接手的人少走大量弯路。如果你正在做 Agent 产品我建议你尽早把技能当成产品来管理每个技能都填完整元信息都有用例都有日志监控。短期看起来繁琐长期下来你会感谢当初的这份规整。最后分享一点实操体会我在实际项目里深有体会的一点是技能设计的好坏直接决定了 Agent 是玩具还是生产力工具。技能不是一个辅助性的代码模块它是 Agent 的能力边界。模型再聪明技能库是空的Agent 也只能空谈。刚开始做agent-skills时我也走了弯路一股脑写了十几个技能结果调参调到头秃。后来从业务清单倒推技能设计重新梳理边界和粒度之后调用准确率一下子从 70% 出头提到了 90% 以上。建议你不用追求一次性把所有技能都写完美先把最小闭环跑通让模型真正通过技能完成一次有价值的任务再逐步扩展。这个过程中你会慢慢建立对哪些逻辑该交给模型、哪些该固化到技能的直觉这是无论框架怎么变都拿不走的核心能力。
返回列表