☰
Agent Skills实战:如何构建可复用的智能体技能体系
2026/10/8 5:05:56 网站建设 项目流程

最近很多朋友都在折腾自己的智能体,问得最多的一句话是:“你的agent到底会什么技能?”一开始我以为他们问的是模型能力,后来才明白,他们问的是agent能不能像人一样“会做事”。这背后就是今天想聊的主题:agent-skills,也就是智能体技能体系的构建。

agent-skills不是某一个具体工具,而是一整套设计思路:把agent能完成的任务抽象成标准化的技能单元,统一注册、统一调度、统一评估。它要解决的核心问题有三个:第一,让agent从只能“说”进化到能够“做”;第二,让能力可以沉淀、复用,而不是每次都现写提示词;第三,让多个agent之间形成能力互借,而不是各自造轮子。这篇文章主要适合正在做LLM应用开发、智能客服、办公自动化助手的团队,也适合那些手头有demo但不知道怎么走向生产的个人开发者。

1. 智能体技能体系的理解与设计

1.1 为什么“技能”比“提示词”更适合智能体

很多人第一次做agent,就是往系统提示词里塞一大段话:“你是企业助手,你会查天气、会订会议室、会发送邮件……”这种方案demo跑得通,但一上生产就露馅。原因很简单:提示词是静态的,而任务是多变的。你写进提示词里的功能描述再多,模型也只能“知道”这些功能,无法真正“执行”它们。它无法查数据库,无法调用外部API,也无法感知当前操作是否成功。

技能抽象改变的是这个基本逻辑。一个技能不是一句话,而是一个完整的能力单元。举个例子,同样是“查询天气”,提示词方案是告诉模型“你是天气助手,可以查天气”,模型会自己编一个天气出来;技能方案则是注册一个weather.query技能,底层挂载真实的气象API,并定义好城市、日期等参数。模型要做的是识别意图、填充参数、触发执行,而不是凭空生成结果。

用装修来类比可能更直观。提示词方案像是给工人一张图纸,图纸画得再细,工人也得自己买材料、自己加工;技能方案像是给工人一个模块化工具箱,里面的柜体、台面、五金件都是预制好的,工人只需要根据图纸选择合适的模块装上去。图纸当然重要,但决定工程质量和交付速度的,是工具箱里有什么模块、模块之间怎么衔接。

这就解释了为什么“技能”比“提示词”更适合agent走向生产环境。技能具备三个关键特性:可复用性、可测试性和可观测性。同一个“发送邮件”技能,可以被客服agent用,也可以被运营agent用;技能单独测试通过后,集成到任何一个agent里都相对可靠;技能每次执行都会留下日志和结果,出了问题能快速定位。这些特性在纯提示词方案里几乎做不到。

1.2 技能库的整体架构设计

聊技能就绕不开技能库,也就是agent-skills里“skills”的载体。我在项目中把技能库拆成四个核心模块:技能注册表、技能路由、技能执行器、技能上下文管理。这四个模块各司其职,组合起来就是一套完整的技能生命周期管理。

技能注册表是“技能的目录”,保存所有已注册技能的定义信息,包括技能ID、描述、参数结构、执行入口等。路由是“调度员”,根据用户输入和会话状态,从注册表里挑出最合适的技能。执行器是“真正的工人”,负责运行技能对应的代码逻辑或API调用。上下文管理则是“共享内存”,负责存放技能执行过程中产生的中间数据,以及技能与主对话之间的信息交换。

这个架构的关键决策在于,技能的定义、路由和执行必须解耦。如果把技能描述直接写在agent的主逻辑里,那每新增一个技能都要改主代码;解耦之后,新增技能只需要在注册表里加一条记录,主流程完全不用动。我实测下来,这种解耦对项目初期的开发速度提升非常明显,尤其是当技能数量超过20个以后。

架构上还有一个容易被忽略的点:技能之间的依赖和隔离。早期我设计技能库时,允许所有技能共享同一个执行环境,结果一个技能的内存泄漏直接拖垮了整个agent。后来改成每个技能独立进程或独立函数沙箱,成本略高,但稳定性好得多。如果你只是做轻量级agent,可以不用进程级隔离,但至少要在代码层面保证技能之间的变量和状态不互相污染。

2. 技能定义与实现的实操要点

2.1 技能模板的字段设计

技能定义是整个体系的地基。字段设计得不好,后续路由、执行、维护都会吃苦头。我在实践中打磨出一套比较通用的技能模板,核心字段包括id、name、description、parameters、executor、output_schema、permissions和version。

先看一个实际例子,这是我在项目中定义的一个会议预订技能:

{ "id": "meeting.book", "name": "预订会议室", "description": "根据日期、时间段和人数预订指定办公区的会议室。当用户表达需要开会、需要讨论空间时优先调用。", "parameters": { "type": "object", "properties": { "date": {"type": "string", "format": "date", "description": "会议日期"}, "start_time": {"type": "string", "format": "time", "description": "开始时间"}, "end_time": {"type": "string", "format": "time", "description": "结束时间"}, "capacity": {"type": "integer", "minimum": 1, "description": "参会人数"}, "office_area": {"type": "string", "enum": ["A区", "B区", "C区"], "description": "办公区"} }, "required": ["date", "start_time", "end_time", "capacity"] }, "executor": { "type": "http", "url": "https://internal-api.example.com/meeting/book", "method": "POST" }, "output_schema": { "type": "object", "properties": { "booked_room": {"type": "string"}, "status": {"type": "string"} } }, "permissions": ["meeting.book"], "version": "1.2.0" }

有几个字段需要特别强调。description是写给LLM看的,不是给人看的。模型靠这段描述来做技能匹配,所以必须说清楚“这个技能在什么场景下用”,最好带一两个典型用户表述。比如上面这个description里的“需要开会、需要讨论空间”,就是我从真实对话里抽出来的高频表达,加上之后路由准确率明显提升。

parameters建议严格使用JSON Schema规范。原因有两点:第一,LLM填充参数时需要明确的类型和取值范围,schema越严格,模型“编参数”的概率越低;第二,执行器可以用同一个schema做运行时校验,防止脏数据打到下游系统。executor字段则要区分类型,我见过三种方案:HTTP调用、Python函数调用、命令行调用。HTTP调用最灵活,适合微服务架构;Python函数调用延迟最低,适合单体应用;命令行调用适合老系统集成,但解析输出比较麻烦。

2.2 参数校验与技能路由

技能路由是agent-skills体系里最有意思、也最容易翻车的部分。路由模块负责回答一个核心问题:“用户这句话到底想触发哪个技能?”我试过两种主流方案:基于LLM判断和基于向量相似度。

基于LLM判断的思路是,把当前用户输入和上下文一起发给模型,让模型从注册表里选一个技能ID返回。优点是对语义复杂、需要推理的场景非常有效,缺点是每个请求都会增加LLM调用次数,延迟和成本都上去了。基于向量相似度的思路是,把用户输入向量化,跟每个技能的description向量算相似度,取top1。这个方案快且便宜,但无法处理“需要结合多轮上下文才能判断意图”的情况。

我最终采用的是混合路由:先用向量相似度做粗筛,筛出前3个候选技能;再把候选技能的description拼进一个轻量级prompt,让LLM做精排。这样既控制了成本,又保证了一定的语义理解能力。混合路由上线后,技能命中率从单用向量方案的81%提升到了94%左右,延迟只增加了大约200毫秒,性价比很高。

参数填充也值得单独说。即便路由选对了技能,模型也可能填出非法参数。比如预订会议时把开始时间写成“下午3点”,而schema要求的是“15:00”。我的处理方式是,模型产出参数后不直接调用执行器,而是先用JSON Schema做一次校验,不合格就带着错误信息让模型重填一次。这个“校验-重填”循环最多跑两轮,超时就返回技能调用失败,避免无限循环。

2.3 技能与工具、插件的边界

很多初学者分不清技能(skill)、工具(tool)和插件(plugin)的区别,但搞清楚这个概念对设计agent-skills至关重要。在我的理解里,工具是最小的可执行单元,比如“发送HTTP请求”“读取文件”“计算一个数学表达式”;技能则是面向业务场景的、组合了工具甚至其他技能的能力封装,比如“预订会议室”技能,内部可能需要“查询会议室可用时间”和“创建会议”两个工具。

插件则是一个更大的分发单位。按这个分层理解,技能是插件里的核心资产,插件是技能的打包分发形式。OpenAI Plugins生态和各类agent框架里的tool calling,本质上都是在做这一层抽象,只是粒度不同。设计技能时不要把粒度做得太细,否则维护成本极高;也不要做得太粗,否则无法复用。我自己的经验是:一个技能应当对应“用户可感知的一个完整任务片段”,说完一句话就能交代出去,执行后能给用户一个明确结果。

边界清楚了,代码结构也就清楚了。技能层只编排能力,不直接碰底层细节;工具层只做具体执行,不关心业务含义。分层设计之后,新增技能时基本只写编排逻辑,复用已有的工具,开发效率非常高。

3. 一套可落地的技能库搭建流程

3.1 场景分析与技能拆解

前面讲了不少设计理念,下面进入实操流程。我按自己落地agent-skills的方法,把它总结成了五个步骤:场景分析、技能拆解、技能实现、注册集成、评估迭代。

第一步是场景分析,就是收集足够多的真实用户对话,梳理出用户到底想让agent做什么。我建议至少准备200条真实对话记录,覆盖日常请求、异常请求和模糊表达。没有真实数据,就靠业务方的口头描述来穷举,效果会差很多,因为“用户实际怎么说”和“产品经理以为用户怎么说”往往是两套话。

第二步是技能拆解。拿一个“日程管理”场景举例。用户的主要诉求包括:新建日程、查看日程、改时间、取消日程、提醒我。最保守的做法是把每个诉求做成一个技能,于是有了schedule.create、schedule.list、schedule.update、schedule.delete。但拆完发现,schedule.list在很多场景下都要先拿到当前用户身份,而身份获取本身也是一个能力,于是我把它拆成了独立的user.get_context技能,供其他技能调用。这个“改时间”操作看似简单,但实际包含“先查旧日程”“再确认不冲突”“最后更新”,所以我让schedule.update在内部调用schedule.list和schedule.check_conflict两个子技能。拆解的判断标准只有一个:这个步骤能不能被其他场景复用。能复用,就拆出来;不能,就先留在当前技能内部。

第三步到第五步是持续开发的过程。我个人强烈建议技能实现后马上写测试用例,而不是等全部技能开发完再统一测。因为技能的输入输出是结构化的,非常适合自动化测试。我每个技能至少写三个用例:正常输入、边界输入、错误输入。这样后续改动技能逻辑时,回归测试能兜住大部分低级问题。

3.2 技能注册与加载机制

技能注册听起来简单,就是“把技能加进注册表”,但注册机制的设计直接影响系统扩展性。我在项目里做了一套基于配置文件的技能加载器,每个技能一个独立目录,目录里包含schema.json定义文件和executor.py执行逻辑文件,技能加载器启动时自动扫描目录,把每一个schema.json解析后注册到内存注册表。

import importlib.util import json from pathlib import Path class SkillRegistry: def __init__(self, skills_dir: str): self.skills_dir = Path(skills_dir) self.skills = {} self._load_skills() def _load_skills(self): for schema_path in self.skills_dir.glob("*/schema.json"): with open(schema_path, "r", encoding="utf-8") as f: schema = json.load(f) skill_id = schema["id"] executor_path = schema_path.parent / "executor.py" self.skills[skill_id] = { "schema": schema, "executor": self._load_executor(executor_path, skill_id) } print(f"[registry] loaded skill: {skill_id}") def _load_executor(self, path: Path, skill_id: str): spec = importlib.util.spec_from_file_location( f"skill_{skill_id}", path ) module = importlib.util.module_from_spec(spec) spec.loader.exec_module(module) return module def get(self, skill_id: str): return self.skills.get(skill_id)

这套机制的好处是“加新技能不用改框架代码”:在目录里新增一个文件夹,里面放好定义和执行文件,重启服务就完成技能注册。配合Git版本管理,技能的每次变更都有记录,出问题可以直接回滚。

注册表还需要支持动态更新,也就是所谓热加载。我实现了针对单技能目录的监听,文件变更后自动重新加载该技能。但要注意,热加载在进程级共享状态时需要特别小心。比如一个技能执行器内部维护了数据库连接池,热加载会覆盖连接池变量,导致旧连接泄漏。我的建议是:热加载只用于开发环境,生产环境务必走重新部署流程,避免各类诡异问题。

3.3 技能执行链路与错误处理

技能执行链路在我的系统里是这么走的:用户输入先进入主agent对话逻辑,主逻辑调用技能路由模块选出候选技能,路由返回最优技能ID后,agent把用户意图和已填参数包装成一个执行请求,交给技能执行器。执行器先做参数校验,再调用具体函数或外部API,拿到执行结果后把结果结构化回传给agent,最终由agent组织成用户可读的自然语言回复。

关键点在于执行结果的成功与失败信息都要结构化返回。我定义了一个统一的结果协议:

{ "status": "success", "biz_code": "MEETING_BOOKED", "data": { "room": "A区-302", "time": "2025-06-18 14:00-15:00" }, "trace_id": "a4f2..." }

失败时,status变成failed,biz_code说明失败类型,message携带可读错误信息,比如“会议室已被占用”。agent拿到失败结果时,不是直接甩给用户,而是根据错误类型决定是否重试、是否换一个替代技能、还是直接告诉用户原因。

错误处理里最容易被忽视的是超时控制。早期我没有给技能执行设超时,一个外部API如果卡死,整个agent会话就挂起,用户等来的只有超时。后来所有技能统一加上超时阈值,默认8秒,超过就终止执行并返回failed,同时把错误记录到日志。实在需要长时间运行的任务,我改成了异步任务机制:技能先返回“任务已提交”,后续通过回调或轮询方式通知执行结果。

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

4.1 技能调用不生效的排查思路

技能不生效是大家遇到最多的问题,症状通常是“模型明明选对了技能,函数也执行了,但输出结果还是不对”。排查时我有一套固定操作顺序。

第一,确认路由是否真的选对了技能。我会先查看agent的会话日志,看路由模块返回的技能ID是不是预期的。如果路由选错,优先优化技能的description,把典型用户表达加进去。第二,确认参数是否有缺失。很多执行失败是模型没填齐required字段导致的,这时候要看回调里的具体错误信息。第三,确认执行结果格式是否被主逻辑正确解析。我踩过一次坑:执行器返回的data字段是JSON字符串而不是对象,主agent拿到后直接拼进了回复,导致用户看到一串转义过的JSON字符。这类问题统一定义好输出格式就能解决。

如果以上都没问题还是表现不对,那就要检查模型的上下文里是否带了太多无关信息。技能执行结果返回后,主agent可能被系统提示词要求“只使用知识库回答”,这个指令压过了技能执行结果,模型就会忽略工具返回内容。解决方法是检查并调整系统提示词,明确“工具执行结果是最高优先级事实来源”,而不是把工具输出和普通文本混为一谈。

4.2 上下文过长与Token占用

技能体系引入后,一个新烦恼是Token消耗明显上升。技能数量多了以后,即使每个技能的description只有几百字,全部塞进系统提示词也会挤占上下文窗口。我试过在上下文里放了40个技能定义,单是技能部分就占了3000多Token,对话轮次一多必然触发长度上限。

解决思路有两个方向:一是控制单个技能的description字数,我强制要求不超过50个字,把“详细说明”挪到技能定义文件内部,不给LLM看;二是做技能筛选,只把可能当前会话会用到的技能定义放进上下文。路由模块先做一个粗筛,把与当前会话相关的5到8个技能完整定义注入上下文,其他技能只保留ID和一句话摘要。这样既保证了路由信息的完整性,也控制了Token消耗。

另外一个容易忽略的Token陷阱是历史对话记录。很多agent会把所有历史对话都完整保留,技能执行的大量中间结果也都在历史里,这样上下文很容易爆掉。我的做法是:技能执行结果不回传原文,只保留结果摘要;多轮历史对话超过一定长度后,用LLM压缩成结构化摘要再继续。实测Token消耗能减少大约一半。

4.3 技能冲突与优先级设计

当技能库规模到几十个时,冲突问题就出现了。最典型的是两个技能描述高度相似,比如schedule.update和schedule.delete,用户说“帮我把明天下午的会议改掉”,既可以被理解成更新,也可以被理解成取消,路由经常选错。

解决这类问题,我在路由环节引入了优先级机制。每个技能多了一个priority字段,表示“当与其他技能候选冲突时,谁优先”。例如schedule.update的优先级高于schedule.delete,因为用户说“改掉”通常意味着要换时间而不是取消。优先级由人工标注并在测试集上验证,这个环节需要业务侧配合。我在项目里拉上业务同事,一起掰扯了十几个这类冲突case,才把优先级列表调好。

还有一种冲突是技能内部子技能调用导致的递归死循环。A技能调用B技能,B技能又调用A技能,如果不设深度上限,服务就直接卡死。我在技能执行器里增加了一个调用链深度计数器,默认上限为3层,超过即抛异常并返回友好错误信息。设计原则是:技能不要互相引用,层级超过两层就应该考虑把公共部分抽成工具,而不是技能套技能。

5. 实战体会与下一步规划

技能库搭起来半年,最深的体会是:agent-skills的本质不是做出一堆技能,而是建立一套能力持续沉淀的机制。今天加了一个技能,明天就能被其他agent复用;今天踩了一个错误处理的坑,明天就能在体系层面避免。这种复利效应,是纯写提示词完全感受不到的。

下一步我计划做两件事。第一,把技能评估工作做得更细,现在每个技能上线前虽然有测试用例,但缺少与真实用户反馈的结合。准备做一套技能成功率追踪,从线上日志里抽取技能调用样本,定期人工复核路由和执行的正确率,形成技能健康度报表。第二,考虑让agent具备“自我学习新技能”的能力:当用户请求没有命中任何已有技能时,记录为新技能候选,由开发人员审核后补充到技能库。这本质上是在给agent做能力积累的闭环,虽然还比较早期,但方向我觉得是对的。

最后再分享一个小技巧:别把技能库设计得过于复杂,刚开始三五条技能就够用,先跑通链路,再逐步扩充。很多人一上来规划几十个技能,结果光是参数打架和维护描述就消耗了大量精力,反而做不成事。agent的能力建设是个长线过程,稳一点,比什么都重要。

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

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

立即咨询