ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Agent技能系统设计指南:从会聊天到能干活的关键一步

Agent技能系统设计指南:从会聊天到能干活的关键一步

直接聊agent-skills这个事。先说清楚它是什么:就是给智能体(Agent)配一套可复用的“技能包”,让它能调用工具、执行任务、处理多步骤流程,而不是只会空对空聊天。做Agent开发的人应该都有感受——模型本身能力越来越强,但落地的时候真正卡脖子的,往往不是模型智商,而是“手上没活儿”。技能系统就是干这个的:把模型能理解和执行的原子动作、组合动作,提前编排好、注册好,让智能体在需要的时候直接拿出来用。

这篇文章适合正在做Agent产品、研究AI应用落地、或者被多步骤任务搞得焦头烂额的开发者读。我会从技能系统是什么、为什么值得做、怎么设计、怎么实现、踩过哪些坑这几个维度展开,全程用我做真实项目时的场景和取舍来讲,不绕弯子。

1. 技能系统的本质:从“会聊天”到“能干活”

1.1 为什么大模型再强也需要“技能层”

先说一个很多人忽略的点:大模型本身并不具备“稳定执行某项任务”的能力。你可以让模型“假装”会调用工具,但如果没有任何约束机制,它可能今天用这个格式调,明天用那个格式调,后天干脆自己在脑内脑补一个执行结果出来。这在生产环境里是致命的。

技能系统做的事情,就是在大模型和真实世界之间,建立一个可枚举、可校验、可回退的操作层。它把“模型想要的结果”翻译成“系统能执行的动作”,再动作执行完后把结果喂回给模型继续决策。没有这一层,模型再聪明也只是一个满嘴跑火车的知识库。

拿我做过的一个实际项目来说,当时接了一个企业内部的知识库问答需求。刚开始方案特别简单:用户提问,模型检索文档,生成回答。跑通以后觉得还挺好,但一测真实数据就发现问题——用户的提问根本不是一个完整问题,而是“帮我查一下上个月那个项目的报销到哪一步了”。模型光靠检索到一段文档是做不出这个回答的,它需要调用审批系统查状态、调报销单的进度、可能还要查一下项目组的人员名单才能组织出答案。没有技能系统,这种任务完全做不了。

后来我把这些操作拆成了技能:查审批单、查报销进度、查项目人员、汇总结果。模型只要识别出用户意图,然后按顺序调度这几个技能,任务就闭环了。这就是技能系统最核心的价值:把模糊的意图,映射到确定的执行路径上。

1.2 技能、工具、插件的边界到底在哪

做Agent开发的人经常把技能、工具、插件这几个词混着用,但它们其实不是一个层面的东西。工具是最底层的单个能力,比如“发HTTP请求”“读数据库”“执行代码”;插件是围绕某个平台或应用做的一揽子适配;技能则是面向某个任务场景组织的执行策略。

我习惯用一个类比:工具是乐高积木的单个零件,插件是一套主题套装里的所有零件,而技能是拼装说明书——它规定了先用哪个零件、怎么拼、拼错了怎么拆掉重来。Agent有了技能才会“干活”,光有一堆工具零件,它还是不知道怎么组合。

实际落地上,技能层需要包含几个信息:

  • 技能的触发条件:什么场景下该用这个技能
  • 输入输出Schema:模型需要提供哪些参数,技能返回什么结构
  • 执行逻辑:内部是调一个API还是多个API,是否需要循环或重试
  • 失败处理:执行失败后是重试、换策略还是直接上报

这些信息合在一起,才是一个完整的技能定义。哪怕最简单的一个“搜索文档”技能,如果没有定义清楚输入输出结构,模型调用的时候就会用五花八门的格式来提交参数,解析逻辑写到你想骂人。

1.3 哪些项目真正需要agent-skills

如果你只是做一个单轮问答Demo,或者一个纯粹靠模型常识就能回答问题的玩具应用,那确实不需要技能系统——模型自己就够了。但如果你的项目沾到下面任何一条,我建议你认真考虑上技能系统:

  • 任务需要调用外部API或数据库获取实时数据
  • 任务包含多个步骤,依赖前一步的执行结果才能走下一步
  • 同一个流程需要被多个场景复用(比如既能被对话调用,也能被定时任务调用)
  • 需要严格记录执行过程,每一步都要可回溯、可审计

我遇到过最典型的一个案例:某公司要做一个营销文案自动生成工具。听起来很简单对吧?用户给个产品名,模型生成文案。但真实需求是:先查商品库拿规格参数,再调舆情系统看最近话题趋势,然后结合品牌调性生成几种风格的文案,最后还要自动过一遍敏感词审核。这里面任何一个模型单独都搞不定,必须靠技能系统把这些步骤串起来。

所以说,agent-skills不是给所有项目准备的银弹,但它几乎是所有“Agent要干实事”的项目绕不开的基础设施。后面我会细讲怎么设计一套适合自己的技能系统。

2. 技能系统的设计思路与方案选型

2.1 原子技能与组合技能的两级架构

一套成熟的技能系统,我建议一开始就分成两级:原子技能和组合技能。

原子技能是不能再拆的最小操作单位,对应到一个具体的外部调用或内部处理。比如“查天气”“发送邮件”“读取文件内容”“计算两个日期的差值”。它的特点是:职责单一、入参明确、可独立测试。

组合技能则是把一个业务流程编排出来,内部会调用多个原子技能或嵌套其他组合技能。比如“邀约客户参观”这个技能,可能拆成“查客户日程表”“选可用会议室”“发邀约邮件”“创建跟进任务”四个原子步骤。

为什么要分这两级?因为灵活性和可维护性是最好的。你不可能为每个业务流程单独写一个魔法函数,那样代码很快会烂成一锅粥。但全是原子技能也不行——模型面对二十个细碎技能的时候,调用成功率会肉眼可见地下降。它不知道该选哪个、参数怎么填。

组合理念还有一个额外的好处:调试的时候,你可以单独测试每一个原子技能,确认它本身没问题后再拿去组合验证。我个人经验是,组合技能的bug有七成以上是原子技能之间参数传递错了,而不是技能本身执行逻辑有问题。

2.2 技能注册表:一切皆可枚举

我强烈建议你从第一天就给所有技能建一个注册表,而不是在代码里散落一堆if-else。注册表是一份结构化清单,记录了每个技能的唯一标识、功能描述、参数定义、返回结构、超时时间、调用权限等元信息。

为什么需要这个?因为模型是通过这份注册表来“知道”有哪些技能可用的。你在设计Agent的时候,通常会把技能列表塞进系统提示词(system prompt),或者用函数调用的方式直接暴露给模型。无论哪种方式,注册表都是模型决策的输入源。注册表组织得好,模型选技能就选得准;注册表写得乱七八糟,模型大概率挑错技能或者干脆胡编一个。

注册表里的技能描述尤其值得花心思。我踩过的坑是:早期我为了省事,技能描述写得特别短,比如“查订单”三个字就完了。结果模型经常在“查订单”和“查订单详情”两个技能之间犹豫,有时候选了这个,有时候选了那个,执行结果完全不可控。后来我把描述改成结构化的:适用场景、使用条件、典型示例、数据来源,每个技能都写清楚。改完之后,模型选技能的正确率上了一大截。

另外注册表要支持动态增删。你不可能第一次就把所有技能想全,一定是业务跑起来之后发现有缺漏,再一个个补。如果注册表设计成静态的,每次加技能都要发版上线,那开发效率会被死死拖住。

2.3 技术方案选型:函数调用还是自然语言触发

技能系统在技术实现上,有两种主流路线:一种是走模型平台官方的函数调用(Function Calling)机制,把技能声明成JSON Schema传进去;另一种是把技能描述写进提示词,让模型以自然语言形式输出调用指令,再在代码里做解析。

这两种方案我都在生产环境里用过,各有各的适用场景。

函数调用机制的最大优势是结构化和稳定。模型的输出直接就是符合Schema的JSON,不需要你做复杂的自然语言解析,出错的概率低很多。而且大部分模型平台对流式输出、并发调用支持得都比较好。缺点是:技能多了以后,你一次性传给模型的Schema会很长,消耗的token明显增加,响应延迟也会变高。

自然语言触发的最大优势是灵活。模型可以自由组合多个技能,甚至在你没有显式声明的情况下,根据对话历史推断出该用哪个技能。缺点是解析依赖不可控,模型经常在输出里多带一些前后缀,你的解析器要不断跟着调整。我在早期项目里吃过这个亏,埋了不知道多少解析bug。

我的建议是:如果项目的核心任务路径相对固定,优先用函数调用机制;如果任务开放性很高、需要模型自由发挥的,则用混合方案——把高频技能走函数调用,低频或者探索性的操作走自然语言触发。不要走极端。

2.4 设计原则:小步快跑,先窄后宽

设计技能系统最忌讳的是一开始就想做个“万能框架”。我见过不少团队,需求还没有完全清晰,就先把各种抽象接口、插件化架构、热插拔机制都安排上了。结果项目做完,核心业务就用了三五个技能,那几十个抽象类全成了摆设。

我的建议是反着来:先写具体业务的硬编码流程,把流程跑通,再从中提炼可复用的技能。比如你要做一个自动周报生成工具,第一版你可以直接在代码里写死:查Jira任务、查Git提交记录、拼装周报文本。跑通之后你会发现,“查任务”和“查提交记录”这两个动作在很多其他场景里也用得上,这时候再抽成独立的技能注册进去。

这个顺序很重要。先窄后宽,你提炼出来的技能边界是经过实际业务验证的,大概率是合理的;反过来先定了抽象再套业务,很容易出现技能定义和实际需求对不上的情况。

另外,单个技能内部也不要设计得太复杂。如果一个技能需要执行20个步骤,你不如拆成4个5步骤的子技能再加一个编排器。否则一旦中间某一步出了错,回退和重试的逻辑会写得非常痛苦。

3. 核心实现细节与关键环节拆解

3.1 技能定义的结构设计

具体到代码层面,技能定义我一般是这样的结构:

@dataclass class SkillDefinition: skill_id: str # 全局唯一标识,如 "order.query_detail" name: str # 展示名称,如 "查询订单详情" description: str # 功能描述,用于模型决策 input_schema: dict # 输入参数JSON Schema output_schema: dict # 输出结果结构定义 timeout_seconds: int = 10 retry_policy: str = "none" # none / once / exponential require_auth: bool = False enabled: bool = True

skill_id的命名我觉得值得单独说两句。很多人爱用这种ID:skill1、func_001、queryOrderDetail。这在小规模项目里没啥问题,但一旦技能数量上了量级,这种命名会让日志和排查变得非常痛苦。我的习惯是用领域.动作两级命名,比如order.query_detail、project.member_list。这样哪怕日志里只打了这个ID,你一眼就知道它大概是干什么的。

输入输出Schema我用JSON Schema标准定义。为什么一定要这个格式?因为它既能用于运行时的参数校验,又能直接转换成模型平台Function Calling需要的格式,还能自动生成API文档。一套定义多处复用,省掉大量重复工作。

一个反例:我有一次图省事,参数定义写得不严谨,date字段没约束格式,结果模型有时传2025-01-15,有时传2025/1/15,后端解析直接崩。后来我花了整个下午把所有技能的入参全部规范成标准格式,并加了自动校验,才消停。参数定义这种事情,前期偷懒,后期加倍还。

3.2 技能注册与加载流程

技能的注册方式,最简单的方案是:写一个全局注册表对象,启动时加载所有技能定义。

class SkillRegistry: def __init__(self): self._skills = {} def register(self, skill: SkillDefinition): if skill.skill_id in self._skills: raise DuplicateSkillError(skill.skill_id) self._skills[skill.skill_id] = skill def get(self, skill_id: str) -> SkillDefinition: return self._skills.get(skill_id) def list_enabled(self) -> list[SkillDefinition]: return [s for s in self._skills.values() if s.enabled]

这里有一个我特别想强调的点:不要在应用层直接扫描模块来做自动注册。Python的反射、Java的注解、Node的装饰器,这些机制看着很优雅,但出了问题非常难查——你根本不知道哪些技能被加载了、加载顺序是什么、重复定义了谁覆盖谁。

我现在的做法是显式注册。入口处一个文件把所有技能定义按顺序列清清楚楚,就像菜谱目录一样。每次新增技能,都会在这个文件里增加一行。丑是丑一点,但可读性和可排查性直接拉满。

技能的执行则建议走一个调度器,而不是直接在业务代码里调用函数。调度器做了几件事:参数校验、权限校验、超时控制、重试、日志记录、结果结构化返回。

class SkillExecutor: def __init__(self, registry: SkillRegistry): self._registry = registry def execute(self, skill_id: str, params: dict, context: dict) -> SkillResult: skill = self._registry.get(skill_id) if not skill: return SkillResult(success=False, error=f"skill not found: {skill_id}") validate_params(params, skill.input_schema) try: output = run_with_timeout(skill.handler, params, skill.timeout_seconds) validate_params(output, skill.output_schema) return SkillResult(success=True, output=output) except TimeoutError: return SkillResult(success=False, error="timeout") except Exception as e: return SkillResult(success=False, error=str(e))

把执行逻辑收敛到调度器,还有个额外好处:可以在这里统一打结构化日志。每一条技能调用记录都包含skill_id、入参、出参、耗时、成功与否,这些日志积累下来,就是一套很宝贵的调试和优化素材。

3.3 模型调用循环的设计

技能系统通常是嵌在某一个Agent主循环里的。循环的基本逻辑就是:模型看到用户输入,决定调哪个技能,系统执行技能,把结果返回给模型,模型根据结果生成下一轮输出或者再调下一个技能。这个循环写起来不难,关键在于状态管理和终止条件。

状态管理要处理的是:多轮技能调用之间,模型需要记住已经拿了哪些数据、当前走到流程的哪一步。实现上可以维护一个context字典,在每轮执行后把关键数据塞进去。我在实际项目中会把context分为两层:user_context存用户原始输入和会话历史,task_context存当前任务的中间状态。区分开的好处是,你可以很方便地把某个状态单独拿出来调试。

终止条件要处理的是:什么时候算任务完成,什么时候算任务失败需要放弃。我见过最魔幻的bug是:模型重复调用同一个技能,因为第二次返回的结果比第一次多了一个字段,模型居然以为任务“更进了一步”,于是又发起第三次、第四次……直到把API配额烧光。后来我加了两条硬性规则:同一技能在单轮任务里最多调用3次;如果连续三次执行结果都没有让关键状态发生变化,直接终止并请求用户确认。

3.4 可观测性:技能执行的记录与回放

做Agent系统,可观测性真的不是可选项,而是必需品。模型决策过程本身就是一个黑盒,如果你的技能执行过程也无从追查,那问题定位基本只能靠瞎猜。

我落地了三样东西,效果立竿见影。

第一,每一次技能调用都打结构化日志。字段包含时间戳、技能ID、入参、出参、耗时、结果状态、错误信息、所在任务ID。排查问题的时候,直接按任务ID查全部日志,就能还原完整执行链路。

第二,每次技能调用生成一个“推理痕迹”(trace)。这个trace不只是记录输入输出,还要把技能描述、模型在调用前的思考片段也记录下来。这样你能看清楚模型是基于什么信息做出这个调用决策的。

第三,保留低层级回放能力。把关键请求的完整交互序列存下来,出问题时可以回放一遍,模拟当时的情景,复现bug。

这些机制做下来,前期会感觉有点重,但一旦系统上了生产、开始接真实用户流量,你就会知道这套东西有多值钱。

4. 项目应用场景与影响范围

4.1 企业内部流程自动化

Agent技能最典型、也最能出成绩的落地场景,就是企业内部流程自动化。拿我最常举的例子:一个新员工入职,需要开通账号、申请工位、配电脑、加群、设定权限。传统做法是员工填一堆表单,管理员一项项操作,耗时一到三天。基于技能系统改造之后,模型读邮件或者表单内容理解新同事的姓名、部门、职级,然后依次调用“创建账号”“分配工位”“申请设备”“加入通讯录”这些技能,再人工审核关键步骤,整个流程从几天压缩到十几分钟。

这类项目的价值不只是快,更重要的是解放了重复劳动。管理员不用再做机械操作,只需要在关键节点审核确认。而且因为有全程执行记录,审计合规的问题也顺带解决了。

4.2 开放式信息整合与个性化生成

另一个值得关注的场景,是做信息整合类的Agent应用。比如用户对模型说:“帮我整理一下这几个客户最近跟我们的往来记录,然后写一封跟进邮件草稿。”

这类任务的特点是信息源分散、步骤不固定。技能系统能做什么呢?它负责把“查客户列表”“查某客户历史记录”“汇总会话摘要”“生成邮件草稿”这几个动作串起来。因为每个动作都是独立的技能,中间任意一步失败了,系统都能单独重试或者换个策略,不会整个流程垮掉。

这块建议做内容生成类产品的同学重点关注。很多人的误区是以为模型生成内容是纯“想”出来的,实际上真正高质量的内容生成,依赖大量的精准信息输入。技能系统把这些输入获取的路径标准化之后,内容的稳定性和质量都能提升一个档次。

4.3 对开发范式的影响

agent-skills的概念慢慢推开之后,对整个AI应用开发范式已经有了一些看得见的影响。

以前开发一个AI应用,核心工作几乎全部在模型本身:调参、调提示词、换更聪明的模型。现在越来越多团队意识到,模型能力是扁平的,真正的竞争力在应用层对任务的拆解和编排上。一个能把任务拆细、技能定义得清晰的团队,哪怕用同一个模型,做出来的产品体验也完全不一样。

这有点像早期前后端分离的过程。以前HTML、CSS、JS混在一起写,后来大家发现分层之后开发和维护的效率高得多。技能系统做的是类似的事情:把模型能力、任务编排、实际执行三者解耦,每一层都可以独立演进。

从更长远的角度看,技能系统还会催生“技能市场”这类生态。以后可能不同团队维护不同的技能包,需要的时候直接订阅接入。就像今天程序员在框架仓库里找现成的库一样。到那个时候,业务按需组合技能的过程,会比今天要快得多。

5. 常见问题与排查技巧

5.1 模型选错技能怎么破

模型选了错误的技能来执行任务是Agent落地最普遍的问题,没有之一。我遇到过的具体场景:用户问“订单为什么还没发货”,模型不去调物流查询技能,而是去调订单详情技能,结果拿到的数据里根本没有任何物流信息,回答就卡壳了。

排查的第一步不是换模型,而是检查技能注册表里的描述写得是否够清楚。大多数选错技能的情况,是因为描述之间出现了语义重叠。比如“订单详情”和“物流查询”,如果你写描述时都提到了“查订单”,模型当然分不清。

解决思路有几种:

  • 把描述写得更具体:加上“该技能返回物流轨迹和预计送达时间,适用于查询包裹运输状态”
  • 给典型的用户问法加上示例:描述里附带几条真实会被问到的问题
  • 在上下文中加上二选一的筛选提示:模型在决策前先列一下候选技能,排除明显不合适的

另外一个经验:如果用户输入和历史对话已经明显指向了某个技能,你可以先在代码层做一次简单意图匹配,命中再让模型产出调用参数。这种“代码先行、模型兜底”的策略能去掉一大半选错问题。

5.2 参数格式混乱导致调用失败

技能参数的格式问题,是我早期项目里翻车频率最高的。模型经常拿着一个自然语言时间“下周五”直接当参数传过来,而不是按Schema要求传标准格式。系统靠正则硬解析,一遇到口语化表达就会出错。

根治办法是我在前面反复强调过的:入参校验必须严格,校验失败就直接返回清晰错误提示给模型,让它自己修正。这里关键不是校验本身,而是错误提示要写清楚到底哪里不合法。你可以返回“参数date必须是YYYY-MM-DD格式,当前收到值为:下周五,请转换为具体日期后再调用”,模型看到这个提示后绝大多数情况能自行纠正。

如果你的模型平台已经收敛了很多错误处理能力,那你会省心很多。但哪怕是这样,参数校验层也不建议省,毕竟你没法控制用户的表达方式。

5.3 组合技能执行到一半失败怎么办

这个属于组合编排里没法回避的问题。比如“邀约客户”技能,前面查日程成功了,后面发邮件失败了,你希望模型怎么办?

我推荐的策略是:把每一个原子技能的执行结果都标记为“可以重试”或者“不可重试”。查日程这种幂等操作,失败可以无限重试;发邮件这种非幂等操作,重试策略必须带上“是否已发过”的状态判断,防止用户收到十几封一模一样的邀约邮件。这个判断标准不写在技能本身里,而是写在组合技能的执行策略里。

编排顺序上,我的建议是把不可逆的动作尽量往后放。比如先把所有数据查齐、校验过,再去执行“发通知”“改状态”这类操作,能大幅度减少半途失败的损失。

5.4 技能调用延迟太高怎么办

模型先发起技能调用请求,等到执行完再回去生成文本,这个来回过程有时会让人觉得“卡”。尤其技能内部还有多个外部调用时,总延迟会叠加。

有几个优化手段我觉得很实用:

  • 并行:同一轮里如果多个技能之间没有依赖关系,同时并发执行,不要一个一个排队。
  • 预热:高频技能的服务常驻内存,不要在每次调用时才初始化客户端。
  • 分级超时:请求外部API的HTTP超时设短一点,宁可失败快速上报,也不要长时间卡在原地。
  • 缓存:对查询类的技能,在数据新鲜度允许的范围内做结果缓存,能少查一次算一次。

5.5 加技能加多了,模型反而不会用了

还有一个问题很有迷惑性:技能从5个加到20个之后,模型调用准确率不升反降。原因不复杂——技能描述在模型眼里都是纯文本,描述越是相似,模型越难区分。你加得越多,选择空间的噪声越大。

应对方法有两个方向。一个是合并,把经常一起被调用的几个技能合并成一个组合技能,减少模型的决策负担。另一个是分组,把技能分成不同命名空间,在提示词里按用户意图预先过滤掉不相关的技能组。

我做过对比测试:同一场景下,15个技能如果全部暴露给模型,准确率大约70%;把技能按场景分组后,每次只暴露和场景相关的5-8个,准确率能提高到90%以上。这个差距非常大,强烈建议在技能数量增长时跟上分组策略。

6. 一些真实的经验体会

做agent-skills这套系统的过程,让我对AI应用开发有了一个新的认知:真正考验工程能力的地方,从来不是如何把一个技能跑通,而是如何在整个流程复杂化之后仍然保持稳定、可维护、可排查。技能系统的价值,也正是在这种复杂度到来之后才真正浮现出来。

如果你现在正准备在项目里引入技能系统,我最后的建议是:先别追求大而全。沿着一条真实的业务路径,写三到五个技能,跑通一个完整流程,然后反复打磨注册表结构、参数校验逻辑、错误处理机制。等你处理过一次技能调用失败、成功让模型自己完成纠错之后,你对这套系统的理解会完全不一样。

等这些基础体验过一遍,再回头去搭组合技能、做并行优化、加上更细的权限控制,就会顺理成章得多。技能系统做得好不好,最后拼的就是这些细节打磨得到不到位。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进