☰
从Function Call到Skills:Agent能力的组织方式与工程实践
2026/10/28 4:11:07 网站建设 项目流程

1. 从Function Call到Skills:Agent能力的组织方式正在变

过去大半年我一直在折腾各类Agent项目,最头疼的事情不是模型能力不够,而是每次搭一个Agent都要把相似的能力重新写一遍。早期大家习惯把能力封装成一个个Function,也就是函数调用,后来又有MCP这类协议来统一工具接入,但这些方案解决的都是"让Agent能用某个工具"的问题,并没有解决"让Agent知道在什么场景下、按什么步骤、用什么参数去完成一整件事"的问题。换句话说,Function Call给Agent的是零散的动作,而实际业务里我们需要的是一套完整的流程。

这也是为什么我特别关注"agent-skills"这个方向。Skills,简单说就是把完成某类任务所需要的知识、指令、脚本和资源打包成一个自包含的单元,让Agent拿到之后可以直接按里面的说明去执行。它跟单个工具最大的区别在于:工具只负责一个原子操作,而技能负责一整条工作流。比如一个"图片上传前检查"的Skill,里面规定了先看格式,再看分辨率,再校验元数据,最后给出优化建议,这一整套逻辑都封装在一个包里,Agent只要找到这个Skill就能从头到尾跑完。

我自己的体会是,2025年之后的Agent项目,凡是能力模块稍微多一点、需要在多个场景复用的,几乎都在往Skills方向收敛。原因很直接:开发一个Agent容易,但让Agent在不同项目之间共享能力、让团队里不同人写的Agent能够复用彼此的经验,这才是真正的瓶颈。Skills这套组织方式恰好把"能力"从代码里抽离出来,变成一种半结构化的、可读的、可分享的资产。

所以这篇内容我想聊的,不是某个具体框架的API怎么调,而是Skills这套思路本身:它解决什么问题、内部怎么组织、实际动手做的时候有哪些坑、怎么把它用到自己的Agent项目里。适合正在做Agent开发、被重复功能折腾过、或者想让团队的Agent能力沉淀下来的朋友参考。

1.1 我为什么在项目里反复整理"技能"

这个习惯最早是从一次失败的交付开始的。当时接了一个内部知识库问答Agent的项目,前期开发很顺,模型选型、向量库、检索链路都跑通了,结果到了验收阶段,客户提了一堆"小事":上传的文档格式五花八门,有的要转成纯文本,有的要抽表格,有的要处理扫描件;问答结果里引用的文件路径要用固定格式;每周还要自动生成一份使用统计报表。这些功能每一个都不难,但散落在代码里,每个调用点的逻辑都略有差异,改起来相当痛苦。

后来我把这些零散的函数按业务流程重新组织,每个流程配一份说明文档,里面写明触发条件、执行步骤、输入输出格式、注意事项,再把对应脚本放在旁边。我发现当Agent用到某个流程的时候,它读这份说明文档的效率,远高于我让它在几十个函数名里猜哪个是干这个事的。这其实就是Skills的雏形。

之后我去看了市面上一些开源项目对Skills的定义和实现,发现大家核心逻辑极其相似:一个技能目录下放一个人类可读的说明文件加一些可执行文件,Agent在需要时扫描这些目录,读取说明,决定是否调用。这种模式的好处是显而易见的:

  • 技能的边界清晰,一个目录就是一件事;
  • 说明和代码在一起,天然自带文档;
  • 新增一个能力只是往目录里加一个包,不需要改主程序;
  • 分享起来方便,拷贝一个目录就行。

1.2 Skills、Tools、MCP:三个名字背后的边界

很多刚接触这块的朋友会把Skills跟Tools、MCP搞混。我自己也是踩了坑才分清楚的。先说Tools,它是最底层的概念,就是一个函数,输入JSON、输出JSON,Agent决定何时调用。Tools的特点是轻、快、原子化。但问题也在这:复杂任务需要Agent自己串联多个Tools,中间一旦某一步输出格式不符合预期,后面的工具就接不上。

MCP解决的是Tools的接入标准化问题。它定义了一套基于JSON-RPC的通信协议,让工具以服务器的形式暴露出来,Agent通过客户端去发现和调用。MCP的价值在于"一次接入,处处可用",它解决的是连接问题,而不是组织问题。你通过MCP能连上很多工具,但每个工具还是干一件事。

Skills则更上一层,它组织的是"完成一个目标"所需的全套要素。一个Skill里面可以包含多个脚本、多份模板、一份逐步操作的说明,甚至还可以定义在什么情况下不要使用它。可以这么理解:Tools是螺丝刀、扳手、电钻,MCP是标准接口的接口规格,Skills则是一个工具箱上贴的标签——"组装书架用这套工具,按这个顺序用"。

在项目里三者的配合方式是这样的:Skills可以把一些指令逻辑直接写在说明文档里,由Agent按说明执行;需要精确计算或系统操作时,Skill内的脚本可以调用注册好的Tools;Tools如果跨项目复用,再通过MCP暴露。这样各层职责清晰,出了问题也好排查,至少你现在知道到底是工具坏了,还是技能使用方式错了。

2. 一个可运行的Skill包到底装了些什么

理解了Skills是干什么的之后,接下来要解决的就是落地问题。我直接拿一个我自己维护的开源仓库的结构来说明,因为这个目录结构经过了多次迭代,踩过不少坑,算是比较实用的一个模板。

一个标准的Agent Skill包,我的组织方式是:

skill-name/ ├── SKILL.md ├── scripts/ │ ├── run.sh │ └── helper.py ├── assets/ │ ├── templates/ │ └── reference/ └── tests/ └── test_cases.json

看起来很简单,但每个部分的细节都有讲究。先说核心的SKILL.md,这是整个技能的灵魂,Agent能不能正确使用这个技能,90%取决于这份文档写得好不好。它不是给人看的API文档,而是给模型看的一步步操作指南。

2.1 SKILL.md:给Agent看的说明书

SKILL.md一般用Markdown写,头部带一段YAML格式的元信息,用来声明技能的名称和描述。这是Agent判断"当前任务是否需要这个技能"的依据,所以描述一定要写得像搜索引擎的索引词一样精准。

我一开始犯过的错误是描述写得太抽象,比如"上传检查工具",结果Agent在遇到"图片太大发不出去"的时候根本不会联想到它。改成"检查图片、视频文件是否满足上传要求,包括格式、大小、分辨率、元数据,并给出压缩或格式转换建议"之后,命中率明显提升。这段描述就像是商品的关键词,Agent本质上是在做一次文本匹配。

SKILL.md的正交部分,我习惯按这样的结构组织:

  • 概要与适用场景:一句话说明这个技能解决什么问题、什么时候用、什么时候绝对不要用;
  • 执行步骤:清晰的编号步骤,每一步写明输入、动作、产出;
  • 依赖条件:需要哪些外部工具、环境变量、网络权限;
  • 输入输出格式:最好给出示例,模型照着填不容易错;
  • 注意事项:边界条件和常见错误。

2.2 脚本与资源:执行层的取舍

SKILL.md负责"思考",scripts目录负责"动手"。凡是涉及确定性操作、需要精确计算或者要跟系统交互的地方,都应该用脚本实现,而不是让模型自由发挥。举个例子,需要判断一个文件是否超过20MB,这是数学比较,模型即使能算,也不该让它算,脚本一行代码就搞定了,还不会出错。

我见过一些设计Skills的团队,什么逻辑都往SKILL.md里写,让模型一步步推导,结果就是稍微复杂一点的任务,模型在步骤五开始忘记步骤二的约束。脚本的价值恰恰在于把那些不需要"智能"的部分从模型里卸载掉。智能应该用在策略层——决定用哪个技能、按什么顺序执行,至于具体计算、格式转换、文件遍历,统统交给脚本。

assets目录放模板和参考文档,这个在需要固定输出格式的场景特别有用。比如自动生成周报的技能,assets里放一个周报模板,SKILL.md里写"按templates/weekly_report_template.md的结构输出内容",这样既能保证格式统一,又方便业务方直接改模板,不需要动代码。tests目录可能有人觉得小题大做,但对于要长期维护、多人使用的技能包,一组简单的测试用例能在改动时快速发现把功能改坏了的情况,尤其是SKILL.md被改过之后,跑一遍测试就知道描述里的关键词是否还能正确触发。

2.3 元信息设计:命名、描述与依赖声明

元信息设计是被讨论最少、实际影响最大的部分。一个Skill的name不能太通用,不能太生僻,最好遵循"动词_对象_场景"的格式,比如convert_image_to_webp_report、check_upload_file_compliance。这样在Agent扫描技能清单时,从名字上就能大致判断用途。

描述字段要包含任务类型、操作对象、典型场景、排除情况四个要素。我自己的写法是正面描述加负面排除:"检查上传文件是否合规(格式、大小、分辨率、元数据)。适用于图片、视频文件;不适用于文本文件,不适用于本地未同步文件。"这样能大幅减少Agent在无关任务上尝试调用技能的概率。

依赖声明也很关键。如果一个技能依赖Python 3.10以上的版本、ImageMagick或者某个内部API,必须在SKILL.md里写明。我在实际项目中遇到过Agent选了一个需要某种环境的技能,结果运行时报错,原因是环境不满足。后来Skill的依赖信息都会单独写在frontmatter里,由调度层在加载前做一次校验,不满足就直接不让选。

3. 手写一个带状态的上传检查技能:从零到可用

理论说再多不如直接动手。我拿最近在内部项目里做的一个技能举例,一步一步说说怎么从场景分析到最终落地。这个技能解决的问题很常见:业务系统里每天有运营同学上传图片和视频素材,但经常出现格式不对、尺寸过大、包含隐私信息等问题,导致下游系统报错。之前靠人工检查,后来想把这部分交给Agent。

3.1 场景定义:为什么选"上传检查"做示例

选这个场景做示例有几个原因。第一,它是典型的"看起来很简单,做起来很多细节"的任务,非常能体现Skills的价值。第二,它的检查逻辑部分适合写脚本(格式、大小、分辨率的硬性判断),部分适合用模型(比如判断图片内容是否包含敏感信息),正好能展示Skill内部怎么混合编排。第三,这个场景在几乎任何内容型产品里都存在,读者可以往下套。

我梳理的完整流程是这样的:

  1. 接收一个文件路径或URL;
  2. 识别文件类型(图片/视频/其它);
  3. 检查格式是否在允许列表内;
  4. 检查文件大小是否超过上限;
  5. 检查分辨率/时长是否满足要求;
  6. 提取并检查元数据,如GPS是否可能暴露位置信息;
  7. 输出合规性结论与优化建议。

每一步看起来都是独立的小判断,但合在一起,就是一个完整的业务动作。如果没有Skill,用普通的函数调用,Agent需要自己决定先调哪个函数再调哪个,每一步的返回值还得自己记着;有了Skill,Agent只需要按文档步骤走,每一步都调用对应的脚本,状态自然流转。

3.2 SKILL.md怎么写,Agent才能正确触发

这个技能的SKILL.md我是这样设计的,先给个精简版作为参考:

--- name: check_upload_file_compliance description: 检查上传文件是否满足平台要求。适用于图片(jpg/png/webp/gif)和视频(mp4/mov)。检查内容包括格式、大小、分辨率/时长、元数据隐私。不适用于文本文件和音频文件。 dependencies: - "python3 >= 3.9" - "pillow" - "ffprobe" ---

正文部分,执行步骤我强调第5步,因为这是Agent最容易糊弄过去的:

## 执行步骤 1. 确认输入文件存在且可读。 2. 用 file_type_detector.py 识别文件真实格式(不要相信扩展名)。 3. 对照 allowed_formats 检查格式是否合规。 4. 用 file_size_checker.py 检查文件大小。 5. 关键:不要跳过元数据检查。先运行 metadata_extractor.py 提取元数据,如果发现GPS字段,直接按"隐私风险"处理。 6. 汇总结果,按 output_template.md 的格式输出报告。

这中间有个很实际的问题:Agent在步骤执行到一半时,特别是前面几步都通过的情况下,倾向于直接下结论说"文件合规"。所以我在第5步用了"不要跳过"这种强指令措辞,还在最后加了一条检查项:"所有步骤必须完整执行,不得省略元数据检查。"虽然听起来笨拙,但确实有效。

3.3 验证效果:让Agent自己把检查流程跑完

写完SKILL.md和脚本之后,真正的考验是让Agent在真实对话里完成整个检查流程。我在测试时故意准备了一批刁钻文件:伪装成jpg的PNG文件、超过20MB但分辨率很小的图片、带GPS坐标的照片、损坏的视频文件。让Agent拿着这个技能去处理。

第一次测试就暴露了问题:Agent在识别文件类型时直接用了扩展名,把伪装成jpg的PNG文件判成了jpg,没有触发格式转换建议。我在脚本里加了类型探测逻辑,并且把第2步文案从"判断文件类型"改成"用file_type_detector.py识别文件真实格式,不要相信扩展名"。改完之后Agent会实际调用脚本,而不是自作主张。

第二次问题出在输出格式上。我要求输出一份结构化报告,结果Agent给了一段一大段的文字,虽然信息都有,但下游没法解析。后来我在输出模板里定义了明确的字段:

检查结果模板: - 文件:xxx - 真实格式:png - 合规性:不合规(格式伪装) - 建议:转换为jpg/webp后上传

加了模板之后输出的规范性明显提升。这也印证了我之前的观点:能让脚本和模板规定的事情,绝不要指望模型自觉。

4. 技能动态加载与复用:避免"技能爆炸"的工程手段

当技能包数量从几个增长到几十个的时候,新的问题就来了。Agent每次启动时如果要把所有SKILL.md都读一遍,token消耗很可观;技能之间还可能出现描述重复、功能重叠;甚至某两个技能的使用条件互相冲突。这就像手机装了几百个App,全都常驻后台,体验必然下降。

4.1 按需加载:Skills不是All-in-One的插件市场

我目前采用的是两级筛选机制。第一级是"技能索引",维护一个极简的清单,每条只有name和description,总共几千token,Agent启动时只读这个索引。在对话中判断当前任务可能涉及哪个方向,再加载对应的完整SKILL.md。第二级是"关键词预检索",在用户问题进来之后,先对问题做一次关键词提取,用这些关键词过滤技能索引,只把命中的技能清单提供给Agent。

这个机制实现起来不复杂,但对性能改善是实打实的。我之前统计过,一个项目的技能库有20多个技能,全部加载的SKILL.md累计超过3万token,每次对话都带上,成本高且容易让模型"挑花了眼"——它在太多选择面前反而不知道怎么选。改成按需加载后,每次对话最多携带2-3个技能文档,准确率反而上升了。

4.2 权限与安全边界:技能包不能是后门

技能包带来一个安全问题:当一个Agent被授权使用某个技能时,它实际上获得了执行该技能内脚本的能力。如果技能包本身设计得有问题,或者被恶意构造,脚本可能在Agent的环境里执行任意操作。这不是危言耸听,我有一次测试一个从网上下载的"PDF处理"技能包,里面竟然内置了一段向某个地址发送HTTP请求的代码,如果不是在隔离环境里跑,就直接中招。

现在的做法是给技能包建白名单机制。内部技能分三级权限:

  • L1:只允许访问临时目录,适合文件转换类技能;
  • L2:允许访问指定业务目录和环境变量,但不允许网络请求;
  • L3:允许网络请求和特定系统调用,必须经过审批。

每个技能包在注册时声明自己的权限级别,运行时由Agent的调度层强制约束,SKILL.md里写了什么承诺都不算数,以权限配置为准。另外,技能包里的脚本我规定了必须使用相对路径,禁止写死绝对路径,避免在不同环境上跑出预料之外的效果。

4.3 版本管理与离线分发:团队协作的落地细节

技能的维护者往往是业务团队的同学,他们不一定懂代码,但很愿意编写业务规则。所以我做了一个偏"傻瓜化"的版本管理设计:每个技能包目录里放一个VERSION文件,用语义化版本号,变更SKILL.md或者脚本都要升版本,而且变更记录要写在CHANGELOG.md里。分发时打包成tar.gz,附带校验值,确保传输过程中没被篡改。

离线分发是我特别看重的。很多业务环境是内网,不能访问外部的技能仓库。我们的做法是搭一个内部的技能源,本质上就是一个静态文件服务器加一个索引清单,Agent启动时检查本地技能版本,发现新版本就拉取更新。这样做还有个好处,发布新技能不需要改动Agent代码,运营同学在后台传一个包上去,所有接入的Agent下次启动就能发现并加载。

5. 实测中的失败模式与优化

技能的编写和使用过程中,我遇到过不少"看起来对、实际不对"的情况。这些失败模式在官方文档里很少提到,但我觉得才是做这块内容最有价值的部分。

5.1 Agent会"拿着锤子找钉子"

这是技能系统引入后最常见的问题。一旦Agent发现某个技能可用,它就会倾向于在任何相关问题上都用这个技能,哪怕场景并不完全匹配。我做过一个"合同摘要生成"的技能,结果有次用户只是问了个简单的合同问题,Agent也去调用了这个技能,跑了几分钟流程,输出一个十分详细但偏离问题的摘要。

这里面有个关键原因:技能描述里的"适用场景"写得太大。模型在判断"是否使用"时,宁可多用一个技能,也不愿漏掉,因为漏了可能被判定为没帮到用户,多用了至少表现得很勤奋。要压制这种倾向,一方面在SKILL.md里要写清楚"不适用场景",另一方面在调度层加一道程序化判断——把那些有明确前置条件的技能标记为"need_confirm",当Agent准备调用时,先反问用户确认,而不是直接执行。

5.2 指令过长被截断和忽略

SKILL.md写得越详细,模型越容易在中后段忽略步骤。这个现象尤其在上下文很长的时候会加剧。我自己测试过一个40行的SKILL.md,当对话轮次超过10轮后,Agent开始跳过步骤6到8,直接跳到最终输出。这不是模型bug,而是注意力被分散了。

解决思路是反直觉的:减少SKILL.md的篇幅,把压缩出来的详细内容移入脚本层面。比如,"支持哪些格式、各格式有什么限制"这种清单类的内容,从SKILL.md移到脚本里由脚本读取配置,SKILL.md里只需要写"运行 check_config.py --show-formats查看格式限制"。模型只需要知道去哪查,不需要把全部内容背下来。这样SKILL.md控制在20行以内,命中率和执行完整度都有明显提升。

5.3 技能冲突和优先级问题

技能多了之后,还会遇到"两个技能都能解决当前问题,该选哪个"的情况。比如"图片格式转换"和"图片体积压缩"两个技能,面对一张需要上传的图片时,Agent可能选择转换格式,但其实用户的原意是压缩体积。这类冲突靠Agent自己判断很不可靠,因为两个技能的描述都有可能匹配上。

我的处理方式是在调度层直接增加优先级字段,并且把业务规则前置:

  • 如果用户明确提到"压缩"或"变小",直接命中compress类技能;
  • 如果用户提到"格式不对""打不开",命中conversion类技能;
  • 如果两个都命中,按优先级:conversion > compress,但需要输出时同时给出另一个技能的建议。

这种规则写在流程代码里,而不是让Agent自由心证。技能系统像放权给Agent,但关键的判断节点还是需要有确定性的兜底逻辑,否则规模上去之后,失控是迟早的事。

6. 从个人脚本库到团队技能库:我的几条经验

聊到最后,跟你分享几条实践中沉淀下来的经验。

第一,技能包的SKILL.md一定要"先窄后宽"。宁可一开始只覆盖一个明确场景,跑通了再扩展,也不要一开始就野心勃勃地写"本技能可用于一切与文件相关的任务"。一个覆盖过宽的技能,Agent不知道该在什么时候调用,结果就是常常帮倒忙。先窄后宽还有一个好处:测试边界清晰,出现问题时能快速定位是哪个环节出了问题。

第二,每次改完SKILL.md,都要重新跑一遍真实的Agent对话验证,不能只看文档格式是否规范。我曾经改了一个技能里的一句话,从"建议转换为WebP"改成"必须转换为WebP",本来是想让Agent给出更确定的操作建议,结果它在所有场景下都强制转换,反而引入了新的问题。文本语义的细微变化,在模型那里可能被放大成截然不同的行为,所以每次变更都要过一遍真实的端到端测试。

第三,如果团队里多人维护技能库,一定要设一个"技能评审"环节。评审的重点不是代码风格,而是看SKILL.md的描述有没有歧义、脚本有没有越权操作、版本号是否正确、示例输入输出是否齐全。这个环节能挡掉多数线上问题,比事后修重要得多。

第四,从一个小的技能写起,哪怕它只能处理一个非常具体的任务,也比追求大而全的框架有价值。我自己最开始就是写了那个上传检查的技能,跑通之后才慢慢发现哪些地方该用脚本、哪些该用指令、哪些该用模板。技能这件事,动手做一遍的收获远大于读十篇文档。

Agent Skills这条路,跟之前的工具调用相比,更贴近业务表达,更强调知识封装和复用。如果你也在做Agent相关的事情,我建议可以先梳理一下日常开发里重复出现的工作流,选一个最小的场景,写成第一个技能包。等你发现第二个、第三个项目能直接复用它的时候,你会回来感谢这个决定的。

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

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

立即咨询