如果说大模型(LLM/VLM)是 Agent 的大脑,那么工具(Tools) 就是它的手、脚、眼睛和耳朵。没有工具,模型只是一个被困在 GPU 显存里的、知识渊博但行动瘫痪的哲学家。
Tool Call(工具调用),在某些框架中也被称为 Function Calling,是 Agent 跨越“语义世界”与“物理/数字世界”鸿沟的唯一桥梁。它不仅仅是简单的 API 调用,而是一套完整的协议:模型根据上下文判断意图,生成符合规范的结构化指令,宿主系统执行指令,并将结果(无论是文本、图片还错误堆栈)回传给模型,从而形成闭环。
一个标准的 Tool Call 并不是“模型直接运行代码”,而是“模型生成文本 -> 系统解析并运行 -> 系统注入结果”的三明治结构。
ASCII 流程图:工具调用的生命周期
[Context / History]
|
+--------------v--------------+
| LLM / VLM (Reasoning) | <--- "这个用户想查库存,我需要调用 inventory_tool"
+--------------+--------------+
|
(Output: Structured Intent)
v
+--------------+--------------+
| Orchestrator (System) | <--- 1. 拦截模型输出
| - Parse & Validate JSON | <--- 2. 校验参数类型 (Schema Validation)
| - Check Permissions | <--- 3. 权限风控
+--------------+--------------+
|
(Execute: API / DB / Code)
v
+--------------+--------------+
| Real World / Environment | <--- 执行动作 (副作用发生处)
+--------------+--------------+
|
(Result: "Count: 42")
v
+--------------+--------------+
| Orchestrator (System) | <--- 4. 格式化结果 (截断、清洗)
| - Format as ToolMessage |
+--------------+--------------+
|
(Input: Append to History)
v
+--------------v--------------+
| LLM / VLM (Synthesis) | <--- "根据库存数据,我们可以..."
+-----------------------------+
在 Agent 开发中,接口定义(Function Signature)就是 Prompt 的一部分。模型读不懂 Python 代码逻辑,它读的是你提供的 JSON Schema 描述。
一个高质量的 Schema 包含三个维度:
search_web: 搜索网络search_web: 当用户询问 2023 年之后发生的实时事件、新闻或具体事实(如股价、天气)时使用。返回结果包含标题、URL 和页面摘要。不要用于回答通用常识。enum(枚举)和 pattern(正则)来物理限制模型的发挥空间。
enum 的地方绝不让模型填空。能用 number 的地方绝不让模型填 string。随着任务复杂度提升,单一的工具调用不再够用。我们需要设计不同的编排模式。
这是最自然的“思考-行动-观察”循环。
Step 1 (Find ID) -> Step 2 (Get Details) -> Step 3 (Process)现代模型(如 GPT-4, Claude 3.5)支持一次输出多个工具调用。
* **工程实现**:宿主程序使 `asyncio` 或线程池并发执行这两个请求,收集所有结果后,拼接成一个包含两个 ToolMessage 的列表返回给模型。
* **收益**:将 `N` 次网络延迟压缩为 `1` 次。
### 3.3.3 异步与长任务 (Async / Long-running)
有些工具执行很慢(如“训练一个模型”或“深度扫描 PDF”),不能让 HTTP 连接挂起等待。
* **模式**:**提交-轮询(Submit-Poll)** 或 **回调(Callback)**。
* **工具设计**:
1. `start_task(...)` -> 返回 `task_id`,立即结束本次 Tool Call。
2. `check_status(task_id)` -> 模型在数轮对话后,或者通过系统定时的“唤醒机制”去查询状态。
* **Rule of Thumb**:对于超过 30 秒的任务,必须设计成异步模式,否则 HTTP 超时会导致模型以为任务失败并触发无限重试。
---
## 3.4 多模态工具设计 (Multimodal Tools)
在多模态 Agent 中,工具的输入输出不再局限于文本。
### 3.4.1 图像作为输入 (Image-in)
当模型要“看”图并操作时(例如:`crop_image(bbox)` 或 `verify_signature(image)`)。
* **引用传递 (Pass by Reference)**:**永远不要**把 Base64 编码的图片在 Prompt 和工具参数间来回传递。这会瞬间耗尽 Token 预算并增加延迟。
* **最佳实践**:
* 模型上下文中:图片以 `image_id` 或 `url` 的形式存在。
* 工具调用时:模型输出 `{"tool": "crop", "args": {"image_id": "img_123", "bbox": [0,0,100,100]}}`。
* 执行层:根据 `image_id` 从内存/缓存中提取实际像素数据传给 OpenCV/PIL。
### 3.4.2 图像作为输出 (Image-out)
当工具生成了图表、渲染了 PDF 或修改了图片。
* **视觉占位符**:工具返回结果不应是二进制流,而是一个描述性结构:
```json
{
"status": "success",
"generated_image_id": "img_new_456",
"description": "A line chart showing revenue growth...",
"preview_url": "https://..."
}
image_id,自动将其渲染到用户的 UI 上,同时将该图片作为新的多模态消息注入到模型的下一轮对话历史中,让模型“看到”自己的工作成果。模型生成的参数经常会出错(格式错误、幻觉参数)。
ValueError: 'aple' is not a valid stock symbol)。ToolMessage (Role: Tool) 返回给模型。{"symbol": "AAPL"}。工具(如 cat file.txt)可能返回 10MB 的文本。
...[content truncated, total length 50000 chars]。File saved to context memory (id: doc_1). Use 'read_chunk(doc_1, start_line)' to read details.idempotency_key 参数,或在系统层通过 trace_id 进行去重拦截。| 陷阱 (Gotcha) | 典型症状 | 解决方案 (Rule of Thumb) |
|---|---|---|
| 工具争抢 (Tool Confusion) | 有两个相似工具(如 search_google 和 search_wiki),模型随机乱用或犹豫不决。 |
合并同类项。做一个通用的 search(query, source="auto"),让代码内部去路由,而不是让模型纠结。或者在 description 中明确界定边界。 |
| 参数依赖丢失 | 模型想调用 summarize(text),但直接把上一轮对话里 5000 字的内容塞进参数,导致输入 Token 爆炸。 |
强制引用。Schema 中要求 document_id 而不是 content。强迫模型先调用 upload 或从上下文引用 ID。 |
| 无限循环 (The Loop of Death) | 模型调用工具 -> 报错 -> 重试 -> 报错 -> 重试… 耗尽额度。 | 熔断机制。系统层记录连续错误次数。如果同一个工具连续失败 3 次,强制插入 System Prompt:“该工具已损坏,请停止尝试并告知用户失败原因”。 |
| JSON 格式灾难 | 模型输出的 JSON 包含单引号、未转义的换行符,或者 Python 风格的 True (而不是 true)。 |
1. 使用容错性强的解析库(如 json5 或 dirty-json)。 |
2. 在 System Prompt 中强调“Strict JSON”。
3. 使用 Pydantic 的 Validator 做清洗。 |
|||
| 幽灵工具调用 | 模型在没注册任何工具的情况下,依然幻觉出 <tool_code>...。 |
这是训练数据的残留。在 Prompt 中明确“如果不需要使用工具,直接回答”。 |