1. agent-skills是什么,为什么值得关注
这些年做AI应用,我最大的感受是:模型本身的能力天花板已经很高,真正拉开差距的是“模型能不能把活儿干完”。你可以让GPT-4写出漂亮的代码,但它不会自己去拉取仓库、运行测试、部署服务;你也能让Claude写一份完美的周报,但它没法自动读取你的飞书日程、翻聊天记录、把数据汇总成表格。这中间的断层,就是agent-skills要解决的问题。
简单说,agent-skills是一种“把外部能力打包给大模型调用”的工程化方案。它不是一个具体的模型,也不是某个厂商的专属产品,而是一套关于“如何组织、描述、调用工具”的规范和实践。核心思路是:把真实世界里的操作——查询数据库、调用API、读写文件、执行脚本、控制浏览器——封装成一个个独立的技能模块,再用自然语言描述和格式化的参数定义告诉Agent:“你有这个能力可用,当用户需求匹配时,你就调用它。”
这套思路解决的问题非常直接:让大模型从一个“只会说话的顾问”变成“能动手的实习生”。它把模型的理解能力与外部工具的执行能力连接起来,让Agent真正完成端到端的任务。当前主流的Agent框架——Claude Skills、OpenAI Function Calling、LangChain工具链、各类国产Agent平台——底层几乎都在做同样一件事:定义技能、管理技能、按需调用技能。
这篇文章适合谁看?如果你正在做Agent类应用开发,或者准备用大模型自动化处理日常工作流,又或者想搞清楚那些“AI替我干活”的产品到底是怎么实现的,那么agent-skills这套东西值得你花一个下午搞明白。后面我会从原理、编写规范、实操步骤到踩坑记录,完整拆一遍。
1.1 从混乱的Tool说起:Skill到底解决了什么
早期做大模型工具调用,大家最常见的做法是“写一堆Function,塞给模型”。模型根据用户的话,输出一个结构化的JSON,系统解析后执行对应函数。这套方案看起来简单直接,实际一跑就出问题。
第一个问题是描述质量的参差。函数描述写得好不好,直接决定模型会不会在关键时刻调用它。很多人写“GetWeather(city)”,描述只有一句“获取天气”,模型面对“今天出门要不要带伞”这种真实需求时,很难把这句话映射过去。你需要写清楚这个函数负责什么、何时使用、参数格式、返回值结构,甚至要不要带示例。这些内容拼起来之后,那个函数的“描述体量”已经不亚于一份小型文档。
第二个问题是上下文的连带膨胀。每塞一个工具进去,它的名称、描述、参数Schema都要占用上下文窗口。当你注册了20个工具,光工具定义就吃掉几千token,留给实际对话和推理的空间被压缩得很难受。更麻烦的是,模型面向太多并列工具时会出现“选择困难”,被无关工具的字段干扰,导致调用错误。
第三个问题是技能之间的组合与复用。很多时候,一个任务不是一次函数调用能完成的。比如“整理今天的会议纪要并发送给参会人”,至少需要读日历、拉会议记录、调用邮件接口三个动作。如果所有逻辑都堆在代码里硬编码,每加一个新流程就要改一遍主程序;如果全交给模型自由发挥,模型又经常搞错参数。这种“多步、可复用、带状态”的操作,用平铺的函数列表表达非常吃力。
agent-skills的思路是把“工具”进一步抽象为“技能”。一个Skill不仅包含“调用哪个接口”,还包括:这个技能何时适用、前置条件是什么、操作步骤怎么写、失败后怎么补救、有哪些注意事项。模型拿到的不再是一堆孤立函数签名,而是一份“带操作手册的工具箱”。模型先判断“这个任务需要哪几样工具”,再按顺序执行,每一步都有说明书可查,成功率自然高很多。
1.2 为什么是现在:Agent应用从“演示”走向“生产”的必然
前两年大家聊Agent,更多是炫技:“看,模型自己调API完成了任务”。现在聊Agent,问的是“能不能稳定的每天跑1000次,出错率控制在几个点以内”。这种从演示到生产的心态转变,把技能工程推到了台前。
生产环境里的Agent必须回答三个问题:怎么保证调用的准确性?怎么保证流程的稳定性?怎么让非技术人员也能参与维护?这三个问题恰好是Skills擅长解决的。
保证准确性靠的是高质量描述和结构化定义,模型在决策时有据可依;保证稳定性靠的是把复杂任务拆成子技能,每个子技能独立测试、独立迭代,不会因为改了一个需求就牵动全局;让非技术人员参与维护靠的是把技能文件化——一个技能就是一组文档加脚本,运营同事也能通过修改描述文本来调整模型的行为。
所以,我现在看一个Agent项目,从来不先问“你用的什么模型”,而是先看它的技能目录长什么样。技能设计得好不好,基本决定了这个Agent的上限。这也是为什么我建议每一个做Agent开发的人,都系统地把agent-skills这套方法论过一遍。
2. 技能的核心构成与编写规范
一个Skill如果不考虑具体平台差异,抽象来看由四个部分组成:技能声明(元信息)、说明文档(Instruction)、资源文件(Resources)、参数定义(Schema)。
技能声明解决“模型什么时候该想起这个技能”。说明文档解决“模型调用这个技能后该怎么操作”。资源文件是技能真正要执行的脚本、模板、配置文件。参数定义决定模型需要提供哪些信息才能正确执行。
四者缺一不可。很多早期工具调用失败,问题都出在“只有资源文件和参数定义,没有说明文档”,模型拿到一个函数签名,但不知道前置条件和操作顺序;也有项目“声明写得天花乱坠,资源文件一团糟”,模型被误导,调用之后拿不到结果。下面逐个拆开说。
2.1 技能声明:决定模型“何时想起”这个技能
技能声明的核心是名称和描述(Name & Description)。不要小看这两行信息,它们在模型决策链路里起到的作用是“筛选触发器”。模型每收到一条用户消息,都会扫描一遍所有可用技能的描述,判断哪个匹配度最高。如果描述写得像百科词条,或者像广告文案,模型很难把握到底什么时候该用。
我踩过一个典型坑:给一个“生成SQL查询”的技能写描述时,用了“该技能可以处理各种复杂的数据分析请求,支持多表联合查询、子查询、聚合函数等高级操作”,看起来没什么问题。实际使用中发现,模型面对任何带“数据”“分析”“报表”字眼的请求都会试图触发这个技能,哪怕用户只是想闲聊两句数据趋势。原因就是描述里“数据”“分析”这类高频词太多,语义边界不清晰。
后来我把描述改成:“仅当用户明确要求从数据库提取信息、且需要生成可执行SQL语句时使用。涉及数据库schema中的表名、字段。若用户未指定具体表名,请先通过其他技能获取表结构。”加了“仅当”“明确要求”“先获取表结构”这类限定短语后,误触发率下降了一半以上。
写技能声明的几条经验,我简单总结在下面:
- 用“当…时”句式描述触发条件,不要用“可以用于”“支持”这类宽泛动词。
- 明确排除项:如果某些场景不该用这个技能,直接写进描述里,模型会把“负向约束”当重要参考。
- 描述执行前提:比如“在调用此技能前,必须先确认用户具备相应权限”,模型会把这句话当作流程上下文。
- 控制描述长度:不是越长越好。把触发条件说清楚就够了,操作细节放进Instruction里。本质上,描述是给模型做“要不要调用”决策的,操作流程是调用后“怎么做”的,两者职责不同,混在一起会让决策变慢。
2.2 说明文档(Instruction):技能执行的“操作手册”
如果说技能声明决定模型“什么时候想起这个技能”,那么Instruction决定的就是“想起之后怎么不出错”。它是一份给大模型看的操作说明,内容可以包括执行步骤、调用顺序、边界条件、返回值处理方式,以及常见的失败原因和兜底策略。
这里有一个很多资料不会细说的关键点:Instruction不仅是写给模型看的“使用说明”,更是你调试Agent行为的“抓手”。当某个Agent总是以错误的方式使用技能时,最优解通常不是改代码,而是改Instruction。比如你发现Agent调用“发送邮件”技能时,经常漏掉抄送字段,那就应该在Instruction里写一句:“发送邮件前,检查收件人列表是否完整,如有抄送需求务必填入抄送字段。若收件人为空,应中止操作并向用户确认。”模型看到这句话之后,就能在很大程度上纠正行为。
Instruction的编写有几个值得注意的原则。第一条是步骤化,大模型对方言的驾驭能力比我们想象中强,但对无序信息的处理能力其实一般,如果你像写散文一样展开说明,出错的概率会明显增加。把一个操作拆成1、2、3、4,模型按顺序执行的稳定性会好很多。第二条是给示例,尤其是参数格式容易出错的地方,给一个“输入示例”和“输出示例”,模型会模仿这个格式,比任何解释都管用。第三条是写失败兜底,明确“如果接口返回错误码X,请重试一次;若仍然失败,告知用户稍后再试”,模型会照着做,而不是自己编一个错误信息。
2.3 资源文件与参数定义:技能的实际执行体
资源文件是技能真正跑起来的底层支撑。它可以是一个Python脚本、一段Shell命令、一个SQL模板,甚至是一个Node.js服务。文件组织方式通常有两种:一种是把脚本放在技能目录下,由Agent框架在执行时拉起;另一种是注册为一个HTTP接口,由Agent通过REST调用。前者更适合轻量级、单机场景,后者更适合已有后端服务、需要统一鉴权和负载管理的场景。
参数定义(Parameter Schema)直接决定了模型能不能把自然语言转成一次合法调用,因此需要格外注意。给参数定义的命名要尽量贴近领域惯用词,这样模型才能从对话里准确提取。给每个参数都写清楚用途和格式,避免模型传入非法值。设置好必填项,可选项如果非要设置默认值,请确保这个默认值不会导致灾难性后果(我见过有人把“输出路径”默认值设为根目录,导致脚本把临时文件写到了系统目录下)。
举一个实际例子。一个天气查询技能的参数Schema可以这样设计:
- city:城市名,字符串,必填,如“北京、上海”
- date:日期,字符串,可选,默认今天,格式YYYY-MM-DD
- unit:温度单位,枚举(celsius/fahrenheit),可选,默认celsius
如果城市名是模型从“帮我查一下深圳明天的天气”里提取,它应该能准确填入city=深圳、date=明天对应的日期。但如果你的参数名叫location_description,模型还要琢磨“描述”到底是指城市名还是详细地址,就容易出偏差。参数命名的本质,是引导模型做信息抽取的锚点,务必清晰。
2.4 元数据命名与库的目录结构设计
当技能数量超过十个以后,命名规范和目录结构就会成为你可维护性的关键。我的习惯是给每个技能建立独立文件夹,文件夹名用连字符风格,例如generate-sql-query、send-email-notification、parse-resume-pdf。文件夹内统一使用skill.md(声明与说明)和resources/(脚本与资源)的结构。每次新增技能,都按照同一套模板生成初始文件,再逐步填充细节。
可别小看这套统一结构带来的收益。你可以写一个简单的脚本,自动扫描所有技能目录,校验描述是否包含触发条件、参数定义是否完整、说明文档是否超过多少字,把不合规的技能自动标记出来。团队协作时,新人也能按模板快速上手写技能,不需要从零摸索。
3. 实操:从零搭建一个agent-skills项目
讲完理论,我们来动手做一遍。我会以一套通用的实现作为示例,让你不只学会“跟着做”,更能理解每一步背后的选型逻辑。整套流程不需要昂贵硬件,一台普通开发机、一个Python环境(或Node环境)、一个LLM API Key就能跑起来。
3.1 环境准备与整体架构
我选择的技术栈是:Python作为主要语言,LangChain承担Agent框架的职责,底层LLM用兼容OpenAI接口的服务(比如DeepSeek或其他国内模型,实测成本和效果都比较平衡)。之所以选LangChain而不是自己手写框架,是因为它已经做好了工具注册、模型对话、循环执行这些基础能力,我可以专注在技能模块的编写上。
如果你用Claude的Agent Skills生态,流程会更简单一些,Claude官方提供了skill的目录约定,可以直接被Claude Code或Claude App加载。但这里我用LangChain举例,因为它的通用性最强,你在国内云服务或开源模型上都能复刻。
目录结构这样规划:
agent-skills-demo/ ├── skills/ │ ├── get-date-time/ │ │ ├── skill.md │ │ └── resources/ │ │ └── get_datetime.py │ └── fetch-weather/ │ ├── skill.md │ └── resources/ │ └── fetch_weather.py ├── agent.py └── requirements.txt先安装依赖:
pip install langchain langchain-openai python-dotenv如果你使用的模型需要额外的SDK,比如阿里云百炼、智谱AI等,记得按对应文档补充安装。
3.2 编写第一个技能:获取日期时间
我们从最简单的“获取当前日期时间”开始。虽然模型本身知道当前时间,但在真实Agent应用中,时间往往需要从系统获取,才能保证准确,同时这也是一个最容易上手、检查全链路是否走通的技能。
先写skills/get-date-time/skill.md:
# Get DateTime ## 描述 当用户询问当前日期、时间、星期几时使用此技能。 此技能从系统获取实时时间,支持返回完整时间、仅日期或仅时间。 如果用户未指定时区,默认使用东八区时间。 ## 操作步骤 1. 检查用户是否指定了格式偏好。 2. 调用 resources/get_datetime.py,传入参数 format。 3. 将返回结果直接回复给用户。 ## 参数说明 - format: 字符串,枚举值 full/date/time,必填,默认 full - full 返回“YYYY-MM-DD HH:MM:SS 星期X” - date 返回“YYYY-MM-DD” - time 返回“HH:MM:SS” ## 示例 用户:现在几点? AI调用:python get_datetime.py --format time 返回:14:30:25 AI回答:现在是下午2点30分。注意这个做法:我把“参数说明”也放进skill.md,而不是单独放到Schema文件里。这样做的好处是,模型在阅读说明文档时能同时看到参数含义,调用时会更加准确。LangChain虽然也能在functions参数里定义Schema,但让模型“先看文档再填参数”,比让它“只看一个JSON Schema”更稳。
接着写resources/get_datetime.py:
import argparse import datetime def parse_args(): parser = argparse.ArgumentParser() parser.add_argument("--format", type=str, choices=["full", "date", "time"], default="full") return parser.parse_args() def main(): args = parse_args() now = datetime.datetime.now() if args.format == "full": weekdays = ["星期一", "星期二", "星期三", "星期四", "星期五", "星期六", "星期日"] output = f"{now.strftime('%Y-%m-%d %H:%M:%S')} {weekdays[now.weekday()]}" elif args.format == "date": output = now.strftime("%Y-%m-%d") else: output = now.strftime("%H:%M:%S") print(output) if __name__ == "__main__": main()写脚本时有个容易被忽略的细节:脚本的打印输出要让模型“一看就懂”。模型拿到这个字符串后,会直接用于组装发给用户的回答。所以不要输出“当前时间:... 星期...”这样的中文掺杂格式,模型还要去解析,增加出错概率。直接给一个干净可读的字符串,模型能更好地组织语言。
3.3 编写第二个技能:天气查询
天气查询比日期复杂一点,涉及外部API。我以公开的天气接口为例(这里不用具体商业API,用Open-Meteo这类免费接口演示,无需密钥)。技能描述要写清楚“什么时候用”和“什么时候不用”。
# Fetch Weather ## 描述 当用户询问某地当前天气、未来天气预报、气温、降水概率等信息时使用此技能。 仅当用户明确提供了城市名称时使用。如果用户没有说明城市,必须先通过询问获取城市后,再调用本技能。 ## 操作步骤 1. 将用户提供的城市名称转换为经纬度坐标。 2. 调用 resources/fetch_weather.py,传入 latitude、longitude。 3. 将返回值中的天气状况、最高温度、最低温度整理后回复用户。 ## 参数说明 - latitude: 浮点数,必填,目标城市纬度 - longitude: 浮点数,必填,目标城市经度 ## 示例 用户:上海明天会下雨吗? AI内部调用:python fetch_weather.py --latitude 31.23 --longitude 121.47 返回:多云,最高26度,最低19度,降水概率20% AI回答:上海明天多云,降水概率不高,最高26度左右。这里我特意在描述里加了一句“如果用户没有说明城市,必须先询问”——这是处理信息缺失的标准做法。很多Agent在用户没说城市时,会自作主张用“北京”或“上海”去猜,结果答非所问。在技能描述里直接规定行为边界,是最省力的修正手段。
fetch_weather.py的核心逻辑是:根据经纬度调用Open-Meteo的API,解析返回的JSON,输出一段人类可读的文本。这一步没有太多玄机,唯一值得强调的还是输出格式:不要输出原始JSON,模型虽然能读JSON,但直接给一小段自然语言,可以减少一次“JSON解析→自然语言转换”的推理负担。反正我们追求的是稳定而不是炫技,简化模型工作就是提高可用性。
3.4 在Agent中注册和使用技能
技能文件写好了,现在要把它们加载到Agent里。LangChain里最直接的做法,就是把每个技能封装成一个Tool,用StructTool来定义参数。用Python的subprocess调用脚本,这样技能与主程序完全解耦,后续新增技能时完全不动主程序逻辑。
import subprocess import json from langchain_core.tools import StructuredTool from langchain.agents import create_tool_calling_agent, AgentExecutor from langchain_openai import ChatOpenAI def run_datetime(format: str = "full") -> str: """获取当前日期时间,format可选full/date/time""" result = subprocess.run( ["python", "skills/get-date-time/resources/get_datetime.py", "--format", format], capture_output=True, text=True, check=True ) return result.stdout.strip() def run_weather(latitude: float, longitude: float) -> str: """获取指定经纬度的天气信息""" result = subprocess.run( ["python", "skills/fetch-weather/resources/fetch_weather.py", "--latitude", str(latitude), "--longitude", str(longitude)], capture_output=True, text=True, check=True ) return result.stdout.strip() datetime_tool = StructuredTool.from_function( func=run_datetime, name="get_datetime", description="获取当前日期时间,当用户询问现在几点、今天几号时使用。" ) weather_tool = StructuredTool.from_function( func=run_weather, name="fetch_weather", description="获取天气信息,当用户询问某地天气时使用。调用前需要经纬度。", args_schema={...} # 按需定义Pydantic模型 )然后把两个工具注册给Agent,再配置Prompt模板。Prompt模板里有一句话我会加上:“你有且仅有一下工具可用……”,目的是抑制模型“编造工具”。模型有时会幻想出一个“发送邮件工具”并直接返回假结果,明确告诉它“只有以下工具”,可以把这个幻觉概率压到很低。
观察到这两个脚本被调用时,我建议你先在终端跑一遍脚本确认输出正常,再交给模型。因为模型出了错还容易排查,脚本自己出错可就无从下手了。我的习惯是“脚本先行,Agent随后”——所有技能脚本先单独测通,再接Agent。
3.5 完整跑通一次Demo
启动agent.py后,试着输入“现在几点了”“北京明天天气怎么样”“帮我查一下杭州后天会不会下雨”。如果一切正常,你应该看到模型先调用get_datetime或fetch_weather,拿到脚本输出后,整理成自然语言回复用户。这一步跑通,你的agent-skills最小闭环就建成了。
跑通过程中如果遇到问题,不要让模型“猜”为什么会失败,直接打日志。我会在AgentExecutor里开启verbose=True,观察模型每一步的Thought和Action。这样能直接看到模型是如何决策的——它有没有正确理解工具描述、参数填得对不对、卡在哪一步。日志是排查Agent问题最有力的工具,没有之一。
4. 常见问题与排查技巧实录
我做的Agent项目越多,越觉得“技能工程”是一个靠踩坑积累经验的领域。很多坑不实际跑一遍根本预料不到。下面这些问题,是我在多个项目里反复遇到的,按问题场景分好类,方便你对照排查。
4.1 技能命中率低,模型“想不起来”用
这是最典型的痛点:技能明明写好了,模型就是不用,甚至用户需求已经非常匹配,模型还是自顾自地回答。排查这个问题的优先级次序是:先看描述有没有歧义,再看模型是否真的看到了这个技能,最后看是不是技能太多导致注意力分散。
描述歧义是最好解决的问题。把技能描述重写一遍,用“当…时”句式,加排除项,加触发场景示例。我在前面已经给过例子,就不再重复。要看模型是否看到了技能,有一个很实用的手段:故意在描述里写一句“如果用户说‘芝麻开门’,请调用本技能并回复‘你好’”,然后测试。如果模型照做了,说明技能加载没问题;如果没做,那问题出在“可见性”上——技能根本没有注册成功,或者被上层指令屏蔽了。
技能太多导致的“选择困难”也是常客。我有一次给一个客服机器人挂了30个技能,结果核心技能命中率反而下降。模型面对30个候选,每一个描述都像,决策力被信息量稀释。解法是做技能分组:高频技能常驻,低频技能“懒加载”——通过一个前置路由技能决定是否加载后续子技能。相当于给Agent做了一个两级目录,先定大类,再选细项,命中率明显回升。
4.2 参数传递错误与上下文膨胀
参数错误大多出在“模型不知道这个参数怎么填”。比如经纬度这种计算型参数,模型不可能自己算出来。所以要么让技能描述里写明“调用前先执行geo编码技能”,要么把经纬度解析直接内置在脚本里,让脚本接收城市名、内部完成转换。尽量把复杂度放到脚本层,不要让模型做它不擅长的事。
上下文膨胀是另一个隐蔽问题:每调用一次技能,工具响应内容都会留在对话历史里,多个轮次之后,响应内容越积越多。一个天气技能每次返回200字,10次调用后就是2000字的噪声,挤占上下文空间,而且这个过程是逐步发生的,你不会立刻察觉。
我的做法是给Agent的记忆机制做裁剪:只保留“工具调用的最终结果摘要”,丢弃完整的工具响应原文。例如天气调用结束后,不要把整个多云转阴的细节放进长时记忆,只记一句“7月20日杭州天气:多云”。这样既保住了完成任务所需的事实,又控制了上下文长度。
4.3 技能脚本异常与失败重试
脚本报错是最不挑模型能力、但最容易让人崩溃的环节。有一次我写的数据解析技能,在一个特殊边界数据上抛出了ValueError。脚本异常后,Agent框架默认把错误信息直接交给模型,模型有时候会把错误当成正常输出,回复用户“解析失败,但你不用管”。这个回答不专业,但更可怕的是模型可能会为了“完成任务”而虚构一个结果。
为此我设计了一套统一的重试协议:所有技能脚本捕获异常后,输出以“ERROR:”开头的标准错误码和简要原因。Agent框架侧检测到“ERROR:”前缀后进入重试逻辑——若允许重试,就以“上一步执行出错,请重新调用或更换参数”作为新的系统提示;若已重试过,就明确要求模型向用户说明“执行失败”,绝不允许模型自行编造结果。这个协议简单有效,强烈建议抄进你的项目。
4.4 技能冲突与版本管理
当技能的触发条件写得太宽泛时,两个技能会“抢活”。比如一个是“查询订单状态”技能,另一个是“查询物流信息”技能,用户说“我的快递到哪了”,两个技能都可能被命中。解决方式是在描述中明确职责边界:物流信息技能写明“仅当用户询问快递物流轨迹时使用”,订单状态技能写明“仅当用户询问订单整体流转状态(待付款/已发货/已完成)时使用”。如果还冲突,就改在前置路由里做硬性条件匹配,而不是让模型自己拿主意。
版本管理也是很多新团队容易忽视的。你的技能文件一旦开始被多个Agent共用,改一个技能描述可能影响所有调用它的Agent。我现在的做法是技能文件进入Git仓库,每次修改都要过PR评审。技能描述不仅要写“是什么”,还要在文档末尾写“变更记录”——什么时候改的、改了什么、为什么改。这样出了bug,能快速定位是哪次修改引入的。
5. 工具选型与生态现状
为了让你对不同方案有个全局认识,我把当前主流的agent-skills实现方式和它们各自的适用场景理了一下。这不是一个穷举清单,但足够覆盖市面90%以上的项目。
| 方案 | 核心机制 | 优点 | 局限 |
|---|---|---|---|
| Claude Agent Skills | 在Claude Code/Claude App中以目录Skills加载 | 编写门槛低,官方生态完善,模型擅长遵循指令 | 仅限Claude生态,迁出成本较高 |
| OpenAI Function Calling | 通过结构化JSON Schema注册函数 | 通用性强,模型调用稳定 | 技能描述容易膨胀,上下文占用高 |
| LangChain/LlamaIndex工具链 | 将技能封装为Tool,注册到Agent | 框架成熟,兼容多种模型 | 封装层级多,调试有一定门槛 |
| 自研技能编排系统 | 自己定义技能格式与调度逻辑 | 自由度最高,可深度定制 | 工程成本大,非必要不推荐 |
选型的时候不要只看“效果”,更要看“团队维护能力”。我见过很多团队一上来就自研技能系统,最后被技能解析、调度、回退、日志这些琐碎问题拖垮;也有团队闭眼用LangChain,出了问题在多层抽象里debug到怀疑人生。我的建议是:小项目用Claude Skills,想要模型无关就用LangChain,团队技术能力强且需求高度定制,才考虑自研。
有一件事要提醒:不要过度依赖单一模型的Function Calling“开箱即用”。不同模型对Function Calling的遵循程度差异很大,同一个技能定义,在GPT-4o上表现完美,换到开源模型可能就频繁漏参数。兼容多模型时,你需要准备一套“参数自校验”机制:模型返回的调用结果先做一遍Schema校验,不过就自动提示模型修正。这个校验机制成本不高,但能让你的Agent在不同模型间平滑迁移。
6. 我个人实际用过之后的一些体会
最后聊几句我自己的感受,这些心得不在任何官方文档里,是跑了无数个版本之后沉淀下来的。
第一个体会是:技能工程是“非对称投入”——前期写描述、做校验、设计目录结构,可能要花费大量时间,但后期收益极其稳定。技能写得好,Agent在生产和可靠性上的表现会远远超出“模型很强”带来的上限。很多项目到了后期竞争力其实不是模型,而是那套工具技能体系。
第二个体会是:一定要给技能做测试。我现在所有技能都配套一个测试用例集,每个测试用例是一条用户消息和对应的“期望动作序列”。每次修改技能描述后,跑一遍全套测试,既能防止回归,又能确保技能库的健康发展。这跟写单测一样,逼自己做,但做了就知道有多值。
第三个体会是:技能描述是一门“措辞的艺术”。同一个意思,“获取天气信息”和“仅当用户明确询问某个具体地点未来24小时天气情况时使用”触发效果完全不同。写技能的时候,尽量用一个对产品需求非常熟悉、习惯咬文嚼字的人来撰写和修改描述。这个投入,比我换一个更大的模型带来的收益都明显。
如果你打算在自己项目里落地agent-skills,我的建议是:不要一次性规划几十个技能,先挑两个最高频、最痛的需求(比如查天气、查订单、发消息),把全链路跑通,包括技能编写、参数校验、执行、异常处理。跑顺之后,再逐步扩展。技能库这东西,不是规划出来的,是一步一步长出来的。
那天我把这套东西分享给团队里新来的同事,他问:“这不就是给模型写说明书吗?”我说:“对,也不对。说明书是给人看的,技能是给模型看的,但要写得好,你需要同时懂人的需求、模型的脾气,还有真实世界的运行逻辑。三个领域缺一个,技能就废了。”
做agent-skills这件事越久,越觉得它不像纯技术,更像是一座连接语言世界和现实世界的桥。桥搭好了,模型说什么,世界就做什么;桥没搭好,模型说得再漂亮,世界也纹丝不动。而搭桥的手艺,就在每一个技能的描述、每一步脚本的编排、每一次错误的修复里。