资讯动态

Agent技能库设计实战:从工具调用到带说明书的技能包

发布时间:2026/10/13 23:16:09 来源:尧图企业网站定制
半年前我开始做一个小项目姑且叫它 agent-skills。起因很简单我手上有个基于大模型开发的智能体能聊天、能查资料、能操作内部系统但用起来总有一种“什么都懂一点什么都干不精”的别扭感。你问它技术问题它给你长篇大论你让它查一份具体合同它在工具列表里翻半天最后告诉你“我找了一下好像没有”。问题不在模型能力而在任务的拆解方式太粗放了。后来我重新设计了这套技能体系把智能体的能力从“一堆工具”升级成“一组带使用说明书的技能包”情况才彻底改观。如果你也在做大模型应用尤其是带工具调用或自主决策的 Agent这篇文章值得看完。我会把 agent-skills 从设计思路、技能定义格式、注册调度机制到实际踩过的坑完整梳理一遍。不是讲概念是讲我反复调过的真实工程方案代码也是可以直接拿去改的。1. 为什么需要一套 Agent 技能库1.1 从一次失败的问答机器人改造说起我最早做的是一个内部知识库问答机器人。设计很常规把文档切段、向量化、存数据库用户提问时先召回相似片段再把片段拼进 Prompt 交给大模型生成回答。上线后跑了两周发现一个特别尴尬的情况用户问“帮我写一份关于新项目的周报”机器人会检索出一堆文档然后生成一份“看起来像模像样”的周报。但你仔细一看里面引用的项目编号是半年前的参与人员名字是错的甚至连项目当前状态都过时了。问题出在哪当时我的 Agent 确实挂着几个工具比如“文档检索”“项目信息查询”“人员信息查询”但工具和任务之间没有建立起可靠的路由。模型遇到复杂任务时经常随机挑一个工具用或者干脆绕过工具直接靠训练数据里的记忆去编。你可以把这个阶段理解为你把一堆专业设备扔给一个实习生但不告诉他什么场景该用哪台设备结果就是要么不用要么乱用。agent-skills 的出发点就是要解决这种“设备有了使用说明书没有”的尴尬。它不关心你底层接了多少 API、数据库、内部系统它关心的是把这些能力组织成一段段可以被模型理解的“技能单元”每个单元都写清楚自己负责什么、在什么条件下被触发、需要什么参数、怎么执行、输出长什么样。模型不再面对一个扁平的工具清单而是面对一本结构清晰的“能力菜单”。1.2 技能化的核心价值把 Prompt 工程沉淀成可复用资产很多人都知道调大模型应用核心其实是在调 Prompt。于是你会遇到一个典型场景项目需求变了你在某个系统提示词里加了一句话发现另一个场景也要这个能力你又复制一版改几个字再后来多处 Prompt 里的逻辑互相矛盾A 场景改了B 场景忘了同步。最后整个项目变成一座随时会塌的纸牌屋。技能化解决的就是这个“混乱复制”问题。它把一段能力从“写在 Prompt 里的话”变成“一个独立封装的模块”模块自带描述、参数、执行逻辑和调用约束。当多个场景都需要同一能力时大家引用同一个技能模块而不是各自复制一段内容。这样有三个直接好处第一能力可维护。想调整搜索逻辑只需要改一个技能模块所有用到它的场景自动生效。第二行为可预期。每个技能的执行边界是清晰的模型通过描述来判断“这个任务该不该用此技能”而不是靠感觉。第三增量更轻松。新增能力时不需要改动整体系统提示词只需要新增一个技能包并告诉模型它的存在。我在这个阶段还深刻体会到一件事技能化的本质是把人类的“岗位说明书”思想搬到了智能体身上。招人时你不会只告诉新人“你是工程师”你会列岗位职责、协作边界、工作产出。Agent 也是一样给它一个模糊的岗位它就只能给你一个模糊的结果。2. 技能体系的设计思路2.1 技能长什么样四段式结构设计 agent-skills 时我把每个技能定义成了四段式结构这也是整个体系的骨架dataclass class SkillDefinition: name: str description: str parameters: dict execute: callable instruction: str safety_tags: listname 是技能的唯一标识必须简短、语义清晰。description 是写给模型看的一句话说明核心作用是帮助模型做路由决策。parameters 定义调用该技能需要传入的参数包含类型、含义、限制条件。execute 是真正执行技能的函数可以是同步函数也可以是异步任务甚至可以只是一个描述性的“要让另一个子 Agent 完成”的元操作。instruction 字段容易被忽略却非常关键。它不是给模型看的而是给调用方看的。比如技能内部要调用某个外部 API但有鉴权流程、有超时限制这些实现细节不适合塞进模型的上下文里放进 instruction 字段就行。这样一段技能既能被大模型自主调用也能被普通代码直接调用做到了“人机同用”。还要单独说明 safety_tags。它标注这个技能是否会操作外部系统、是否会发送消息、是否会修改数据属于权限控制字段。模型可以在 Prompt 里看到技能描述但真正执行前框架会检查当前对话的安全级别是否满足技能要求。比如“发送邮件”这个技能在测试环境中可以被自由调用在正式环境中就要求用户二次确认。2.2 技能描述怎么写才容易被调用我发现很多人做技能库最后死在“描述”上。常见写法是“执行文件搜索”。这种描述对模型来说基本无效因为它没有告诉模型什么时候该用搜索的逻辑范围是什么输入什么输出什么描述写得太笼统模型就会在多技能场景下陷入选择困难。我的建议是把 description 写成一段“选择条件 能力边界 输出格式”的完整说明篇幅控制在三到五行。以文件搜索技能为例我实际用的是这样一段当用户需要根据文件名、路径关键词或者文件内容关键词查找本地文档时使用。支持按扩展名过滤返回结果为文件路径列表每条路径包含文件名、所在目录、修改时间和大小。如果用户想搜索的不是文档而是在线网页或外部数据库不要使用本技能。这段描述解决了三件事它的适用场景、它返回什么格式、它不负责什么。特别是“不要使用本技能”这种负面约束能大幅降低模型误调用概率。我在实测中发现加了负面约束后误调用率下降了接近六成。parameters 的写法同样有讲究。除了基本的类型定义我强烈建议加一个枚举或示例值。模型在生成参数时如果看到示例会大幅度减少“编造参数”的情况。比如parameters: query: type: string description: 搜索关键词 example: 项目周报 file_type: type: string enum: [pdf, docx, xlsx, md] default: 有了 enum 和 example模型生成的参数就会收敛在一个安全范围内。我在早期版本里没做这个限制模型经常传一个根本不存在的文件类型进来结果就是技能抛异常整个链路中断。2.3 技能注册与上下文管理技能库不能是“一筐技能扔进去”必须有一个注册中心和清晰的上下文管理机制。我采用的是“手动注册 自动装载”策略。在框架启动阶段开发者把所有技能定义写入注册表注册表会做三件事检查重名、校验参数 schema、检查 execute 函数是否可调用。任何一个环节不通过注册失败并抛出明确错误。这段注册逻辑虽然简单但在团队协作时价值极大——多人同时往技能库添加能力重名、错误配置是最常见的问题在注册阶段拦掉比运行阶段报怪错好太多。上下文管理则要解决“模型到底该看到哪些技能”的问题。假设系统里有五十个技能你不能把五十个技能描述全部塞进系统提示词那样既占用上下文窗口又会干扰模型的决策。我的方案是给技能打标签当前对话基于意图识别结果动态选择十个以内的技能进入模型视野。这就像图书馆有几十万册书但读者面前只摆着相关书架上的那几本。3. 从零搭建 agent-skills 技能框架3.1 基础框架与技能载体接下来进入实操环节。我会用一个最小可运行的 Python 项目演示 agent-skills 的完整闭环。不要被代码量吓到这个框架的核心逻辑其实只有不到两百行。先创建项目结构agent-skills/ ├── skills/ │ ├── __init__.py │ ├── registry.py │ ├── base.py │ ├── file_search.py │ ├── schedule_notice.py │ ├── web_summary.py │ └── loader.py ├── main.py └── skills.yamlbase.py 里放的是技能基类也就是上面见到的 SkillDefinition。为了更好用我给它加了一个 from_dict 方法方便从 YAML 配置创建技能声明# skills/base.py from dataclasses import dataclass, field from typing import Callable, Optional, Any dataclass class SkillDefinition: name: str description: str parameters: dict execute: Callable instruction: str safety_tags: list field(default_factorylist) classmethod def from_dict(cls, data: dict, execute: Callable): return cls( namedata[name], descriptiondata[description], parametersdata.get(parameters, {}), executeexecute, instructiondata.get(instruction, ), safety_tagsdata.get(safety_tags, []), )这里我刻意把 execute 作为参数传入而不是从配置里读取因为代码函数不能被 YAML 序列化。配置文件只负责描述技能真正的执行逻辑还是要回归到 Python 函数中。3.2 核心调度逻辑让模型学会选技能技能调度是整个系统的神经中枢。它要完成两个任务第一把可用的技能描述组合成一段结构化的“系统提示”第二解析模型返回的调用意图执行技能并组织最终回复。我把调度逻辑写成一个简洁的循环# main.py 核心调度逻辑 # 伪代码用于说明核心流程 def run_agent(user_message, available_skills): # 1. 组装系统提示 skills_block [] for skill in available_skills: skills_block.append(format_skill_spec(skill)) system_prompt build_system_prompt(skills_block) # 2. 让模型决策直接回答还是调用技能 response llm.chat( systemsystem_prompt, messages[{role: user, content: user_message}] ) # 3. 如果模型决定调用技能解析参数并执行 if response.action call_skill: result execute_skill(response.skill_name, response.skill_params) final_answer llm.chat( systemsystem_prompt, messages[ {role: user, content: user_message}, {role: assistant, content: response.original_text}, {role: tool, name: response.skill_name, content: result} ] ) return final_answer return response.textformat_skill_spec 函数的作用是把技能定义转成模型友好的文本。我实际使用的格式类似这样[技能名] file_search [适用场景] 当用户需要根据文件名、路径关键词或者文件内容关键词查找本地文档时使用 [参数说明] query: 搜索关键词; file_type: 可选取值 pdf/docx/xlsx/md [输出说明] 返回文件路径列表包含文件名、目录、修改时间和大小 [限制] 仅限本地文档不适用于在线搜索我在反复调试后发现一个重要原则这个技能列表的格式必须稳定每次请求都完全相同不能时有时无。大模型对格式一致性的依赖超乎想象你哪怕只多了一个空行它都可能在解析技能参数时出现异常。3.3 示例实现三个实用的基础技能第一个技能是文件搜索。它的执行函数如下# skills/file_search.py import pathlib from typing import Dict, Any, List def search_files(query: str, file_type: str , base_path: str .) - List[Dict[str, str]]: results [] root pathlib.Path(base_path).resolve() for path in root.rglob(*): if path.is_file(): if file_type and path.suffix.lstrip(.) ! file_type: continue if query.lower() not in path.stem.lower() and query.lower() not in str(path): continue stat path.stat() results.append({ filename: path.name, path: str(path), size: stat.st_size, modified: stat.st_mtime, }) if len(results) 20: break return results注意我做了两个工程上的保护限制最大返回条数为 20避免一次性输出海量路径导致上下文被撑爆用 rglob 时指定 root.resolve()避免路径穿越问题。技能执行函数必须自己处理异常和资源限制不能指望调用方帮你兜底。第二个技能是日程提醒。这个技能看起来简单但踩坑价值很高。我发现纯函数无法“主动”提醒用户于是把技能定义成一个大模型驱动的“伪执行”。# skills/schedule_notice.py def add_schedule_notice(event: str, time: str, remind_before: int 10) - str: # 实际项目中这里会调用日历服务的 API return f已为【{event}】创建日程时间 {time}提前 {remind_before} 分钟提醒为什么说是“伪执行”因为真实环境中这个技能背后挂着一个任务调度队列。模型调用它时实际上只是向队列写入一条消息真正的提醒动作由后台定时任务完成。这里我要强调一个观点不要把技能执行过程理解成“立即得结果”有些技能是“发指令、后生效”这种异步设计在复杂 Agent 中很常见。第三个技能是网页摘要。它的作用是抓取指定 URL 的文本内容生成适量摘要。这个技能我特别注意了限制条件——只允许抓取特定域名防止 Agent 被恶意引导到不可信页面# skills/web_summary.py allowed_domains [example.org, docs.example.com] def fetch_web_summary(url: str) - str: from urllib.parse import urlparse domain urlparse(url).netloc if domain not in allowed_domains: return 该域名不在访问白名单中无法获取内容 # 实际项目中使用 HttpClient 获取页面并提取正文 return 页面摘要内容三个技能放在一起系统就有了一个多元能力的小生态。下一步要把它们全部注册进技能库。# main.py from skills.registry import SkillRegistry from skills.file_search import search_files from skills.schedule_notice import add_schedule_notice from skills.web_summary import fetch_web_summary registry SkillRegistry() registry.register_from_yaml(skills.yaml, execute_map{ file_search: search_files, schedule_notice: add_schedule_notice, web_summary: fetch_web_summary, })注册完成后同一套技能即可以被大模型场景通过调度器调用也可以被普通 API 场景直接调用。这个“双通道”设计让我在后期扩展时省了大力气。4. 踩过的坑与问题排查4.1 技能永远不被模型调用先检查描述再检查参数这是我遇到最多的问题而且排查方向经常被搞反。团队里有同事说“技能库加了好几个技能但模型死活不用”。跑到日志里一看模型输出里根本没有调用意图。第一反应是模型不聪明换了大参数模型还是不行最后才发现是技能描述写得过于笼统。我举个例子。最初我给文件检索技能写的描述是“在本地文档中搜索相关信息”。这个描述放在技能列表里模型根本搞不清它跟“网页搜索”有什么区别更搞不清什么时候该触发。后来我改成完整的三段式描述并加了“不适用于在线搜索”的限制问题迅速解决。如果你也遇到技能不被调用按这个顺序排查描述里有没有说清楚“什么情况下用”描述里有没有说清楚“什么情况下不用”参数说明里的 example 和 enum 是否齐全技能是否进入了当前模型的可见列表大多数时候问题不在模型在于你把一个“没有使用说明书”的工具硬塞给了它。4.2 技能参数格式总出错用 Schema 校验而非期望模型自觉另一个高频坑是参数格式。模型生成的 JSON 参数经常缺字段、类型错误、甚至把枚举值写错。我在早期版本里直接把模型生成的参数透传给 execute 函数结果就是每逢大版本模型升级参数错误率就会波动。后来我引入了 Pydantic 风格的严格校验所有参数在进入执行函数之前先做合法性检查。类型不对就拒绝执行并返回错误说明而不是硬着头皮用默认值。有一次模型把 file_type 传成“word”明显是它的拼写习惯被校验拦住后返回了“仅支持 pdf/docx/xlsx/md 格式”模型看到错误提示后重新生成了正确参数整个体验反而更顺畅了。这个经验的核心是永远不要把参数正确性建立在模型的“自觉”上要通过框架层做强制约束。模型是概率系统它大概率会输对但小概率的输错足以毁掉用户体验。4.3 技能之间互相“打架”用命名空间隔离状态当技能数量超过十五个之后我开始遇到另一个问题技能调用完以后其中的变量或资源没有清理干净导致下一个技能被影响。最典型的是文件句柄未释放以及某个技能修改了全局配置却没有恢复。这种 Bug 极难排查因为它们不是每次必现而是看运气。我的解决方案是给每个技能一个独立的执行上下文。具体来说execute 函数被包裹在一个隔离环境里运行技能内部的临时变量、打开的连接、写入的文件都在调用结束后统一清理。如果你用的是 Python最简单的做法是用 contextmanager 包一层或者把技能拆成独立服务。强烈建议技能内部不要修改任何全局状态哪怕只是为了“临时方便”也别这么干。我正在 debug 一个诡异问题某次对话中模型调用完“日程提醒”技能后紧接着调用“文件搜索”结果文件搜索的路径参数被悄悄替换成了一个日程字符串。排查到最后发现是一个全局变量被第二个技能误读了。加隔离后这个问题彻底消失。4.4 异步执行与超时控制技能系统上线后我很快发现一个现实约束不是所有技能都能在五秒内返回。网页抓取可能慢数据库查询可能慢调用外部 API 也可能慢。如果调度循环是同步等待用户的体验就是机器人“卡死”了。我后来把所有可能耗时的技能统一改成异步模式并且为每个技能配置超时阈值。一旦超时调度器返回“该技能执行超时请重试或换一种问法”。另外同一时间里最多只允许两个技能并行执行防止模型在复杂任务中一口气触发五个技能把下游系统打爆。技能名超时阈值是否异步安全级别file_search3 秒否只读schedule_notice2 秒是需确认web_summary10 秒是白名单域名这张表就是我在项目里实际使用的技能配置注册技能时可以直接参考。超时、异步、安全级别这三件事必须从第一天就想清楚不然后期改造的成本会很高。5. 这套体系还能怎么扩展agent-skills 目前已经能支撑我一个中等复杂度业务场景的日常运行但它还只是骨架。我认为最值得继续投入的方向有三个第一是技能评估自动化。现在每个技能靠人工描述、凭经验调试但如果技能数量过百就必须建立一套自动评估标准。比如给每个技能准备一组典型测试用例每次改动后自动跑一遍看调用率、参数正确率、执行成功率是否回退。第二是技能组合与工作流。目前模型是基于当前任务选择单个技能但真实需求往往需要多步操作。比如“整理某项目周报并发送给团队”这个任务涉及信息检索、文档生成、消息发送三个技能。下一步我计划引入技能链的概念让技能之间可以声明依赖关系模型负责编排框架负责保证顺序和上下文传递。第三是冷启动技能推荐。新场景接入时如何从已有技能库中挑选合适技能减少人工配置成本。我打算基于历史对话日志做意图聚类自动为场景推荐技能组合。但这些都是后话。如果你现在正要为自己的 Agent 构建技能体系我的建议很简单先别追求技能数量把手头最频繁使用的三五个能力做成高质量技能把描述写清楚、参数校验做严、执行函数做健壮然后把一个个场景往里接入。只有被真实业务反复打磨过的技能才算是真正合格的能力模块。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑