# 工具调用 · Function Calling 完全指南 · Agent 实战 · 从零构建完备智能体

**作者** Aklman · **章节** 03 / 19 · **首发** 2026.07 · **语言** 中文为主, 中英双语
**原文** https://library.aklman.com/books/build-agent/03-tool-use
**全书 markdown** https://library.aklman.com/books/build-agent/llms.md
**上一章** https://library.aklman.com/books/build-agent/02-minimal-loop/llms.md
**下一章** https://library.aklman.com/books/build-agent/04-memory/llms.md

> 本章来自 Aklman · Library, 完整免费阅读。允许 AI 摘读、引用、问答; 转载请保留作者署名与原文链接 (CC BY-NC-ND 4.0, https://library.aklman.com/license)。

---

> 工具是 Agent 的手脚 —— JSON Schema 定义能力边界，错误处理决定鲁棒性，并行调用决定速度。

研究助手现在只会搜索；这一章给它加第二个工具 —— fetch_page，把搜到的网页抓回来读。借它把工具的三件事讲透：schema 怎么定义能力、错误怎么回传决定鲁棒性、并行怎么调决定速度。模型够不到你的函数实现，只看得到你写的 schema 和描述。

### I · 工具的定义就是它的契约

模型不读你的函数实现，只读你的 schema 和描述。这份契约写得好不好，直接决定它调得对不对。

一个工具就三块：name、description、入参 schema。description 不是文档，是 prompt —— 模型靠它决定「什么时候、用什么参数」调这个工具。[^1]模型能只凭描述和少量示例就学会怎么调，靠的是 in-context learning —— GPT-3 那篇早已证明，大模型「无需梯度更新或微调」、仅凭提示里的示例就能改变行为。[^4]同一个工具，两家的写法几乎一样：

#### Anthropic

```python
{
 "name": "fetch_page",
 "description": "抓取一个 URL 的正文内容。要读 web_search 找到的网页时调用。",
 "input_schema": {
 "type": "object",
 "properties": {
 "url": {"type": "string", "description": "要抓取的页面 URL"},
 },
 "required": ["url"],
 },
}
```

#### OpenAI

```python
{
 "type": "function",
 "function": {
 "name": "fetch_page",
 "description": "抓取一个 URL 的正文内容。要读 web_search 找到的网页时调用。",
 "parameters": {
 "type": "object",
 "properties": {
 "url": {"type": "string", "description": "要抓取的页面 URL"},
 },
 "required": ["url"],
 },
 "strict": True,
 },
}
```

两边都支持 `strict` —— 强制模型的调用严格符合 schema，省掉一整类「参数缺失 / 类型不对」的脏活。[^1][^2]能开就开。

反过来，过度暴露能力是这一章最该警惕的反模式：一个 `run_sql(query)` 工具，等于把整个数据库的权限交给了模型。工具的边界就是权限的边界 —— schema 越窄越安全。[^3]这条线第 9 章还会再画一次。

---

### II · 错误处理决定鲁棒性

工具一定会失败 —— fetch_page 尤其会：404、超时、抓回一页渲染不出的 JS。agent 的鲁棒性不在 happy path，在你怎么把失败回传给模型。

关键一招：别让工具的异常炸掉整个循环，把错误当成一个普通的 `tool_result` 传回去（带 `is_error`）。模型看到错误，就能改参数重试，或者体面地告诉用户做不到。[^1]

_把失败当结果回传，而不是抛异常_
```text

```python
def call_tool(block):
 try:
 out = run_tool(block.name, block.input)
 return {"type": "tool_result", "tool_use_id": block.id, "content": out}
 except Exception as e:
 return { # 错误也是一种 observation
 "type": "tool_result", "tool_use_id": block.id,
 "content": f"error: {e}", "is_error": True,
 }
```

```

但别让它无限重试。给每个工具一个重试上限，撞到上限还失败，就把失败如实交给用户。盲目重试既烧钱又卡死 —— 这笔账第 11 章细算。

---

### III · 并行调用决定速度

研究助手常常想一次搜好几个角度、或一次抓好几个页面 —— 模型在一轮里给出多个工具调用。串行执行，是在浪费它的并行意图。

一个 assistant 轮次里可以有多个 `tool_use` 块。把它们并发执行，再把所有 `tool_result` 放进同一个 user 轮次回传 —— 每个结果带对自己的 `tool_use_id` 就行，顺序无所谓。[^1]

_并发执行一轮里的多个工具调用_
```text

```python
import asyncio

async def run_calls(blocks):
 async def one(b):
 out = await run_tool_async(b.name, b.input)
 return {"type": "tool_result", "tool_use_id": b.id, "content": out}
 return await asyncio.gather(*[one(b) for b in blocks]) # 一起跑，不排队
```

```

但并行有前提：工具之间无依赖，且都能安全并发。读操作放心并行；有副作用的工具（写文件、下单、转账）要想清楚 —— 并发的副作用是最难复现的 bug。

动手 · 给你第二章那个工具做三件事：

- 把它的 description 当 prompt 重写一遍，测模型调得准不准 —— 改前改后各跑 5 次，数调对的次数。

- 把它从「抛异常」改成「返回带 `is_error` 的结果」，看模型遇错会不会自己改参数重试。

- 如果你有两个以上互不依赖的工具，把它们改成并发执行，量一下一轮快了多少。

> 工具的 schema 就是它的权限，
> 描述就是它的说明书。

## 引用与参考

01 · Anthropic 文档 · Tool use —— 工具由 name / description / input_schema（JSON Schema）定义；strict: true 保证调用严格符合 schema；tool_choice 可选 auto / any / tool / none；tool_result 可带 is_error 把失败回传给模型。截至 2026-05。  (Anthropic · Tool use)
02 · OpenAI 文档 · Function calling —— 工具用 {type: function, function: {name, description, parameters, strict}} 定义；响应在 message.tool_calls，结果以 {role: tool, tool_call_id, content} 回传。形态稳定，作对照。  (OpenAI · Function calling)
03 · JSON Schema —— 工具入参 schema 的规范来源（type / properties / required / enum 等）。模型读的是这份契约，不是你的函数实现。  (JSON Schema · Understanding)
04 · Brown et al. · 「Language Models are Few-Shot Learners」（GPT-3，2020，arXiv:2005.14165）—— in-context / few-shot learning：大模型「无需任何梯度更新或微调，仅凭文本里的少量示例」就能执行新任务。这是工具描述与示例为何能改变模型行为的根。  (arXiv · GPT-3 / Few-Shot Learners (2005.14165))

---

*工具调用 · Function Calling 完全指南 · Agent 实战 · 从零构建完备智能体 · Aklman 著 · CC BY-NC-ND 4.0 · https://library.aklman.com/books/build-agent/03-tool-use*
