07Tool Use 协议

L2P0五步章 · 技术章
Why · 为什么要学
你给 agent 加了 10 个 tool,结果模型频繁出错——参数填错、tool 选错、连续调一个 read tool 100 次。问题不是模型笨,是 tool 设计有缺陷。Tool Use 协议是 harness 工程最高频接触的接口——你写出来的每一个 tool 都是模型的"动作选项"。这一节是写出"模型会用对"工具的物理基础。
1 ·核心要点

Tool Use 协议涉及三个对象:

1. Tool 定义(你写):{name, description, input_schema}

· name:动词+名词,namespace 加前缀(fs_read_file, git_commit)

· description:模型决定"何时调"的唯一依据——必须写清用途/适用场景/返回什么

· input_schema:JSON Schema,type / enum / format / required 严格定义

2. Tool 调用(模型生成):

{type: "tool_use", id: "toolu_abc", name: "read_file",
 input: {path: "/tmp/x.txt"}}

3. Tool 结果(harness 提供):

// 成功
{type: "tool_result", tool_use_id: "toolu_abc",
 content: "file contents..."}

// 失败 — is_error 让模型知道并自纠
{type: "tool_result", tool_use_id: "toolu_abc",
 content: "Error: ENOENT /tmp/x.txt", is_error: true}

并行调用:一个 assistant response 可含 N 个 tool_use blocks(模型决定)。harness 通常并发执行,所有 results 按 tool_use_id 配对返回,不依赖顺序。

tool_choice 控制调用策略:

tool_choice 行为 场景
{"type":"auto"}模型自由选默认,99% 用
{"type":"any"}必须调,模型选哪个明确要工具(检索 agent)
{"type":"tool","name":"X"}强制调指定 toolstructured output 用
{"type":"none"}不能调任何 tool纯回答模式

关键抽象:模型决定"做什么、调什么、参数多少",harness 决定"真的执行、如何执行、能不能执行"。这两层职责分离是 agent 设计的灵魂。

2 ·最小代码示例
tools = [{
    "name": "read_file",
    "description": "Read text contents of a file from disk. "
                   "Use when user asks about file contents or you need to "
                   "inspect code/config. Returns the file content as string.",
    "input_schema": {
        "type": "object",
        "properties": {
            "path": {"type": "string", "description": "Absolute file path"}
        },
        "required": ["path"]
    }
}]

resp = client.messages.create(
    model="claude-sonnet-4-5", max_tokens=1024,
    tools=tools,
    messages=[{"role": "user", "content": "What's in /etc/hosts?"}]
)

# resp.content 包含一个 tool_use block:
# [{"type":"tool_use", "id":"toolu_abc", "name":"read_file",
#   "input":{"path":"/etc/hosts"}}]

# 执行 + 把 result 塞回继续对话:
tool_block = next(b for b in resp.content if b.type == "tool_use")
content = open(tool_block.input["path"]).read()

next_resp = client.messages.create(
    model="claude-sonnet-4-5", max_tokens=1024, tools=tools,
    messages=[
        {"role": "user", "content": "What's in /etc/hosts?"},
        {"role": "assistant", "content": resp.content},
        {"role": "user", "content": [
            {"type": "tool_result", "tool_use_id": tool_block.id, "content": content}
        ]}
    ]
)
3 ·工程权衡

何时用 tool use

  1. 需要访问外部世界——文件、API、DB、shell、网页
  2. 多步任务有分支决策——模型选下一步做什么
  3. 需要 structured output——用 tool_choice: {type: "tool", name: "submit_answer"} 强制按 schema 输出