LLM 本身只会生成文本,读文件、跑命令、查数据库都要靠工具(Tool)。理解工具机制是理解 Agent 的地基:一个能干活的 Agent = 会决策的 LLM + 提供外部能力的 Tool + 把两者接起来的 Agent Harness。这篇按"定义—实现—调度"三层拆开讲完整调用链路。
三层结构各管一段
| 层 | 职责 |
|---|---|
| 工具定义(Definition) | 告诉模型工具能做什么、怎么传参 |
| 工具实现(Implementation) | 真正执行操作的代码 |
| 调度层(Agent Harness) | 接收调用请求、找到实现、执行并把结果回传 |
最容易混淆的是定义与实现:模型只看得到定义,永远接触不到实现。定义是"说明书",实现是"机器"。
定义层:name、description、input_schema
以一个读文件工具为例:
{
"name": "read_file",
"description": "Read the contents of a file.",
"input_schema": {
"type": "object",
"properties": {
"file_path": {
"type": "string",
"description": "Path of the file to read"
}
},
"required": ["file_path"]
}
}
三个字段的分工:name 唯一标识工具;description 是模型决定是否调用的主要依据;input_schema 用 JSON Schema 风格约束参数结构——object 类型、properties 列出每个参数的类型与用途、required 标明必填。模型据此生成形如 {"file_path": "main.py"} 的调用参数。description 的质量直接影响调用准确率,含糊的描述会让模型在相近工具之间乱选。
实现层:任何能执行操作的代码
实现不一定非得是 Python 函数,HTTP API、数据库查询、远程服务都算。本地函数版本:
def read_file(file_path: str) -> str:
with open(file_path, "r", encoding="utf-8") as f:
return f.read()
模型与实现之间靠注册表(Tool Registry)连接:
TOOL_REGISTRY = {"read_file": read_file}
tool_name = "read_file"
tool_args = {"file_path": "main.py"}
tool_func = TOOL_REGISTRY[tool_name]
result = tool_func(**tool_args)
调度层:四步主链路
完整的一次 Tool Calling 分四步,顺序不能乱:
- 定义下发:Harness 把工具定义通过模型 API 的
tools参数提供给模型(不是拼进普通聊天消息); - 模型发起:LLM 根据任务生成结构化调用请求——注意它只是"发起请求",并不亲自执行任何函数;
- Harness 执行:按工具名查注册表找到实现,先做参数校验和权限检查,再执行;
- 结果回传:执行结果作为工具消息返回给模型,模型继续推理或发起下一次调用,直到完成任务。
第 3 步是安全边界所在,直接信任模型生成的参数是重大隐患:LLM 可能拼出越权路径(file_path: "/etc/passwd" 或带 ../ 穿越)、超长字符串、类型错误的值。生产实现至少要做三件事——按 input_schema 校验参数结构;对文件路径类参数做白名单目录限制和规范化检查;校验失败时把结构化错误信息返回给模型,让它修正参数重试,而不是抛异常中断整个任务。
Tool、Function Calling、MCP 的关系
概念纠缠是学习路上的常见卡点,用一句话切开:Tool 是能力本身,Definition 描述它,Implementation 执行它;Function Calling 是让 LLM 生成结构化调用请求的机制;MCP 是把外部工具接入 AI 应用的标准化协议;Harness 负责全程调度。
本地 Python 函数走注册表直调;工具由外部 MCP Server 提供时,Agent 通过 MCP Client 与服务器通信、由服务端执行。接入方式不同,但对模型来说都是"看定义、发请求、收结果"——理解了这条主链路,再往上叠参数校验、权限控制、超时重试、调用日志(记录 tool_call_id 做链路追踪),就是一套完整的 Agent 工具系统。

评论0