☰
Agent技能设计实战:从原子技能到复合编排与评测
2026/10/13 7:59:53 网站建设 项目流程

做Agent开发的朋友应该都撞过同一堵墙:单轮对话、简单工具调用跑得挺顺,一旦进入多步骤任务,Agent就开始“犯迷糊”,要么调错工具,要么参数编得离谱,要么绕半天回不来。这两年我前后折腾过好几套Agent项目,最后发现瓶颈往往不在模型智商,而在“技能”这件事上没想清楚。这篇文章想聊聊我理解中的agent-skills:它到底是什么、怎么设计、怎么落地,以及我在真实项目里踩过哪些坑。如果你正在做Agent应用,或者准备把已有系统改造成智能体形态,这篇应该能帮你少走几步弯路。

1. 什么是Agent技能,先想清楚再动手

1.1 技能不是提示词,是能力的封装

很多人第一次接触“技能”这个词,会下意识觉得它跟“提示词模板”差不多。我一开始也这么想,后来被现实教育了。提示词只是告诉模型“你要做什么”,但技能是一整套可执行的方案,包含触发条件、输入输出契约、执行逻辑、校验方式和回退策略。

我做过一个比喻:如果Agent是一个人,提示词是“口头指示”,技能则是他“长期训练形成的做事方法”。你说“帮我把报告写出来”,他会写但可能写成任何风格;但如果他先跑了一遍“报告生成技能”,他会自动收集数据、选定模板、按结构输出、再自查一遍。这个区别在单次任务里看不出问题,放在长链路任务里就是天壤之别。

所以我的定义是:Agent技能 = 一个具备明确能力描述、可复用的任务执行单元,内部可以包含提示词调用、工具函数、数据校验与错误恢复逻辑。它必须是模型可感知、可选择的“肌肉记忆”,而不是贴一段文字就完事。

1.2 技能设计的三层结构

我在项目实践中,通常把一个完整的技能体系拆成三层。

第一层叫原子技能,这是最小可执行单元。比如“查天气”“算加法”“搜文档”“发邮件”,一个原子技能只做一件事,输入输出都非常明确。原子技能的特点是边界清晰、容易被模型理解和调用,也最容易做单元测试。

第二层叫复合技能,它把多个原子技能按固定顺序或判断逻辑串起来。比如“安排出差行程”这个复合技能,内部可能先调用“查日程”再调用“订酒店”再调用“查天气”。复合技能本质上是一个小型的编排流程,但它仍然对外暴露成单一技能,模型只需要说“我要安排出差”,剩下的交给内部逻辑。

第三层叫元技能,它负责管理和选择其他技能。比如任务规划、技能路由、冲突仲裁。在很多框架里,元技能通常以“Planner”或“Router”模块的形式存在。我给前两层投入了大量精力之后,才明白一个道理:如果原子技能质量不高,元技能再聪明也救不回来,跟“垃圾进垃圾出”一个道理。

1.3 我见过最典型的落坑方式

提这个是因为太多人上来就追求“大而全”,恨不得一个技能搞定整个业务链路。我有一次接手某公司的客服Agent项目,前任工程师把“订单查询、退款申请、物流跟踪、发票管理”全塞进一个技能里,结果模型每次都在内部乱跳:用户问物流,它去查订单;用户要退款,它先查了三遍订单再走退回逻辑。最后那个技能被拆成四个独立技能,问题立刻解决大半。

拆技能这件事,原则是“每个技能只对一类目标负责”。判断标准很粗暴:如果一个技能内部包含两个以上的“成功终点”,它就该被拆开。比如“处理售后”是一个宽泛目标,但“退货”和“换货”是两种成功终点,必须拆开。

2. 技能设计的原则:从任务出发,别从API出发

2.1 把任务拆到“能独立验证”的粒度

我在设计技能边界时,最常用的问题不是“这个功能有哪些接口”,而是“用户这句话到底想要什么结果”。结果不是过程。用户说“帮我看看明天北京适合穿什么”,背后的目标是“获取着装建议”,而不是“调用天气API”。如果你从API出发,你会做个“查天气技能”;从任务出发,你会做“着装建议技能”,里面再调用“查天气”和“查历史穿搭经验”。

两者的差别在于:前者把工具直接暴露给模型,模型需要自己完成“天气到穿衣”的推理;后者把推理封装进技能内部,模型只需要说清楚“我要明天的穿衣建议”,结果的稳定性会高很多。我自己的经验是,能让技能多承担的任务就不要丢给模型现场推理,这不光是为了结果稳定,也是为了节省Token和降低延迟。

维度上,我建议把“能独立验证”作为拆分标准。一个技能完成任务后,用户或系统能基于明确标准判断它对不对,这就是独立验证。比如“查天气”的结果可以对照天气预报网站验证,“算运费”的结果可以用公式重算验证。只有每个技能都能被验证,你才能在后续做评测和回归测试,这一点会在第4节展开。

2.2 技能与工具的边界怎么划

技能和工具(Tools/Function)的关系经常让人困惑。我的理解非常简单:工具是“没有决策能力的执行器”,技能是“会做选择的任务单元”。

举个例子,“发送邮件”是一个工具,它接收收件人、标题、正文,然后发出去,内部没有任何判断。而“给客户写跟进邮件”是一个技能,它要先判断客户处在哪个阶段、选择合适语气、生成内容、调用“发送邮件”工具。一个技能通常由多个工具组成,但一个工具也可以被多个技能复用。

如果在设计阶段把工具当技能用,Agent在调用时就会很吃力:它需要自己规划参数、判断分支、处理异常。不是说完全不行,而是稳定性差,尤其模型能力不够强的时候,翻车概率指数级上升。我在某跨平台系统的实践中,把所有第三方接口全部封装成工具层,所有业务意图封装成技能层,模型只跟技能层对话,效果比之前模型直接面对几十个API好太多。

2.3 一个实际例子:会查天气的Agent技能

为了把上面的抽象概念落到地面,我用一个最经典的“天气技能”来演示。

先看错误版本,直接把天气API暴露给模型,参数是城市名和日期。模型调用时会发生几个问题:城市名可能写成别名(“帝都”而不是“北京”)、日期可能写成“明天”而不是具体日期、单位可能不统一。所以正确做法是把这些逻辑封装进技能内部:

def weather_skill(city_alias: str, date_text: str): # 标准化城市名 city = normalize_city(city_alias) # 把“明天”“后天”这类自然语言转为日期 date = parse_date_text(date_text) # 调用上游天气API(工具层) raw = weather_api(city, date) # 数据校验与兜底 if raw is None: return {"status": "unavailable", "message": "没有获取到天气数据"} # 输出结构化结果 return {"city": city, "date": date, "weather": raw["condition"], "temp": raw["temperature"]}

这里的关键点有三个:参数标准化、自然语言转结构化、异常兜底。做好这三件事,模型不管怎么说,技能都能产出稳定的结构。这本质上是把模型的“发挥空间”压缩到可控范围,模型只需要做意图识别,剩下的脏活累活全在技能内部完成。

3. 实操:手写一套Agent技能的完整过程

3.1 搭建最小框架:LLM + 工具函数注册 + 循环执行

理论说多了容易飘,直接进入实操。我搭过最精简的Agent技能运行框架,核心就三块:模型客户端、技能注册表、主循环。

技能注册表本质上是一张“技能名 -> 执行函数 + 描述 + 参数Schema”的映射表。模型通过描述和Schema感知技能,执行函数负责真正干活。主循环则反复执行“模型决策 -> 调用技能 -> 返回结果 -> 再决策”的流程,直到模型认为任务完成。

先定义技能基类,我会用Python做示范,但核心思想不绑定任何语言:

class BaseSkill: name: str # 技能名,必须是动词短语,如 "query_weather" description: str # 给模型看的描述,说明何时使用、输入什么、返回什么 parameters: dict # JSON Schema 形式的参数声明 def run(self, **kwargs): # 子类实现具体逻辑 raise NotImplementedError class Agent: def __init__(self, model_fn, skills: list[BaseSkill]): self.model_fn = model_fn self.skills = {s.name: s for s in skills} # 把技能信息转成模型可识别的functions格式 self.functions = [build_function_schema(s) for s in skills] def run(self, user_message: str): messages = [{"role": "user", "content": user_message}] for step in range(MAX_STEPS): resp = self.model_fn(messages, functions=self.functions) if resp.is_function_call: skill_name = resp.function_call.name args = json.loads(resp.function_call.arguments) result = self.skills[skill_name].run(**args) messages.append({ "role": "function", "name": skill_name, "content": json.dumps(result) }) else: return resp.content

这个框架非常朴素,但足够用。我当年做某图像处理Demo的Agent端时,用这个结构扩展了十几个技能,跑起来很稳定。核心好处在于:你新增一个技能只需要写一个新类,注册到列表里,主循环不用改一行代码。

3.2 原子技能实现示例

我做几个具体的原子技能,帮你感受一下设计粒度。

第一个是“计算器技能”。你可能觉得这有什么好做的,模型自己会算。但实测下来模型做多位数乘法经常出错,手写计算器反而能保证准确性。实现的时候,关键是要对参数做数值类型校验,模型经常输出字符串数字:

class CalculatorSkill(BaseSkill): name = "calculator" description = "当用户需要算术运算时使用,支持加、减、乘、除。输入必须是数字表达式,如 '12.5 * 3'。" parameters = { "type": "object", "properties": { "expression": {"type": "string", "description": "数学表达式"} }, "required": ["expression"] } def run(self, expression: str): # 安全校验:只允许数字和基本运算符 if not re.fullmatch(r"[0-9+\-*/().\s]+", expression): return {"error": "包含非法字符"} try: result = eval(expression, {"__builtins__": {}}, {}) except Exception as e: return {"error": str(e)} return {"result": result}

注意description的写法,它是写给模型看的,含糊不得。“当用户需要算术运算时使用”比“执行计算”更有指导性,模型在纠结要不要调技能时,靠的就是这句话。

第二个是“文档搜索技能”。在某个跨平台系统里,我需要Agent从一批文档中检索信息,实现上用了简单的关键词召回加语义匹配。这里重点不是算法,而是输入设计。我把搜索范围、关键词、返回条数都作为可选参数,让模型按需传入。如果你把返回条数写成固定值,模型想多要几条依据时就会束手无策,甚至编造内容出来。

原子技能的评价标准我总结成一句话:输入自然、输出稳定、单一职责、失败有兜底。任何不满足这四点的原子技能,都应该回炉重做。

3.3 复合技能编排:让Agent学会“先查后算”

复合技能是我个人觉得最有意思的部分。它把几个原子技能串起来,对外却像单个技能一样自然。

举一个我在某电商订单场景中做过的“优惠计算技能”:用户说“我买3件59元的上衣,和2件89元的裤子,满199减20,运费10元,最后多少钱?”如果把这些计算步骤全丢给模型,出错率很高。我做了复合技能,内部固定流程是:

  1. 调用“单品价格查询”技能拿到每件商品单价
  2. 调用“乘法计算”技能计算小计
  3. 调用“满减判断”技能决定是否触发优惠
  4. 调用“加法计算”技能汇总
  5. 调用“运费计算”技能加上运费

在代码上,复合技能也是一个BaseSkill,但它的run方法内部会维护一个子任务队列:

class DiscountCalcSkill(BaseSkill): name = "calc_cart_total" description = "计算购物车总价,支持满减和运费规则。用户描述购物明细时使用。" parameters = { "type": "object", "properties": { "items": { "type": "array", "items": { "type": "object", "properties": { "product_id": {"type": "string"}, "quantity": {"type": "integer"} }, "required": ["product_id", "quantity"] } } }, "required": ["items"] } def run(self, items): total = 0 for item in items: price = price_skill.run(item["product_id"])["price"] total += calculator_skill.run(f"{price} * {item['quantity']}")["result"] total = discount_skill.apply(total) return {"total": total}

复合技能的执行方式有两种:一种是“技能内部自行编排”,由代码控制子技能的调用顺序;另一种是“把子技能都暴露给模型,让模型编排”。我强烈建议前者。原因很简单,固定流程的稳定性远高于模型自由发挥。模型擅长的是在开放性任务里做取舍,而不是重复执行一套固定计算步骤。

3.4 技能间的数据契约:JSON Schema思维

说到参数声明,值得单独开一小节。我在排查大量技能调用错误后发现,很多问题不是模型不行,而是我们的Schema写得太随意。

JSON Schema在这里起的是“数据契约”的作用,规定技能接收什么、产出什么。我把Schema设计经验总结成五条:

第一,所有属性都要有description,描述里写清楚取值范围和单位。比如温度字段要写“单位:摄氏度,数值范围 -50 ~ 50”,否则模型可能会传华氏度。

第二,必填字段一定要列出。少列一个性能好的模型能自己推断,性能差的就会直接漏传。

第三,尽可能用enum枚举可选值。比如“排序方式”字段写“type: string”不如写“enum: ['price', 'sales', 'rating']”,模型看到枚举后基本不会编新值。

第四,嵌套对象不要设计太深,最多两层。超过两层,模型生成JSON参数的出错率明显上升。如果确实需要复杂结构,就拆成多个技能分别调用。

第五,输出也建议定义固定结构。我在函数返回时统一用{"status": "success/error", "data": {...}, "message": "..."},上层Agent看到这个结构很容易判断下一步动向,而不是傻傻地从头解析。

4. 技能评测:不评测,你根本不知道技能有多烂

4.1 离线评测与在线评测

很多开发者做完技能就上线,这是大忌。Agent技能和普通函数不一样,它的调用效果取决于模型对描述的理解、参数的生成能力、以及技能本身逻辑的正确性。这三层任何一层出问题,整体就出问题。所以我坚持做两层评测。

离线评测是用固定测试集跑技能,不接入真实用户,重点验证“技能是否按预期工作”。例如给“查天气”技能准备100条用户问法,包括“北京明天冷吗”“上海后天有雨吗”这种口语化输入,统计成功率和错误类型。离线评测能快速暴露技能内部逻辑问题,成本也低。

在线评测是接入真实流量后,记录成功率、延迟、Token消耗、用户反馈等指标。我通常用一套简单的影子模式:真实流量同时走旧逻辑和新技能,两边结果做对比,攒几天数据后再决定是否全量切换。这种方式做A/B测试很稳,不会因技能变差影响线上体验。

4.2 构造评测集的方法

评测集不是随便找几十条数据就够的,我习惯按“难度梯度”构造。最小集是“正常输入”,比如直接给出完整信息的问题。中等难度是“隐含参数”,比如用户不说城市,但通过IP能判断位置。高难度是“多意图混合”,比如“帮我查天气,顺便定个明早7点去机场的车”,这时技能要正确拆解。

我在某客服项目中就用过这套梯度评测法,效果很直观。正常输入准确率做到了98%,隐含参数掉到了82%,多意图混合只有61%。看到这个结果,团队才意识到问题出在“意图拆解”而非“查询逻辑”,于是把多意图处理从技能层提升到元技能层,准确率升到了92%。

构造评测集时有个技巧:从真实日志里捞数据,而不是自己凭空编。自己编的数据容易“太标准”,跟真实用户口语差距很大。我一般会让产品同学整理一个月的聊天记录,筛出包含技能相关意图的前1000条,再做去重和标注。这样评测结果才能反映真实水平。

4.3 一些常见的性能坑

评测跑得多了,我总结出几个高频性能坑,这里直接列给你。

一个是“循环调用”。模型在技能返回结果不符合预期时,会反复调用同一个技能,直到步数上限。我处理的方式是主循环里做去重:如果模型连续两次调用同一个技能且参数完全一样,就直接打断并询问用户是否需要换个方式。这一步看起来简单,但能把无效Token消耗减少20%以上。

另一个是“上下文膨胀”。技能返回的原始数据往往很大,比如一份完整文档或一屏搜索结果,直接塞进上下文里,几轮下来模型就开始丢信息。我在技能执行函数里加了输出裁剪,默认只返回摘要和关键字段,并提供一个detail_mode,模型需要更详细信息时可以再查一次。

还有一个是“技能冲突”。两个技能描述相近时,模型容易选错。比如“查询订单状态”和“查询物流状态”,订单是有商品信息和应付金额的,物流才有轨迹信息。我在设计阶段会刻意让描述里的触发词区分开,一个强调“订单金额、商品信息”,另一个强调“包裹、运输进度”。描述写得越具体,选错概率越低。

5. 常见问题与排查技巧实录

5.1 “工具明明存在,Agent就是不调用”

这是新手阶段最常碰到的问题。技能和函数都注册了,描述也写了,但模型就是不用它,硬靠自己编答案。排查思路分三步。

第一步,看模型版本和接口类型。不是所有模型都支持函数调用,有些模型需要你把技能信息放到System Prompt里才能感知到。如果你用的是旧版模型,检查一下是不是没开启function calling的开关。

第二步,检查描述是否与用户意图匹配。我在实际项目X里遇到过“技能描述只有一句话,模型根本没理解使用场景”的情况。后来把描述改成“当用户询问天气预报时,必须先调用query_weather获取数据,不能直接回答”,模型立刻开始调用。

第三步,看是否有未处理的前置约束。比如模型认为用户问题太模糊,不值得调用技能,这时你要在描述里告诉它“即使信息不全也可调用,技能内部会处理默认值”。很多技能不调用的问题是“模型觉得不该用它”,而不是“它不存在”。

5.2 “技能总选错”

这个坑也很常见。一个Agent挂了5个技能,模型每次都选错其中一个。我遇到过一个经典案例:用户问“怎么退款”,模型选了“订单查询”而不是“退款申请”。原因就是“订单查询”的描述里写了“订单状态、退款进度”,出现了“退款”这个词,模型就误选了。

我的处理方法是给每个技能加上“适用时机”和“不适用场景”两段描述。描述示例:

适用场景:用户明确表示要申请退款、或者对订单金额有异议。 不适用场景:用户只是想知道订单状态、物流位置,不要使用此技能。

实测下来,加了“不适用场景”之后,选错概率下降了一半以上。模型在决策时最怕“模棱两可”,你把界限写清楚,它的判断就准。

5.3 “每次执行结果不稳定”

还有一类问题让人抓狂:同样的输入,这次成功下次失败,而且失败得毫无规律。我排查这类问题时,第一反应是看“有没有非确定性因素混进了技能内部”。

最常见的非确定性源是模型本身的采样温度。如果技能内部调用了大模型做子任务,而温度又设得偏高,输出就会漂移。我通常把技能内部的模型调用温度降到0或者0.1,这能极大提高稳定性。如果技能内部都是确定性代码逻辑,那就检查输入预处理环节,比如“日期解析”在不同格式下结果不同,导致后续逻辑走不通。

最近一次排查某Agent偶发错误时,发现是缓存问题:技能第一次跑的结果被缓存了,第二次参数不同但命中了同一个缓存键,返回了旧数据。这种问题隐蔽性极强,日志看不出来,只能通过随机化测试发现。从那之后我给所有技能缓存都加了参数指纹作为键的一部分。

5.4 排查技巧速查表

我把日常定位技能问题的流程整理成一张表,贴在这里方便对号入座:

现象可能原因排查方式修复建议
Agent不调用技能描述不清晰或模型版本不支持打印模型完整返回,确认是否有function_call字段优化描述;升级接口;在System Prompt中加入技能说明
调错技能两个技能描述重叠构造模糊问法对比选择结果区分触发词;增加“不适用场景”描述
参数错误Schema描述不完整、缺枚举抓取模型生成的原始arguments完善JSON Schema描述;枚举化可选值
结果不稳定内部子模型温度高、缓存污染多次运行对比日志温度调低;缓存键加参数指纹
死循环调用缺少调用去重和步数限制检查主循环日志增加连续相同调用拦截;设置步数上限
上下文溢出工具返回数据过大查看Token消耗变化返回值摘要化;提供按需查询模式

这张表是我排查技能类问题时最先对照的清单。大多数问题不会超出这几类,剩下的都需要具体业务具体分析。

6. 我现在做技能开发时的一些习惯

技能开发这件事,做得越久越明白“简单可靠”比“功能强大”重要得多。我最后分享几个现在坚持的习惯,算是个人的经验沉淀吧。

第一,每个技能都必须配测试用例再进库。我给自己定了个规矩:没有评测过的技能不允许上线。哪怕是一个“查星期几”的小工具,也要有至少10条测试数据。这个习惯帮我挡住了无数次回归事故。

第二,技能库要控制规模,不搞“多多益善”。技能越多,模型选择负担越重。我现在倾向于单Agent的技能库控制在20个以内,超过这个数就做子Agent拆分,让不同Agent各管一组技能。20个听起来挺少,但实践中真正高频使用的往往不到10个,一多真的容易乱。

第三,描述语言要站在模型的角度写,而不是站在人的角度写。人觉得言简意赅的“用于天气查询”,模型可能无法理解触发边界。反而是略显啰嗦的“用户询问当前或未来几天天气情况时使用,包含温度、降水、风力等信息”更好用。我在写技能描述时会问自己:一个什么都不懂的新手,看了这个描述能不能正确使用?这个标准很管用。

第四,技能内部出错一定要有默认方案,而不是裸抛异常。例如查天气API挂了,技能可以返回“依据历史同期数据估算”的结果并标注置信度。这样Agent还能继续往下走,而不是中断任务让用户尴尬。裸抛异常对开发者友好,但对用户体验伤害很大,上线前要想清楚。

Agent技能这条路没有标准答案,产品形态、模型能力、业务逻辑都会影响你的技能拆法和编排方式。但只要抓住“可复用、可评测、可兜底”这三个要素,在大多数场景下都不会跑偏。最后再给自己团队打个补丁:技能迭代不是上线就完事,每个月都要抽一批真实流量重新跑评测,用户会变、模型会更新、业务规则会调,技能库不维护,早晚会悄悄腐烂。

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

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

立即咨询