type
Post
status
Published
date
May 4, 2026
slug
agent29
summary
tags
Agent
category
icon
password
很多人第一次接触 Function Calling 时,会误以为“大模型真的在调用函数”。实际上不是。LLM 本身并不会执行你的函数、读你的数据库、写你的文件、调用你的 API。它做的事情是:在生成文本的过程中,按约定生成一个结构化的“工具调用请求”,然后由宿主程序、SDK、Agent Runtime 或模型厂商的服务器去执行这个请求,并把结果再塞回上下文。
Anthropic 的官方文档把这件事说得非常直接:工具调用是应用程序和模型之间的一种 contract;开发者提供可用操作及输入输出形状,Claude 决定何时、如何调用,但模型不会自己执行操作,它只输出结构化请求,由你的代码或服务端执行,再把结果回传给模型。(Claude Platform) OpenAI 的官方文档也把流程拆成五步:带工具请求模型、接收模型的 tool call、应用侧执行代码、把工具输出再次发给模型、得到最终回答或更多 tool calls。(OpenAI开发者)
1. 一句话理解 Function Calling
Function Calling 的本质是:
把“自然语言意图”翻译成“结构化函数调用参数”,再由外部运行时执行函数,并把执行结果作为新的上下文交还给 LLM。
所以它不是传统意义上的 RPC,也不是 LLM 内部真的链接了你的函数。它更像是:
LLM:我需要调用search_docs,参数是{ "query": "xxx", "top_k": 5 }Runtime:收到,我去执行search_docs(query, top_k)Tool:返回检索结果Runtime:把结果包装成tool_result/function_call_outputLLM:根据结果继续推理并回答用户
2. Function Calling 的核心协议:Tool Schema
LLM 想正确调用工具,必须先知道有哪些工具可用、每个工具什么时候用、需要哪些参数。这就是 tool schema。
一个典型工具定义包含:
字段 | 作用 |
name | 工具名,也是模型输出 tool call 时引用的名字 |
description | 工具语义说明,影响模型什么时候选择它 |
parameters / input_schema | JSON Schema 或 Zod Schema,用于描述输入参数 |
strict | 是否要求模型严格遵守 schema |
execute | 宿主程序里的实际执行函数,很多 SDK 会封装这一层 |
Anthropic 的工具定义要求包括
name、description、input_schema,并且说明当你传入 tools 参数时,API 会根据工具定义、工具配置和用户 system prompt 构造特殊的 tool-use system prompt,把可用函数以 JSON Schema 形式暴露给模型。(Claude Platform) OpenAI 的函数定义也包含 type、name、description、parameters 和 strict 等字段,其中 parameters 是 JSON Schema。(OpenAI开发者)一个简化版 OpenAI 风格工具定义如下:
注意:
description 不是给人看的注释那么简单,它是模型选择工具的重要依据。Claude 文档明确建议写详细描述,包括工具做什么、什么时候用、参数含义、限制和返回信息,并建议复杂工具提供 input examples。(Claude Platform)3. 底层执行流程图
核心闭环可以概括成:
Prompt with tools → Model emits tool call → Runtime validates and executes → Runtime returns tool result → Model continues.
Anthropic 把 client-side tool 的 agentic loop 描述为一个
while stop_reason == "tool_use" 的循环:发送带 tools 的请求,Claude 返回 tool_use block,应用执行工具并格式化为 tool_result,再把原始消息、assistant 响应和 tool_result 发回模型,直到不再返回 tool_use。(Claude Platform)4. 模型输出的不是“函数执行结果”,而是“调用意图”
以 Claude 为例,模型返回的结构可能类似:
OpenAI Responses API 中,则可能出现
function_call item,包含 call_id、name、arguments。工具执行后,应用需要把结果作为 function_call_output 返回,并通过 call_id 关联到原始 tool call。OpenAI 文档强调,tool call output 可以是 JSON、纯文本、图片或文件内容,但需要引用具体的 model tool call。(OpenAI开发者)这也是很多 Agent 框架的共同实现方式:
这段代码揭示了 Function Calling 的本质:LLM 负责决策,Runtime 负责执行。
5. Function Calling 的“底层机制”到底是什么?
从模型侧看,可以拆成四层。
5.1 Schema 注入:把工具说明塞进上下文
调用模型时,Runtime 会把工具定义传给模型 API。模型看到的不只是用户问题,还包括:
你有哪些工具每个工具叫什么每个工具什么时候用每个工具需要什么参数输出应该遵守什么格式
Anthropic 文档说明,传入
tools 时,API 会构造特殊 system prompt,其中包含工具定义和格式化指令。(Claude Platform) 这意味着工具调用不是魔法,而是“协议化提示 + 模型训练 + 输出解析”的组合。5.2 Tool Selection:模型选择工具
模型在生成阶段会判断:
当前任务是否能直接回答?是否需要外部数据?是否需要执行动作?哪个工具最匹配?参数如何从用户意图中提取?
例如用户问:
“帮我看一下这个项目最近失败的 CI 是什么原因。”
Agent 可能需要依次调用:
git_status
github_list_workflow_runs
github_get_job_logs
read_file
bash
如果工具描述写得模糊,模型可能会选错工具。如果工具太多,模型也可能混淆。所以 Anthropic 建议合并相关操作、使用命名空间、减少选择歧义,比如用
github_list_prs、slack_send_message 这类名字。(Claude Platform)5.3 Argument Generation:模型生成结构化参数
模型不是输出“我要查天气”,而是输出:
如果是普通 tool calling,这个 JSON 参数本质上仍然是模型生成的文本,只是会被 provider 或 SDK 解析。它可能出错,例如少字段、类型错、枚举错、编造参数。
所以生产系统通常会做三层保护:
层级 | 作用 |
Provider strict mode | 尽量让模型输出符合 schema |
Runtime schema validation | 执行前校验参数 |
Tool-level guardrail | 工具内部再次校验权限、路径、命令、额度 |
OpenAI 文档说明,
strict: true 会让 function call 更可靠地遵守 schema,并且 strict mode 底层利用 Structured Outputs;同时 strict schema 要求对象设置 additionalProperties: false,并且所有 properties 都在 required 里。(OpenAI开发者) Vercel AI SDK 也把 inputSchema 同时用于 LLM 指导和 tool call 输入校验,并支持 provider 可用时启用 strict tool calling。(AI SDK)5.4 Tool Result Injection:把执行结果回灌给模型
工具执行后,Runtime 会把结果包装成特殊消息,而不是简单拼到 prompt 里。
例如 OpenAI 风格:
Claude 风格:
然后模型继续生成。它可能:
根据结果给最终答案发现信息不够,再调用另一个工具对工具错误进行修复请求用户确认终止任务
这就是 Agent 常说的 Observe / Act / Observe 循环。
6. Function Calling 与 ReAct 的关系
Function Calling 是“工具调用协议”;ReAct 是“推理-行动-观察”的 Agent 循环范式。
二者关系可以这样理解:
Function Calling 解决的是:
模型如何以结构化方式表达“我要调用哪个工具、参数是什么”。
ReAct 解决的是:
多轮任务中,模型如何在“思考、调用工具、观察结果、继续决策”之间循环。
Claude、OpenCode、OpenClaw、Cursor、Copilot Coding Agent 这类 coding agent,本质上都不是只调用一次函数,而是围绕 tool calling 搭了一个长期循环:读文件、搜索、编辑、运行测试、观察报错、再编辑。
7. 从 Claude / Claude Code 看工具调用设计
Claude API 把工具分成三类:
类型 | 谁定义 schema | 谁执行 |
用户自定义 client tools | 开发者 | 开发者应用 |
Anthropic-schema client tools | Anthropic 发布标准 schema | 开发者应用 |
Server-executed tools | Anthropic | Anthropic 服务端 |
Anthropic 文档中,
bash、text_editor、computer、memory 属于 Anthropic-schema client tools;web_search、web_fetch、code_execution、tool_search 属于 server-executed tools。server-executed tools 由 Anthropic 服务端内部执行循环,应用侧不需要构造 tool_result。(Claude Platform)这对 Agent 设计很关键:
client tool 适合接入业务系统、文件系统、私有 API;server tool 适合通用搜索、沙箱代码执行等厂商托管能力。
Claude Code 作为 coding agent,会读取代码库、编辑文件、运行命令,并集成开发工具。Anthropic 官方概览明确把 Claude Code 描述为能读代码库、编辑文件、运行命令并集成开发工具的 agentic coding tool。(Claude API Docs) Claude Code 的 hooks 文档还说明 hooks 是在 Claude Code 生命周期特定点执行的用户定义 shell 命令,可用于 deterministic control,而不是依赖 LLM 自己决定是否运行某些规则。(Claude API Docs)
这说明成熟 coding agent 通常会把系统分成两层:
LLM 负责“判断与生成 tool call”,Hooks / Permissions / Runtime 负责“确定性控制”。
8. 从 OpenCode 源码看工具注册与执行
OpenCode 是一个开源 coding agent。它内置 Build 和 Plan 两类主 agent:Build 默认拥有完整开发工具访问,Plan 更偏只读分析,默认会限制编辑和 bash。(GitHub) OpenCode 文档还说明,Primary agent 的工具访问由 permissions 配置,例如 Build 全工具可用,Plan 受限。(OpenCode)
OpenCode 的工具系统很值得看,因为它体现了典型 Agent Runtime 的几层结构:
从 OpenCode
ToolRegistry 源码可以看到,Registry 会导入内置工具,如 shell/read/glob/grep/edit/write/task/webfetch/websearch/skill/patch 等,也会加载 plugin tools;插件工具会被转换成统一的 Tool.Def,包含 id、parameters、jsonSchema、description 和 execute。源码里还会对插件返回值做截断处理,并把执行 span 打上 tool.name、session.id、message.id、tool.call_id 等属性。(GitHub)把源码思想简化成伪代码:
这里的重点不是某个工具怎么写,而是这个架构:
工具先注册成统一协议,再根据模型、agent、权限、运行环境过滤,最后才暴露给 LLM。
OpenCode 权限文档也说明,权限按工具名和安全守卫配置,包括
read、edit、glob、grep、bash、task、skill、webfetch、websearch、external_directory、doom_loop 等;默认 .env 文件会被 deny,外部目录和 doom loop 默认 ask。(OpenCode)9. Bash Tool 为什么是高风险工具?
Coding Agent 里最危险的工具通常是 shell / bash,因为它具备任意执行能力。OpenCode 的 bash tool 源码里可以看到几个典型工程设计:
它为 bash tool 定义了command、timeout、workdir、description等参数;会解析 bash / PowerShell 命令;会收集涉及的外部路径;执行前通过ctx.ask做权限确认;运行时支持 timeout、abort、输出截断和 metadata 更新。(GitHub)
简化后类似:
这就是为什么生产级 Agent 不能只做“模型输出 tool call → 直接执行”。中间必须有:
schema validationpermission checksandboxtimeoutoutput truncationaudit loghuman approvaldangerous command detection
10. 从 OpenClaw 看“工具、技能、插件”的分层
OpenClaw 的工具文档把能力面拆成 tools、skills、plugins。文档中说明:tool 是 agent 可以调用的 typed function,如
exec、browser、web_search、message、image_generate;visible tools 会作为结构化 function definitions 发送给模型;模型只能看到经过 active profile、allow/deny policy、provider restrictions、sandbox state、channel permissions、plugin availability 等过滤之后仍然可用的工具。(GitHub)这点非常关键:
不是系统里存在的所有工具都应该暴露给模型。模型只能看到当前回合、当前身份、当前权限下允许调用的工具。
OpenClaw 文档还区分了:
概念 | 作用 |
Tool | 可调用动作,例如执行命令、读写文件、搜索网页 |
Skill | 注入 prompt 的工作流说明、规范、操作步骤 |
Plugin | 添加 runtime 能力,例如工具、provider、channel、hook、技能包 |
OpenClaw 文档中也说明,plugins 可以注册额外工具,插件作者通过
api.registerTool(...) 和 manifest 中的 contracts.tools 接入。(GitHub)把这个思想放到通用 Agent 系统里:
所以 Skills、Tools、Plugins 不是一回事:
Skill 改变“模型知道什么流程”;Tool 改变“模型能请求什么动作”;Plugin 改变“系统实际具备什么运行时代码和集成能力”。
11. Function Calling 与 RAG 的关系
RAG 其实可以看成一种特殊工具调用:
当用户问业务知识、历史文档、代码库上下文时,Agent 可以调用检索工具:
区别在于:
场景 | 工具作用 |
普通 Function Calling | 执行业务函数、API、数据库操作 |
RAG Tool | 检索外部知识,把证据交给模型 |
Coding Agent Tool | 读文件、改文件、跑测试、查日志 |
Browser Tool | 操作网页或抓取页面内容 |
MCP Tool | 通过标准协议调用外部 server 暴露的工具 |
OpenAI 文档也把 function calling 的主要用途分成数据获取和动作执行:获取天气、账户详情、退款等都可以作为工具能力暴露给模型。(OpenAI开发者)
12. Parallel Tool Calls:一次返回多个工具调用
现代模型可能一次返回多个 tool calls。例如用户问:
“帮我查东京和巴黎今天的天气。”
模型可能同时返回:
Runtime 可以并发执行,然后把两个结果一起回传。OpenAI 文档说明模型可能在一个 turn 中调用多个函数,也可以通过
parallel_tool_calls: false 禁止这种行为,确保一次最多调用零个或一个工具。(OpenAI开发者) Anthropic 也支持 parallel tool use,并允许通过配置限制并行工具调用;其 release notes 中提到可在 tool_choice 中设置 disable_parallel_tool_use: true 以确保最多使用一个工具。(Claude API Docs)工程上建议:
场景 | 是否允许并行 |
多个只读查询 | 可以并行 |
写操作 / 资金 / 邮件发送 | 不建议并行 |
有依赖顺序的操作 | 不并行 |
shell 命令 / 文件编辑 | 谨慎,通常串行 |
13. Streaming Tool Call:参数可能是流式生成的
当开启 streaming 时,模型可能不是一次性给出完整 JSON 参数,而是以 delta 形式逐步吐出函数参数。OpenAI 文档说明,streaming function calls 可以实时显示模型正在填充的函数和参数,并且 Responses API 中会有
response.function_call_arguments.delta,需要聚合 delta,最终在 response.function_call_arguments.done 中拿到完整 function call。(OpenAI开发者)因此生产系统要注意:
不要在参数还没完整时就执行工具。必须等到 tool call argument done / 完整 JSON parse 成功 / schema validation 通过之后再执行。
简化版聚合逻辑:
14. 生产级工具调用 Runtime 应该怎么设计?
一个生产级 Agent Runtime 通常至少包含这些模块:
14.1 Tool Registry
负责注册工具:
14.2 Tool Call Parser
负责兼容不同模型供应商:
14.3 Schema Validator
不要相信模型参数一定合法:
Vercel AI SDK 文档也明确列出 tool-call 相关错误,包括模型调用不存在的工具、输入不匹配 schema、tool call repair 失败;工具执行异常会以 tool-error content parts 形式进入多步场景。(AI SDK)
14.4 Permission Engine
不要把权限交给模型判断。模型可以建议,不能授权。
OpenCode 权限系统就是按工具名和安全守卫做控制,并支持 ask / allow / deny 这类行为。(OpenCode) AI SDK 也提供 tool execution approval,并说明可以根据工具输入动态决定是否需要审批。(AI SDK)
14.5 Tool Executor
执行器要处理超时、取消、重试、输出截断:
OpenCode bash tool 源码中就包含 timeout、abort、进程 kill、输出截断、metadata 更新等处理。(GitHub)
15. 常见失败模式
15.1 工具名幻觉
模型输出了不存在的工具:
处理方式:
不执行。返回 tool error:No such tool。让模型重新选择已暴露工具。记录审计日志。
15.2 参数不合法
例如 schema 要求
top_k 是 number,模型输出 "five"。处理方式:
schema validation 拦截。返回结构化错误给模型。允许模型自修复一次,但要设置最大重试次数。
15.3 工具返回过大
例如 grep 返回 10MB 日志。
处理方式:
工具层截断。返回摘要、路径、行号、分页 token。不要把完整日志塞回上下文。
Anthropic 工具定义最佳实践也强调,tool responses 应该只返回高信号信息,避免冗余字段浪费上下文。(Claude Platform)
15.4 工具执行有副作用
例如发送邮件、删文件、付款、发 PR。
处理方式:
默认 ask。敏感操作必须 human-in-the-loop。最好做 dry-run / preview / diff。执行后记录审计日志。
15.5 Doom Loop
模型反复调用同一个工具,参数一样,结果一样。
处理方式:
记录 tool call hash。同样工具 + 同样参数连续 N 次时中断。要求模型总结已知信息或询问用户。
OpenCode 权限文档中有
doom_loop 安全守卫,用于同一工具调用以相同输入重复 3 次的场景。(OpenCode)16. 工具设计最佳实践
16.1 工具粒度不要太碎
不要这样设计:
更好的方式:
Anthropic 文档也建议合并相关操作,避免每个 action 一个工具,因为更少、更强的工具可以减少选择歧义。(Claude Platform)
16.2 工具名要有命名空间
推荐:
不推荐:
16.3 参数要语义稳定
不要让模型传内部临时对象:
推荐传稳定 ID:
16.4 返回值要短而有证据
RAG 工具不要只返回一大段文本,最好返回:
这样模型可以引用、比较、判断时效性。
17. 一个完整的 Function Calling Mini Runtime
下面是一个可落地的极简 TypeScript 版本,展示从工具注册到循环执行的主干逻辑:
这个 mini runtime 已经具备生产系统的雏形:
工具注册可见工具过滤模型调用tool call 解析schema 校验权限审批超时取消错误回传多步循环最大步数限制
18. 为什么 Function Calling 比“让模型输出 JSON”更可靠?
早期做法通常是:
让模型输出 JSON用正则或 JSON.parse 抽取根据字段名手动调用函数
问题是:
输出格式不稳定容易混入解释文本无法表达多工具并发缺少 call_id缺少 provider / SDK 层面的 schema 约束很难统一 streaming、error、tool result
Function Calling 的改进是:
能力 | 普通 JSON 输出 | Function Calling |
工具名结构化 | 弱 | 强 |
参数 schema | 弱 | 强 |
多工具调用 | 难 | 原生支持 |
工具结果关联 | 手动 | call_id / tool_use_id |
SDK 支持 | 少 | 完整 |
strict schema | 通常无 | 支持 |
Agent Loop | 自己拼 | 标准模式 |
Anthropic 文档中有一个判断标准很值得记住:如果你正在写 regex 从模型输出里提取决策,那这个决策就应该被设计成工具调用 schema。(Claude Platform)
19. Function Calling 在 Agent 系统中的位置
一个完整 Agent 通常不是只有 Function Calling,而是:
其中:
模块 | 负责什么 |
Function Calling | 结构化表达工具调用 |
Tool Registry | 管理工具定义 |
Permission Engine | 决定能不能执行 |
Executor | 真正运行函数 |
Context Manager | 管理历史、结果、压缩 |
Planner / ReAct Loop | 决定多步任务怎么推进 |
Memory / RAG | 补充历史和外部知识 |
Human Approval | 控制高风险动作 |
所以,Function Calling 是 Agent 的“动作接口”,但不是整个 Agent。
20. 总结
LLM 工具调用的底层机制可以压缩成一句话:
模型根据上下文和工具 schema 生成结构化 tool call;Runtime 校验、授权并执行真实函数;执行结果再作为 tool result 回到模型上下文,模型继续生成或继续调用工具。
成熟 Agent 的关键不在于“能不能调用函数”,而在于:
哪些工具暴露给模型schema 是否清晰参数是否严格校验工具结果是否高信号权限是否确定性执行高风险操作是否审批是否有超时、审计、截断、沙箱是否能防止工具循环和上下文污染
Claude / Claude Code、OpenCode、OpenClaw 的共同趋势都是:**LLM 只做语义决策,真正的执行、权限、安全、编排必须交给 deterministic runtime。**这也是从 Demo 走向生产级 Agent 系统的分水岭。
分享
