简介:本文从代码实现层面拆解Agent技能机制与函数调用的异同:二者职责如何分工、宿主需要补哪些能力、技能目录与工具注册表为何不是一回事。读者将掌握技能加载循环的实现要点、两类方案的选型依据与迁移注意事项,避免把“写了SKILL.md”误当成“模型自动获得新工具”。
在构建具备工具使用能力的Agent系统时,开发者经常会遇到两个听起来相近、实际职责却截然不同的机制:函数调用(Function Calling) 与 技能机制(Agent Skills)。前者已经是主流模型接口的标准能力,后者则随着带有 SKILL.md 的开放技能格式逐渐进入工程实践。
混淆通常来自一个直觉:既然技能最终也要读文件、跑脚本,那它不就是另一种形式的函数调用吗?从代码层面看,这个直觉只对了一半。二者确实共享同一套工具调用循环,但它们在“模型看到什么”“宿主需要实现什么”“什么是可复用的”这三个问题上给出的答案完全不同。
本文对比的对象就是这两类机制:
SKILL.md 为核心的可复用包,包含元数据、操作方法与可选资源(脚本、参考材料、模板),通过“先发现、再按需加载”的方式进入模型上下文。需要说明的是,本文讨论的是带有 SKILL.md 的开放技能格式,而不是某些框架把任意函数简单命名为“skill”的旧用法——后者本质上仍是函数注册,不在讨论范围内。
函数调用解决的是“动作表达”问题。 它定义了模型与宿主之间的一种契约:模型不再用自然语言描述“我想查一下数据”,而是输出一个带名称和参数的结构化对象;宿主负责校验、执行、回填。它关心的是“调用哪个工具、传什么参数、返回什么结果”。
技能机制解决的是“方法组织”问题。 它把“什么时候该用某套方法、按什么步骤做、要参考哪些材料、失败时如何处理”打包成一个可复用的目录。技能本身不注册新工具,它改变的是模型在动手之前所掌握的上下文。
一个便于记忆的类比:函数调用是“工具箱里的扳手”,技能是“维修手册里的操作流程”。扳手能拧螺丝,但手册告诉你先断电、再拆哪颗、扭矩多大、遇到滑丝怎么办。
尽管定位不同,二者在工程上高度协同,共同点体现在四个方面:
SKILL.md、运行脚本,走的仍是原有的 read_file、run_process 等工具。也就是说,技能是“寄生”在函数调用循环之上的。函数调用依赖一份工具注册表:每个工具需要预先定义名称、描述、参数 Schema,并在每次请求中随 tools 字段传给模型。工具的集合是显式的、结构化的、参与解码约束的。
技能机制依赖一次目录扫描:宿主在启动时遍历配置的技能根目录,解析每个 SKILL.md 的元数据(名称、描述、文件位置),渲染成一份“技能目录”注入系统提示。技能正文不参与解码约束,它是普通的自然语言上下文。
这意味着:新增一个工具,通常要改代码里的 Schema 定义并重新部署;新增一个技能,往往只需往目录里放一个文件夹。
函数调用的工具描述是全量常驻的——只要在 tools 列表里,每一轮请求都会带上,模型始终“看得见”所有工具。工具多了,上下文开销和选择难度同步上升。
技能采用两级加载:第一级只暴露元数据目录(名称+描述),成本很低;第二级在任务匹配时才读取完整 SKILL.md,甚至进一步读取其引用的脚本与参考资料。这种“渐进式披露”让技能数量增长时上下文压力相对可控。
工具的参数是强类型的,有明确的字段、类型、必填项,便于程序校验;技能正文是 Markdown,承载的是操作步骤、判断准则、注意事项、失败处理策略等难以 Schema 化的知识。
这正是二者的互补之处:Schema 擅长表达“传什么”,Markdown 擅长表达“怎么做、何时做、做错了怎么办”。
工具的执行逻辑通常写在宿主代码里,是宿主进程的一部分(或通过受控适配层调用)。技能的脚本是外部文件,宿主并不理解其内部逻辑,只负责在受控环境中启动进程、传入参数、收集输出。
由此带来一个关键差异:技能脚本的可靠性、依赖环境、副作用,宿主无法从类型层面保证,只能靠沙箱、超时、资源限制和输出校验来兜底。
| 维度 | 函数调用 | 技能机制 |
|---|---|---|
| 新增能力 | 改代码、定义 Schema、重新部署 | 新增目录与 SKILL.md,重启扫描即可 |
| 迭代主体 | 通常是开发工程师 | 领域专家也可参与撰写方法 |
| 版本管理 | 随代码仓库 | 随技能目录,可独立分发 |
| 失败影响面 | 影响宿主进程稳定性 | 隔离在子进程中,影响相对局部 |
把技能接进一个已有工具循环,最小改动不是“增加业务函数”,而是补两块宿主能力:发现(discover) 与 受控分发(dispatch)。
以下为省略 API 适配、文件解析与异常处理细节的示意性伪代码,非可直接运行的示例:
# 1. 启动时扫描技能目录,解析元数据catalog = discover_metadata(config.skill_roots)# 2. 将技能目录渲染进系统提示,并告知加载规则messages = [system(base_rules + render_catalog(catalog)),user(request),]# 3. 工具列表保持不变,不因技能而新增tools = [READ_FILE_SCHEMA, RUN_PROCESS_SCHEMA]for round in range(MAX_ROUNDS):reply = model(messages, tools=tools)messages.append(reply)if not reply.tool_calls:return reply.textfor call in reply.tool_calls:# 宿主的职责:识别已注册工具、校验参数与路径、# 检查权限、在受控环境中执行、回填成功或错误结果result = checked_dispatch(call)messages.append(tool_result(call.id, result))raise BudgetExceeded
这段代码有三个值得注意的点:
tools 列表里没有出现任何技能相关的新工具。 技能不注册为工具,模型读技能靠的是 read_file,跑脚本靠的是 run_process。render_catalog 是新增的关键环节。 它把“有哪些技能、各自解决什么问题、文件在哪”告诉模型,并附上“任务匹配时先读取对应 SKILL.md”的指令。checked_dispatch 必须由宿主完整实现。 模型给出的路径与参数一律视为不可信输入,需要做白名单校验、路径穿越防护、权限检查与受控执行。对照一个 CSV 数据质量检查技能的目录结构:
csv-audit/├── SKILL.md└── scripts/└── audit.py
SKILL.md 可以写成:
---name: csv-auditdescription: 检查 CSV 的缺失值和重复键,输出数据质量摘要。---先确认主键列和空值定义,不自行猜测。使用 scripts/audit.py 检查,不修改输入文件。报告行数、缺失值数、重复键数。执行失败时说明原因,不编造统计数字。
职责划分一目了然:Markdown 给出操作方法与判断准则,Python 文件承载实际计算。规范要求技能目录中有 SKILL.md 并包含名称、描述等元数据与 Markdown 正文,脚本、参考材料、模板均为可选资源。文件格式本身不会把 audit.py 注册成模型的新工具,也不会替宿主安装运行环境。
| 对比维度 | 函数调用 | 技能机制 |
|---|---|---|
| 核心职责 | 表达调用意图与参数 | 组织使用方法与参考材料 |
| 上下文形态 | 结构化 Schema,全量常驻 | 元数据目录 + 按需读取的 Markdown |
| 是否注册新工具 | 是 | 否,复用既有工具 |
| 扩展方式 | 改代码、改 Schema | 新增目录与文件 |
| 加载时机 | 每轮请求携带 | 任务匹配后渐进加载 |
| 强约束能力 | 强(类型校验) | 弱(自然语言,靠宿主兜底) |
| 安全边界 | 宿主函数内部 | 子进程 + 沙箱 + 路径校验 |
| 适合承载 | 原子动作 | 流程、准则、经验知识 |
优先强化函数调用的场景: 动作边界清晰、参数强类型、调用频次高、对延迟敏感。例如查询结构化数据、发起状态变更、读写受控资源。这类场景需要解码约束与严格校验,自然语言方法反而增加不确定性。
优先引入技能机制的场景: 任务需要多步骤方法、依赖领域经验、失败处理有讲究、知识更新频繁。例如数据质量核查、报告生成规范、合规检查清单、特定格式的文档处理流程。这类场景的瓶颈往往不在“有没有工具”,而在“会不会用、用得对不对”。
二者配合的典型形态: 技能负责“先读手册、按步骤来、出错照准则处理”,函数调用负责“真正动手”。技能正文里可以明确指示“使用某个已有工具完成某步”,从而把方法知识与执行能力串起来。
给出条件化的判断思路,而非绝对推荐:
使用边界与风险:
从纯函数调用方案迁移到“函数调用 + 技能”的混合方案,需要关注以下几点:
回到对比主题:函数调用与技能机制不是替代关系,而是动作层与方法层的分工。函数调用负责把“调用哪个工具、传什么参数”表达清楚,并依赖强类型 Schema 获得可靠的执行契约;技能机制把“什么时候用、按什么步骤做、参考哪些材料、失败怎么办”组织成可复用的包,通过目录发现与按需加载进入上下文。
代码层面上,最清晰的判断标准是:如果一项改动需要新增或修改工具 Schema,它属于函数调用;如果一项改动只是往目录里放 SKILL.md 与配套资源,它属于技能机制。 前者的扩展成本落在代码与部署上,后者的扩展成本落在元数据质量与宿主兜底能力上。
选型时不必追求“哪个更先进”,而应回到失败来源:如果失败主要来自“工具不够用”,补函数调用;如果失败主要来自“做法不一致、步骤遗漏、准则缺失”,补技能。两者配合,才能让Agent既有力气,也有章法。