创见博客
从一次抓包看 Agent 如何调用工具
七崽爱吃小饼干2026/07/14阅读 0专栏 AI开发

从一次抓包看 Agent 如何调用工具

当我们和一个代码 Agent 对话时,经常会看到它先说一句“我先看一下项目结构”,然后很快读文件、搜索代码、执行命令。表面上看,这像是大模型自己拥有了操作本地环境的能力;但从一次真实请求抓包来看,真正发生的事情更像是一套清晰的协议:模型负责选择动作,Agent runtime 负责执行动作。

本文基于一次 OpenCode 到 LLM 服务的请求分析,拆解 Agent 是如何识别可用工具,以及 LLM 的回复如何驱动 Agent 执行工具调用的。

这次请求里发生了什么

用户的问题是:

text
介绍一下这个项目

这类问题看起来是一个普通问答,但对于代码 Agent 来说,正确做法不是凭空猜测,而是先查看项目的关键文件。抓包里可以看到,请求发送到了 LLM 的 responses 接口,请求体大致包含这些字段:

json
{
  "model": "gpt-5.5-2026-04-24",
  "input": [...],
  "tools": [...],
  "tool_choice": "auto",
  "stream": true
}

这里最关键的是 tools 和 tool_choice。

tools 是 OpenCode 在请求时提供给模型的工具清单。这次请求里一共有 68 个工具,包括 read、glob、grep、bash、apply_patch、浏览器工具、Figma 工具和一些平台相关工具。

tool_choice: "auto" 表示模型可以自行决定是否调用工具,以及调用哪个工具。

也就是说,模型并不是自己扫描本机环境后“发现”了工具。它知道有哪些工具,是因为 Agent runtime 在发起 LLM 请求时,把工具列表作为结构化 schema 一起传给了模型。

工具是如何暴露给模型的

每个工具在请求里都不是一段自然语言说明,而是一个 function schema。以 read 工具为例,它大致长这样:

json
{
  "type": "function",
  "name": "read",
  "description": "Read a file or directory from the local filesystem...",
  "parameters": {
    "type": "object",
    "properties": {
      "filePath": {
        "type": "string",
        "description": "The absolute path to the file or directory to read"
      },
      "offset": {
        "type": "integer"
      },
      "limit": {
        "type": "integer"
      }
    },
    "required": ["filePath"]
  }
}

模型因此知道三件事:

  1. 有一个工具叫 read。
  2. 这个工具用于读取本地文件或目录。
  3. 调用它时必须传 filePath,也可以传 offset 和 limit。

这和传统函数调用非常像。区别在于,函数本体并不在模型里,模型只看到函数签名和说明。真正的函数实现位于 Agent runtime 中。

LLM 返回的不是“命令文本”,而是结构化 function_call

这次响应里,模型先返回了一段面向用户的普通文本:

text
我先快速看一下仓库根目录和关键配置,再结合项目说明给你概览。

这属于 assistant message,是用户可以看到的自然语言内容。

随后,模型返回了多个独立的结构化输出项,类型是 function_call。例如:

json
{
  "type": "function_call",
  "name": "read",
  "call_id": "call_nS74UCeshMSaMeqjoMauECS2",
  "arguments": "{\"filePath\":\"/Users/bytedance/code/marketing_fe/package.json\",\"offset\":1,\"limit\":200}"
}

这里有几个重点:

  1. function_call 是 API 响应里的结构化 JSON 输出,不是普通文本内容。
  2. name 指定要调用哪个工具。
  3. arguments 是传给工具的参数。
  4. call_id 用于后续把工具执行结果和这次调用配对。

这次模型一共发起了 4 个 read 调用,分别读取:

text
/Users/bytedance/code/marketing_fe/package.json
/Users/bytedance/code/marketing_fe/pnpm-workspace.yaml
/Users/bytedance/code/marketing_fe/README.md
/Users/bytedance/code/marketing_fe

注意,这里模型没有执行 shell 命令。它没有返回 cat package.json,也没有调用 bash。它选择的是更适合读文件的 read 工具。

这个选择来自两部分信息:一是 read 工具的 description,二是 developer prompt 里的行为约束,例如“读文件优先使用 Read,不要用 bash 的 cat/head/tail”。

谁真正执行了工具

模型本身不会读本地文件。它只返回 function_call。

真正执行动作的是 OpenCode 的 Agent runtime。执行流程可以理解为:

text
用户输入
  -> OpenCode 组装 input + tools
  -> LLM 返回 message + function_call
  -> OpenCode 解析 function_call
  -> OpenCode 在本地执行 read 工具
  -> OpenCode 把结果作为 function_call_output 发回 LLM
  -> LLM 基于结果继续推理或生成最终回答

后续请求证明了这个闭环。OpenCode 把上一轮的工具调用和本地执行结果一起放进了下一次 LLM 请求:

json
{
  "type": "function_call_output",
  "call_id": "call_nS74UCeshMSaMeqjoMauECS2",
  "output": "<path>/Users/bytedance/code/marketing_fe/package.json</path>..."
}

这里的 call_id 和前面的 function_call.call_id 对应,用来告诉模型:这是哪一次工具调用的返回结果。

为什么这是 Agent 能力的关键

这次抓包说明了一个重要事实:Agent 的“会用工具”不是魔法,而是协议设计。

LLM 负责:

  1. 理解用户意图。
  2. 根据工具 schema 选择合适工具。
  3. 生成合法的工具参数。
  4. 在拿到工具结果后继续推理。

Agent runtime 负责:

  1. 把可用工具以 schema 形式提供给模型。
  2. 校验和解析模型返回的 function_call。
  3. 在真实环境里执行工具。
  4. 把执行结果包装成 function_call_output 继续回传。

这种分工让模型不需要直接拥有本地权限,也能通过受控接口完成文件读取、代码搜索、浏览器操作或命令执行。

function_call 与普通回复的区别

一个容易混淆的点是:function_call 到底算不算 LLM 回复?

答案是:算模型输出,但不是普通文本回复。

普通文本通常出现在 assistant message 中:

json
{
  "type": "message",
  "role": "assistant",
  "content": [
    {
      "type": "output_text",
      "text": "我先快速看一下仓库根目录和关键配置..."
    }
  ]
}

工具调用则是独立结构:

json
{
  "type": "function_call",
  "name": "read",
  "call_id": "...",
  "arguments": "..."
}

所以更准确的说法是:function_call 是 LLM API 响应里的结构化输出项,不是展示给用户的自然语言内容。

小结

这次抓包展示了一条完整的 Agent 工具调用链路:

  1. OpenCode 在请求里声明 68 个可用工具。
  2. LLM 通过 tools schema 知道有哪些工具以及参数格式。
  3. 用户要求介绍项目时,LLM 判断需要先读文件。
  4. LLM 返回 4 个 read 的 function_call。
  5. OpenCode 本地执行这些 read 调用。
  6. OpenCode 把结果作为 function_call_output 发回模型。
  7. 模型再基于真实文件内容生成项目介绍。

这就是现代代码 Agent 的基本工作方式:模型不直接操作环境,而是通过结构化工具调用协议,把“想做什么”交给 runtime 执行。工具清单、参数 schema、调用 ID 和执行结果回传,共同构成了 Agent 能可靠工作的基础。

评论
0/100