简介:本文从代码实现层面剖析智能体Skills与Function Calling的运行机制,梳理两者在抽象层级、上下文占用、状态编排与行为持续性上的核心差异,并结合单步操作、多步工作流、持续行为影响、耗时后台分析四类典型场景给出选型建议,帮助开发者判断何时直接用工具调用、何时用技能编排,避免过度设计或流程失控。
随着大模型驱动的智能体(Agent)应用逐渐普及,“让模型能做事”已经从单一的文本生成,扩展到读文件、执行命令、调用接口等真实操作。在这个演进过程中,开发者普遍会接触两个概念:Function Calling(函数调用/工具调用)与 Agent Skills(智能体技能)。
不少团队在架构设计时会产生疑问:Skills 是不是就是把一堆 Function Calling 打了个包?两者在代码层面到底有什么本质区别?什么时候该用哪一个?如果选错了粒度,轻则上下文浪费、响应变慢,重则多步流程失控、行为不可预期。本文围绕这两个对象,从实现机制、能力边界、适用场景三个层面做系统对比。
1. Function Calling(函数调用 / 工具调用)
Function Calling 是模型与外部运行时之间的一种结构化协议:开发者预先声明一组“工具”(包含名称、描述、参数的JSON Schema),模型在对话过程中根据用户意图决定是否调用某个工具,并输出符合Schema的参数;由外部运行时解析、校验并真正执行,再把结果回填给模型,模型基于结果继续生成回答。它的本质是“把模型的自然语言决策,转化为可执行的函数签名”。
2. Agent Skills(智能体技能)
Skills 是在工具调用之上构建的一层“能力封装”:把完成某类任务所需的知识(操作指引文档)、资源(脚本、模板、参考材料)与执行入口(触发条件、编排逻辑)打包成一个自包含单元。技能带有描述性元数据,供智能体在任务匹配时按需加载;执行时,技能内部可能编排多个工具调用、子流程甚至子上下文,形成一条完整工作流。
可以粗略类比:Function Calling 是一个个“原子动作”,Skill 是完成某类事情的“完整打法手册”。
1. Function Calling 的实现链路
Function Calling 在代码层面是一个典型的“决策—执行—回填”循环:
tools = [{"name": "read_file","description": "读取指定路径的文件内容","parameters": {"type": "object","properties": {"path": {"type": "string", "description": "文件路径"}},"required": ["path"]}},{"name": "run_command","description": "在受控环境中执行一条命令","parameters": {"type": "object","properties": {"cmd": {"type": "string"}},"required": ["cmd"]}}]messages = [{"role": "user", "content": "帮我看看配置文件里数据库地址是什么"}]while True:resp = llm.chat(messages=messages, tools=tools)if not resp.tool_calls:break # 模型给出最终回答,循环结束messages.append(resp.message)for call in resp.tool_calls:result = execute(call.name, call.arguments) # 运行时分发执行messages.append({"role": "tool","tool_call_id": call.id,"content": serialize(result)})
这条链路有三个关键特征:
2. Skills 的实现链路
一个技能在代码与文件层面通常表现为一个自包含目录:
skills/code_review/manifest.json # 名称、触发描述、适用范围、入口声明instructions.md # 详细执行指引(多步流程、判断规则)scripts/ # 辅助脚本(静态检查、格式化等)templates/ # 报告模板、输出格式定义
其核心是“渐进式加载”(progressive disclosure)的三段式设计:
# 第一层:只加载技能索引(每个技能只占几十个token的摘要)skill_index = load_manifest_summaries()# 模型根据任务描述命中技能后,第二层:加载完整指令task = "对这次改动做一次代码审查"skill = match_and_load(skill_index, task) # 命中 code_reviewinject(skill.instructions, context) # 注入详细工作流指引# 第三层:技能引擎按指引编排多个工具调用for step in skill.plan:if step.type == "tool_call":result = dispatch(step.tool, step.args) # 底层仍是Function Callingelif step.type == "checkpoint":skill.save_state(step.data) # 保存中间状态
在此基础上,Hooks(钩子) 与 Fork(分支执行) 进一步扩展了技能的行为边界:
def on_after_tool(tool_name, result):if tool_name == "write_file" and is_typed_script(result.path):run_formatter(result.path) # 写完即格式化register_policy("auto_format", enable=True) # 持久化为会话策略
async def run_heavy_analysis(task):child = Context(parent=main_ctx, isolated=True)report = await child.execute(task) # 长时间运行,不阻塞主对话return report.summary # 只把结论带回主上下文
| 维度 | Function Calling | Agent Skills |
|---|---|---|
| 抽象层级 | 协议层:单次调用契约 | 应用层:完整任务封装 |
| 执行粒度 | 原子动作,单步 | 多步工作流,可编排 |
| 上下文占用 | Schema常驻,随工具数线性增长 | 索引常驻+按需加载,占用可控 |
| 状态管理 | 无跨调用状态 | 可维护检查点与流程状态 |
| 决策方式 | 模型逐轮自主决策 | 引擎编排+模型决策结合 |
| 行为持续性 | 一次性,不改变后续行为 | 可通过Hooks持续生效 |
| 隔离能力 | 主循环内同步执行 | 可Fork子上下文异步执行 |
| 接入成本 | 低,定义Schema即可 | 高,需设计清单/指引/编排 |
| 适用任务 | 读文件、跑命令等单步操作 | 代码审查、部署等流程性任务 |
| 失败处理 | 单次重试 | 多步回滚、补偿、断点续跑 |
结合工程实践,可归纳为四类典型判断:
回到最初的问题:Skills 与 Function Calling 不是竞争关系,而是不同抽象层级上的互补关系。Function Calling 解决“单次动作如何被可靠地发起与执行”,是协议与地基;Skills 解决“一类任务如何被完整、可复用地完成”,是流程与建筑。
判断的核心只有三问:任务是单步还是多步?行为是一次性还是需要持续生效?执行要不要隔离于主上下文? 单步、一次性、轻量——用Function Calling;多步、流程化、需要状态与持续策略——用Skills,必要时叠加Hooks与Fork。理解了这组分层关系,工具与技能的边界划分就不再是风格偏好问题,而是一项可以推导、可以验证的架构决策。