资讯动态

AI Agent开发实战:从零构建可复用的agent-skills技能体系

发布时间:2026/10/13 6:01:12 来源:尧图企业网站定制
做AI Agent开发越久越发现真正拉开项目差距的往往不是模型选得多强而是你有没有一套能把模型能力落地成“可执行动作”的技能体系。我常跟朋友说Agent就像一个新员工模型是它的脑力agent-skills就是它的工作手册——没有手册再聪明也不知道先干什么、怎么干。这篇文章不聊空泛的架构只讲我在实际项目中设计、落地一套agent-skills的完整思路和踩过的坑适合正准备给智能体做能力沉淀、或者已经做完却总感觉调用混乱的开发者。我最初接触这个名词是在一个内部工具型Agent项目里当时所有能力都写成一堆if-else模型只会根据输入关键词硬匹配结果就是稍微换一种问法就失灵代码里还堆了一大堆分支。后来我把它彻底重构按照“技能”的方式组织能力效果立竿见影调用准确率上去了代码也清爽了。这套方法论不依赖特定框架也不绑定单一模型更像是一种设计规范。下面我会从设计思路、核心字段、实操代码、排查经验四个层面展开尽量做到让一个刚入门的开发者也能照着重现。1. 先从“能力池”说起Agent到底需要什么样的技能1.1 为什么不能把技能直接写死在代码里很多人做Agent时第一反应是“我先定义几个函数让模型去选”。这本质上没错但问题出在“函数”这个概念太编程化了——它天生缺少弹性写死了入参类型、返回结构模型只能在你预设的缝隙里做事。比如你做了一个天气查询函数签名是get_weather(city: str)模型从用户的话里抽出一个城市名填进去看起来很顺但一旦用户问“今天杭州上海哪里更热”模型就得连续调用两次再比较而你的函数根本没有反馈“比较”这个动作的入口于是只能靠模型自己搞最后往往搞错。把技能写死在代码里的另一层坏处是耦合。技能一旦和主流程纠缠在一起改一个就牵一发动全身加一个新能力要动老逻辑两个人的代码还容易冲突。我在第一个版本就是这么干的用一个超大的字典存储了所有动作和对应函数越写越臃肿到后面连我自己都分不清哪个分支会有副作用。agent-skills的思路是把每一项能力都当成独立、可描述、可组合的“技能”。技能不是函数而是“一个带有标准接口和自描述信息的功能单元”。它告诉模型我叫什么、我擅长解决什么问题、我需要什么参数、我会返回什么结果。模型只看描述就能决定要不要调用调用后还能根据结果决定下一步。这种设计把“能力”和“记忆”分离逻辑上非常接近人类的工作方式——你不需要把每个步骤都写在流程图上只要知道“会修水管的人可以解决漏水”就够了。1.2 一套技能的三个层次我习惯把agent-skills里的技能拆成三个层次对应不同的复用粒度第一层是原子技能也就是不可再拆的单一动作。比如“按城市查天气”“获取当前时间”“计算两个日期之间的天数”“调用某接口发送通知”。它们只做一件事输入明确输出简单通常对应一个函数或一次API调用。原子技能是整个体系的砖块必须保证质量稳定、边界清晰。第二层是组合技能它把多个原子技能串成一条流程。比如“安排一场会议”会涉及“查看参会人空闲时间”“选择一个时间窗”“创建日历事件”“发送通知”这四个步骤。组合技能内部可以有自己的状态和编排逻辑但对上层模型来说它仍然是一个整体描述时写成“根据参会人档期自动安排会议并通知”。第三层是策略技能它不是直接执行动作而是指导Agent“该怎么想”。策略技能可以是一条决策规则、一套优先级标准甚至是一段Prompt模板。比如“当用户要求推荐方案时优先考虑成本最低的方案”或者“如果查询结果为空自动放宽条件再试一次”。这类技能不产生直接的物理动作却能显著影响Agent的行为质量。三个层次不一定要分开部署但设计时要心里有数。我以前只做第一层结果流程逻辑全散在模型对话里每次都要靠模型临场拼装稍微复杂一点就断链。后来加了组合技能和策略技能Agent的稳定性和可解释性明显提高。简单说原子技能负责“能做什么”组合技能负责“怎么串”策略技能负责“怎么选”。1.3 设计原则高内聚、低耦合、可观测技能设计跟写代码一样得讲设计原则。我踩过无数坑后总结出三条最管用的高内聚一个技能只解决一类问题不要试图做一个万能技能。我见过有人把“查询”和“修改”写在一个技能里理由是“都跟数据库有关”结果模型选技能时经常误调用把读取操作误判成写入。技能的内聚性直接决定了模型选择的准确率内聚越低描述就越模糊越容易出错。低耦合技能之间不直接互相调用统一通过接口或者编排层交互。我维护过一个“发票识别”技能它内部直接调用了“OCR解析”另一个技能的函数后来OCR改了内部实现发票识别差点崩了。改成技能与技能之间只通过输入输出传数据后再也没出过这种事故。可观测每个技能要能记录自己什么时候被调用、参数是什么、返回结果是什么、耗时多少。别小看这条没有可观测性后面排查问题简直是大海捞针。我第一次上线时没做日志模型连续三次调用失败我花了整整一天才意识到是某个技能返回了空列表而代码里根本没有处理空列表的分支。这三条原则听起来简单但真正做到需要你在每个技能设计时都停下来问一句这个技能的边界在哪它和别的技能有没有隐性依赖如果出了错我能不能通过日志快速定位我的做法是写了一个技能模板把记录日志的逻辑内置进去强制所有新技能都带上基础的可观测能力。2. 核心细节解析给技能画一张“标准身份证”2.1 技能描述字段怎么定模型不会阅读你的源码它只能看到一个技能的文字说明。这个说明的质量比函数写得多优雅重要一百倍。我把每个技能的标准字段定为七个name、description、parameters、required_parameters、returns、errors、tags。用一个JSON对象来表示加载后可以直接传给模型或用于本地匹配。name要短、稳、语义明显比如check_weather不要用get_weather_data_async_v2这种带版本号的丑名字模型容易字符串匹配错误。description是最关键的一行开头先写“这个技能用来做什么”再写“适合在什么场景下调用”最后补一句“不适合做什么”。比如描述分子技能时我还会刻意加入同义词和边界词。比如查询天气的技能描述里可以写“支持今天、明天、一周预报也支持‘早上冷不冷’这类模糊问题”后面加一句“如果要对比两个城市的温度请分别调用后再由模型自行比较”。这样模型就知道这个技能的边界在哪不会强行让它去干不属于它的活。parameters用JSON Schema描述required_parameters单独列出必填项。returns写清返回结构比如“返回一个对象包含date、temp_max、temp_min、condition无数据时返回空列表”。errors说明可能会抛出的错误码或错误信息给模型一个处理预期。tags是我的小习惯给技能打上“工具”“查询”“系统”这类标签方便做预筛选。2.2 参数声明与校验模型天生会“编参数”尤其是当它不确定你期望什么格式时。比如你说要查天气它可能给你传一个{location: 杭州天气怎么样}把用户原话整个塞进去。为了防这种情况参数声明必须具体到类型和格式。JSON Schema是真的好用能声明string、number、integer、array、object还能限定枚举和正则。比如城市名我用type: string, pattern: ^[\u4e00-\u9fa5]{2,10}$来限制只能传入2到10个中文字符。日期字段用format: date如果模型传了“后天”这种相对时间我会在技能层先做一次“时间表达标准化”把它换算成具体的YYYY-MM-DD。这一步非常关键因为很多模型对日期相对表达处理不稳定与其信任它的换算不如写一个时间解析小模块统一处理。校验通过后还有一个细节对参数做归一化。比如城市名去空格、日期转成统一格式、数字转成float或int。不要直接拿原始参数去查询否则你会遇到“Chrome 杭州”这种带尾巴的脏数据。我在实际项目中就吃过亏有个城市名参数带了用户浏览器插件拼接的字符串查询接口直接报错后来加了严格的schema校验和正则才解决。2.3 错误处理与超时设计技能调用不可能每次都成功网络超时、返回空数据、权限不足都可能发生。我的经验是给技能定义三类错误可重试错误比如超时、5xx、不可重试错误比如参数校验失败、认证失败、自定义业务错误比如数据源返回空数据。分类后Agent才能在第一时间决定是重试、换技能还是直接告诉用户失败原因。超时是最常见的坑。外部接口没有超时控制技能就可能挂起连带着整个Agent卡住。我的做法是为每个技能设置两个超时值内部超时和外部超时。内部超时指从Agent开始调用计算到技能返回的时间上限外部超时指技能内部最耗时的子操作的时间上限。如果外部超时到了技能会返回一个“超时但可能有部分数据”的状态给Agent一个机会做部分结果处理而不是全盘失败。重试策略要根据错误类型分开。可重试错误我用指数退避第一次等0.5秒第二次1秒第三次2秒最多三次。不可重试错误绝不重试直接返回错误原因让模型换思路。这里有个容易忽略的点模型可能因为收到错误信息而反复调用同一个技能形成“调用死循环”。我设计了一个调用计数同一技能在一个任务里最多调用5次超过就强制切换到别的方式避免无限递归。3. 实操过程从零搭一个技能库3.1 目录结构与存放约定一个可维护的技能库文件组织要有约定。我目前用的是这个结构skills/ ├── weather/ │ ├── skill.py │ ├── schema.json │ └── prompt.md ├── calendar/ │ ├── skill.py │ ├── schema.json │ └── prompt.md └── common/ └── utils.py每个技能一个文件夹skill.py包含技能逻辑入口schema.json包含技能描述和参数声明prompt.md存放技能详细说明和给模型看的调用示例。common/存放跨技能共用的工具函数比如时间解析、日志上报、HTTP请求封装。我之所以把描述拆到独立JSON里是为了让“技能元数据”和“技能实现”分离。这样有几个好处一是我可以写一个通用加载器遍历所有文件夹读取schema自动生成系统提示词中技能清单二是不需要import每个技能就能提前校验描述有没有语法错误三是有新同事加入时不用读源码看schema就能知道这个技能能干什么。3.2 注册与发现机制技能库要有“注册”和“发现”机制。我实现了一个最简易的注册装饰器用来把函数挂到全局技能表上SKILL_REGISTRY {} def skill(name, schema_path): def decorator(func): with open(schema_path, r, encodingutf-8) as f: schema json.load(f) SKILL_REGISTRY[name] { func: func, schema: schema, } return func return decorator skill(check_weather, skills/weather/schema.json) def check_weather(city: str, date: str) - dict: # 实际查询逻辑 return {city: city, date: date, temp_max: 30.0}这样每次新增技能只需要两步写一个schema文件在函数头上加装饰器。不需要手动注册到任何列表运行时会自动收集。发现机制我做了两层第一层是做一次“预筛选”根据用户请求的关键词和技能标签做粗糙匹配筛掉明显无关的技能。比如用户问“今天杭州天气”只有weather标签的技能才会进入候选列表。第二层是交给模型决策把候选技能的完整描述塞进系统提示词由模型决定调用哪个。这样既保留了模型的灵活性又减少了模型在几百个技能里“大海捞针”的概率。3.3 让模型学会调用技能系统提示词里的技能清单系统提示词不是把JSON schema往里面一扔就行模型很可能看不懂一堆嵌套结构。我会把每个技能描述压缩成一行“技能说明书”放在提示词里可用技能 - 查天气检查指定城市未来几天的天气情况。参数城市名、日期可选。使用场景用户询问天气、气温、降水。 - 设提醒创建一个时间提醒。参数提醒内容、日期时间。使用场景用户让你“记得”某件事。 ...这份说明书不能太长我控制在每个技能一两句话最多不超过两百个技能。如果技能超过这个量就必须依赖预筛选来缩小候选范围。让模型学会调用的另一个技巧是给少量示例。我在每个schema的prompt.md里写了三个示例对话展示什么时候调用、参数怎么填、得到结果后怎么答复用户。示例不追求多但一定要覆盖“边界情况”比如“查询城市不存在时怎么回复”“结果为空时怎么委婉说明”。我见过很多Agent连最简单的示例都没给模型全靠猜效果自然不稳定。调用完技能后返回结果要有一个统一包装。我规定技能必须返回一个字典至少包含statussuccess或error、data结构化数据或错误信息、raw原始数据用于排查。模型读到status后就知道要不要继续下一步不用再自己猜测。这里还需要注意技能返回给模型的内容也要做“压缩”太长的原始数据会占用上下文窗口我用一个summarize函数先取关键字段。4. 常见问题与排查技巧实录4.1 技能召不准怎么办这是所有Agent开发者的头号痛点。你明明有很多技能模型就是不调用或者调用了错误的那个。我遇到过的典型案例是用户说“帮我查一下杭州明天的最高气温”模型却没有选天气技能。排查下来发现是语言表达差异——用户用了“气温”而技能描述里只写了“天气”和“温度”模型匹配不上。解决办法有三个第一扩充description中的同义词和典型问题表述把“气温”“冷不冷”“热不热”“穿什么”都写进去。第二给技能加更多示例让模型熟悉表达方式。第三改造预筛选逻辑用更全面的关键词表做粗匹配甚至可以直接用文本embedding算相似度再把最接近的几个技能送给模型。第三种效果最好但会增加一些算力开销我在处理几千个技能时才用。还要注意技能的竞争问题。比如你有“查天气”和“查空气质量”两个技能它们高度相关模型经常选错。我的处理方式是在描述里显式写明“本技能不包含空气质量数据如需空气质量请调用另一个技能”再提供一个组合技能“查询出行建议”把两者都包装进去。这样模型更容易选到正确目标。4.2 参数反复出错模型被“逼疯”我见过一个很搞笑的日志模型调用某技能失败了八次每次都传相同的错误参数因为技能返回的错误信息只是“参数错误”模型不知道自己错在哪。最后Agent抛异常用户只好手动操作。这个问题的根源是错误信息太模糊。我在错误信息里加入了“正确参数格式示例”比如错误参数city不符合要求。正确示例{city: 杭州, date: 2025-03-01}模型一看到正确示例马上就会修正自己的输出。还有一个办法是“自动纠偏”在技能层直接处理常见偏差比如把“后天”自动解析成具体日期把“北京、上海”自动分割成两个城市名。纠偏逻辑值得多写因为它能让Agent少犯几次蠢错。参数校验失败时我还建议做一个“失败回传”机制把失败原因和当前参数一起回传给模型让模型判断是自己表达错了还是用户需求变了。这个机制能显著减少机械重复。4.3 技能多了之后调用速度慢技能库从几十个膨胀到几百个后系统提示词被塞得越来越长每次请求的token消耗也跟着涨响应时间越来越慢。最开始的方法是直接缩减技能清单但效果不理想因为有些技能确实会被用到只是频率低。我的最终方案分三步第一按标签分组比如工具类、数据类、系统类先用规则找到用户意图可能涉及的组忽略其他组。第二组内技能按“最近使用频率”排序高频技能优先出现在提示词里。第三对低频技能不在提示词里完整展示而是只提名字等模型表示需要时再动态加载。这样既保住了长尾能力又把常规请求的上下文压缩了三分之二。还有一个小技巧对组合技能做好“预编译”。有些组合技能其实可以一次性完成不需要模型一步步拆解。比如“天气播报技能”内部把查天气、格式化播报文案、语音合成串在一起模型只要调一次就好。组合技能执行时间也远低于模型多次调用。4.4 并发与资源冲突Agent技能不只是在单用户对话里用我这个项目里还同时跑了好几个并发任务技能之间会互相踩踏。最常见的坑是技能内部用了共享变量一个任务改了状态另一个任务读到脏数据。排查这类问题特别头痛出问题的任务不一样复现还随缘。解决方法是给技能增加“运行上下文”概念。每次调用技能都显式传递一个context_id技能内部所有临时数据和状态都装在以context_id为键的字典里任务结束后自动清理。另外对会修改外部资源的技能比如写入数据库、修改文件我用一个全局锁和信号量来控制并发上限避免多个Agent同时写同一个资源。如果技能需要用外部API还要考虑API的限流策略。我在技能封装里加了一个简单的令牌桶每个API的调用频率被限制在每秒N次超出后返回“请稍后再试”而不是直接崩溃。这个设计让我在流量高峰时保住了一个关键接口没有引发连环故障。最后分享一个小经验别等到技能出问题再补设计一开始就为每个技能写好schema、错误码和示例。我最初图省事几个技能直接写死函数后来为了加一个字段改了整整三个文件还带出一个隐藏bug。Agent的技能体系跟代码库一样前期的结构质量决定了中后期的维护体验。如果你现在正在做Agent相关项目不妨先把你能想到的所有“动作”列一张清单给它们命名、写描述、划边界这就是你agent-skills的第一版草稿。有了这个草稿后面的路会顺很多。

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

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

免费获取报价 →
↑