Agent Skills与Function Calling:异同解析与选型指南

作者:热心市民鹿先生2026.11.04 12:36浏览量:0

简介:本文从代码实现层面剖析智能体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 在代码层面是一个典型的“决策—执行—回填”循环:

  1. tools = [
  2. {
  3. "name": "read_file",
  4. "description": "读取指定路径的文件内容",
  5. "parameters": {
  6. "type": "object",
  7. "properties": {
  8. "path": {"type": "string", "description": "文件路径"}
  9. },
  10. "required": ["path"]
  11. }
  12. },
  13. {
  14. "name": "run_command",
  15. "description": "在受控环境中执行一条命令",
  16. "parameters": {
  17. "type": "object",
  18. "properties": {"cmd": {"type": "string"}},
  19. "required": ["cmd"]
  20. }
  21. }
  22. ]
  23. messages = [{"role": "user", "content": "帮我看看配置文件里数据库地址是什么"}]
  24. while True:
  25. resp = llm.chat(messages=messages, tools=tools)
  26. if not resp.tool_calls:
  27. break # 模型给出最终回答,循环结束
  28. messages.append(resp.message)
  29. for call in resp.tool_calls:
  30. result = execute(call.name, call.arguments) # 运行时分发执行
  31. messages.append({"role": "tool",
  32. "tool_call_id": call.id,
  33. "content": serialize(result)})

这条链路有三个关键特征:

  • 工具Schema随每次请求发送,常驻上下文,工具越多、描述越长,占用越大;
  • 每次调用都是独立的一次“模型决策→执行→回填”,下一步走向完全由模型自主决定;
  • 运行时不维护跨调用的业务状态,流程状态隐式存在于对话历史中。

2. Skills 的实现链路

一个技能在代码与文件层面通常表现为一个自包含目录:

  1. skills/code_review/
  2. manifest.json # 名称、触发描述、适用范围、入口声明
  3. instructions.md # 详细执行指引(多步流程、判断规则)
  4. scripts/ # 辅助脚本(静态检查、格式化等)
  5. templates/ # 报告模板、输出格式定义

其核心是“渐进式加载”(progressive disclosure)的三段式设计:

  1. # 第一层:只加载技能索引(每个技能只占几十个token的摘要)
  2. skill_index = load_manifest_summaries()
  3. # 模型根据任务描述命中技能后,第二层:加载完整指令
  4. task = "对这次改动做一次代码审查"
  5. skill = match_and_load(skill_index, task) # 命中 code_review
  6. inject(skill.instructions, context) # 注入详细工作流指引
  7. # 第三层:技能引擎按指引编排多个工具调用
  8. for step in skill.plan:
  9. if step.type == "tool_call":
  10. result = dispatch(step.tool, step.args) # 底层仍是Function Calling
  11. elif step.type == "checkpoint":
  12. skill.save_state(step.data) # 保存中间状态

在此基础上,Hooks(钩子) 与 Fork(分支执行) 进一步扩展了技能的行为边界:

  • Hooks 在工具执行前后插入回调,实现对后续行为的持续影响:
  1. def on_after_tool(tool_name, result):
  2. if tool_name == "write_file" and is_typed_script(result.path):
  3. run_formatter(result.path) # 写完即格式化
  4. register_policy("auto_format", enable=True) # 持久化为会话策略
  • Fork 为耗时任务派生隔离子上下文,独立执行后仅回传摘要:
  1. async def run_heavy_analysis(task):
  2. child = Context(parent=main_ctx, isolated=True)
  3. report = await child.execute(task) # 长时间运行,不阻塞主对话
  4. return report.summary # 只把结论带回主上下文

四、相同点分析

  1. 目标一致:两者都是为了让模型突破“只能生成文本”的限制,把自然语言意图转化为可验证的外部动作。
  2. 底层依赖相同:技能在执行阶段最终仍要落到一个个工具调用上,Function Calling 是技能运行的基础设施之一。
  3. 都依赖模型的指令遵循与结构化输出能力:无论是输出工具参数,还是判断“该触发哪个技能”,都要求模型具备可靠的决策与格式化能力。
  4. 都需要运行时兜底:两者都不能“裸奔”,都需要外部执行环境负责鉴权、参数校验、超时、重试与结果回填。
  5. 都面临安全边界问题:执行外部操作必然涉及权限最小化、沙箱隔离与审计日志。

五、核心差异分析

  1. 抽象层级不同:Function Calling是协议层,定义“模型如何发起一次调用”;Skills是应用层,定义“一类任务如何被完整地完成”。技能可以包含工具,工具却不感知技能的存在。
  2. 执行粒度不同:一次函数调用解决一个原子动作(读一个文件、跑一条命令);一个技能覆盖一条多步流程(审查、部署、规范检查),内部可能串联数十次工具调用。
  3. 上下文管理策略不同:函数调用的工具Schema常驻上下文,工具规模增长会线性推高“常驻成本”;技能采用按需加载,未触发的技能只占索引级开销,命中后才注入完整指令,天然更适合大规模工具集。
  4. 状态与编排能力不同:函数调用本身无状态,长流程全靠模型逐轮决策,容易走偏或重复;技能引擎可显式维护步骤、检查点、条件分支与重试策略,把一部分“流程确定性”从模型手里接管过来。
  5. 行为持续性不同:一次工具调用结束后不会改变智能体的后续行为;技能结合Hooks可以在执行后注册持久策略,让“这次决定的规则”持续生效。
  6. 隔离性不同:函数调用在主对话循环内同步执行,长任务会阻塞并污染上下文;技能可配合Fork派生子上下文,把耗时分析隔离在后台,仅回传结论。
  7. 工程复杂度不同:接入函数调用只需写Schema与执行函数,改造成本低;建设技能体系需要设计清单规范、指引文档、触发匹配、状态存储与Hook机制,前期投入明显更高。

六、对比表格

维度 Function Calling Agent Skills
抽象层级 协议层:单次调用契约 应用层:完整任务封装
执行粒度 原子动作,单步 多步工作流,可编排
上下文占用 Schema常驻,随工具数线性增长 索引常驻+按需加载,占用可控
状态管理 无跨调用状态 可维护检查点与流程状态
决策方式 模型逐轮自主决策 引擎编排+模型决策结合
行为持续性 一次性,不改变后续行为 可通过Hooks持续生效
隔离能力 主循环内同步执行 可Fork子上下文异步执行
接入成本 低,定义Schema即可 高,需设计清单/指引/编排
适用任务 读文件、跑命令等单步操作 代码审查、部署等流程性任务
失败处理 单次重试 多步回滚、补偿、断点续跑

七、典型场景选择

结合工程实践,可归纳为四类典型判断:

  1. 单步原子操作——直接用Function Calling。任务本身就是“读一个文件、执行一条命令、写一个文件”,中间没有流程语义,引入技能只会增加匹配与加载开销,属于过度设计。
  2. 多步工作流——用Skill编排。代码审查需要“读差异→跑检查→对照规范→生成报告”多个环节;部署流程涉及“构建→验证→灰度→确认”的顺序与条件依赖。这类任务的步骤间存在确定性依赖,适合用技能把流程固化,降低模型逐轮自由决策带来的不确定性。
  3. 执行后需要持续影响行为——用Skill + Hooks。例如要求“每次写完某类静态类型前端脚本文件后自动格式化”,这本质上是一条跨越多次工具调用的持续策略,单次工具调用无法表达,需要Hook在工具执行后注册持久规则。
  4. 耗时分析且不需改变主上下文——用Fork模式。深度扫描、批量依赖分析等任务可能运行数分钟并产生大量中间输出,放在主循环里既阻塞交互又挤占上下文;派生子上下文独立执行、只回传摘要,是更合理的选择。

八、选型建议

  1. 不要二选一,而是分层组合:Function Calling是地基,Skills是地基上的流程建筑。成熟的智能体应用通常会同时保留两者——原子工具直接暴露给模型,流程型能力以技能形式组织。
  2. 以工具数量与流程长度为标尺:工具数在个位数、任务以单步为主时,纯函数调用足够;当工具规模明显扩大、Schema挤占上下文,或出现重复的多步调用链时,才值得引入技能层。
  3. 以确定性需求为标尺:流程约束越硬(合规检查、发布流程),越应把控制权上收到技能引擎;探索型、开放型任务可保留更多模型自由度。
  4. 以行为是否需要跨调用延续为标尺:凡是“从今往后都要……”类需求,都指向Hooks机制,而不是反复在提示词中提醒模型。
  5. 团队与平台考量:自建技能体系对工程能力要求较高;如果团队希望聚焦业务逻辑,也可以评估托管化程度更高的智能体开发平台(例如百度智能云提供的智能体开发相关通用服务),借助平台的能力封装与编排组件降低自研成本,但仍需结合自身定制深度与数据合规要求综合判断。

九、迁移与使用注意事项

  1. 从纯工具调用迁移到技能体系时,应优先把“重复出现的调用链”沉淀为技能,而非一次性把所有工具都包装成技能——否则技能索引膨胀会反向拖累触发准确性。
  2. 技能的manifest与指引文档需要版本化管理:指令措辞的细微变化可能改变模型的行为路径,建议像管理代码一样做评审与回归测试。
  3. 权限边界要重新评估:技能往往需要组合多个工具的权限,最小权限原则容易被“打包”稀释,应按技能粒度而非工具粒度配置授权,并对脚本类资源做沙箱执行。
  4. 可观测性要升级到链路级:单次工具调用只需看单条日志;技能执行需要跨步骤的调用追踪、检查点状态与失败补偿记录,否则排障成本会随步骤数上升。
  5. Fork子上下文要设计好回传结构:只回传摘要可以节省上下文,但会丢失细节,关键结论应结构化输出并落盘,供事后审计。
  6. 保持解耦:技能层应避免绑定特定模型的调用格式,保持对底层工具协议的中立性,便于后续替换或升级模型与运行时。

十、总结

回到最初的问题:Skills 与 Function Calling 不是竞争关系,而是不同抽象层级上的互补关系。Function Calling 解决“单次动作如何被可靠地发起与执行”,是协议与地基;Skills 解决“一类任务如何被完整、可复用地完成”,是流程与建筑。

判断的核心只有三问:任务是单步还是多步?行为是一次性还是需要持续生效?执行要不要隔离于主上下文? 单步、一次性、轻量——用Function Calling;多步、流程化、需要状态与持续策略——用Skills,必要时叠加Hooks与Fork。理解了这组分层关系,工具与技能的边界划分就不再是风格偏好问题,而是一项可以推导、可以验证的架构决策。