# LLM 工具调用确认机制整理

## 文档目的

整理本次会话中关于“LLM 如何与 Agent/工具系统确认工具调用”的核心内容，形成一份可复用的知识文档。

一句话结论：

> LLM 不会直接执行工具，它只会根据 Agent 提供的工具定义输出结构化调用请求；真正的确认发生在 Agent 对该请求进行校验、执行，并将结果回填给 LLM 的闭环中。

---

## 1. 参与者与职责

一个完整的工具调用链路里，通常有 4 个角色：

### 1.1 User（用户）
负责提出任务或问题，例如：
- 现在几点了？
- 读取某个文件内容
- 帮我检查目录下有哪些 Python 文件

### 1.2 LLM（大模型）
负责：
- 理解用户意图
- 判断是否需要工具
- 选择调用哪个工具
- 生成结构化 tool call
- 基于工具返回结果继续推理或输出最终答案

LLM 本身不直接操作系统、文件、浏览器或网络。

### 1.3 Agent（代理框架）
例如 Hermes 这类 Agent 框架，负责：
- 管理对话历史
- 将可用工具及其 schema 暴露给 LLM
- 接收并解析 LLM 的 tool call
- 校验工具调用是否合法
- 调度具体工具执行
- 将工具执行结果回填给 LLM

### 1.4 Tool（工具执行层）
负责真正执行动作，例如：
- terminal：执行 shell 命令
- read_file：读取文件
- search_files：搜索文件或内容
- browser：浏览器自动化
- web：联网检索

---

## 2. 工具调用确认的本质

“确认工具调用”不是一句自然语言上的“我来查一下”，而是一个结构化闭环：

1. Agent 将工具定义（名称、描述、参数 schema）发给 LLM
2. LLM 判断是否需要调用工具
3. LLM 返回结构化 tool call
4. Agent 校验这次调用是否合法
5. Agent 真实执行工具
6. Agent 将执行结果作为 tool message 回填给 LLM
7. LLM 基于结果决定：继续调用、重试修正、还是输出最终答案

可以压缩成一句话：

> LLM 负责“决定调用什么”，Agent 负责“确认能不能调、真的去执行、并把结果带回来”。

---

## 3. 工具调用前：Agent 如何把工具介绍给 LLM

在调用模型时，Agent 不只发送普通文本消息，还会附带工具定义。每个工具通常包含：

- 工具名，例如 `terminal`
- 工具描述，例如“运行 shell 命令”
- 参数 schema，例如 `command` 必须是字符串
- 调用约束，例如某些浏览器操作需要先 navigate 再 click

这一步的作用是告诉 LLM：

- 你现在有哪些外部能力可以用
- 每个能力应该以什么格式调用
- 哪些字段是必填
- 哪些值是合法的

这相当于把一份“可调用 API 文档”注入到模型上下文中。

---

## 4. LLM 如何决定要不要调用工具

当 LLM 同时看到：
- 系统规则
- 用户问题
- 工具定义

它会判断：

1. 这个问题能否直接回答？
2. 是否必须使用工具才能保证正确性？
3. 当前是否存在合适的工具？
4. 是否需要先获取外部信息再回答？

例如：
- “现在几点了？”属于实时信息，应该使用工具
- “Transformer 的 self-attention 是什么？”如果不要求查资料，通常可以直接回答

因此，是否调用工具，本质上是一次策略决策。

---

## 5. LLM 返回的不是自然语言，而是结构化 tool call

当 LLM 决定调用工具时，它不会只说“我去查一下”，而是返回一个结构化请求，典型形式如下：

```json
{
  "tool_calls": [
    {
      "id": "call_001",
      "type": "function",
      "function": {
        "name": "terminal",
        "arguments": "{\"command\": \"date\"}"
      }
    }
  ]
}
```

这表示：
- 选择了哪个工具：`terminal`
- 要传什么参数：`command = date`
- 用 `id` 追踪本次调用的结果

这一步并不是“工具已经执行成功”，而是“LLM 明确提交了一次调用请求”。

---

## 6. Agent 如何确认这次工具调用是否合法

LLM 发起调用后，Agent 不会无条件执行，通常还要经过校验。

### 6.1 工具存在性校验
检查 LLM 请求的工具是否真的已注册、已启用。

### 6.2 参数 schema 校验
检查：
- 参数类型是否正确
- 是否缺少必填字段
- 枚举值是否在允许范围内

### 6.3 上下文状态校验
例如浏览器工具可能要求：
- 先 navigate
- 再 snapshot
- 再 click

如果状态不满足，调用不能直接执行。

### 6.4 安全策略校验
例如：
- 高风险 shell 命令是否需要批准
- 是否访问了受限制资源
- 是否符合沙箱/权限策略

### 6.5 工具可用性校验
例如：
- API key 是否存在
- 浏览器会话是否可用
- 依赖命令是否安装

如果校验失败，Agent 可能：
- 直接拒绝本次调用
- 或将错误回填给 LLM，让其修正后重试

---

## 7. 工具真正执行时发生了什么

当校验通过，Agent 会把 tool call 路由到对应工具实现。

例如：
- `terminal(command="date")`
- `read_file(path="/tmp/a.txt")`
- `search_files(pattern="*.py", path=".")`

工具执行后，会返回一个结构化结果，例如：

```json
{
  "output": "Fri Apr 24 01:12:30 UTC 2026",
  "exit_code": 0
}
```

或者失败时：

```json
{
  "error": "File not found: /tmp/a.txt",
  "exit_code": 1
}
```

注意：
此时真正“拿到结果”的是 Agent，不是 LLM。

---

## 8. Agent 如何把结果回填给 LLM

Agent 会将工具结果包装为一条 `tool` 消息，并写回对话历史。例如：

```json
{
  "role": "tool",
  "tool_call_id": "call_001",
  "content": "{\"output\":\"Fri Apr 24 01:12:30 UTC 2026\",\"exit_code\":0}"
}
```

这一步至关重要，因为：
- LLM 不能直接看到真实世界
- 它只能通过 Agent 回填的结构化结果了解“刚才执行了什么”

也就是说，LLM 对工具状态的认知完全依赖回填消息。

---

## 9. LLM 拿到结果后如何继续决策

当 LLM 看到工具结果后，会重新判断：

1. 结果是否足够回答用户？
2. 是否需要继续调用其他工具？
3. 是否失败了，需要修正参数或改用别的工具？
4. 是否需要向用户追问更多信息？

常见三种情况：

### 9.1 一次调用后直接回答
例如：
- 问当前时间
- 调用 `date`
- 得到结果后直接回答

### 9.2 串联多个工具
例如：
- 先搜索 Python 文件
- 再计算文件大小排序
- 再读取最大文件内容

### 9.3 调用失败后重试或换策略
例如：
- 文件不存在
- 参数格式错误
- 命令超时

模型会基于错误结果继续修正。

---

## 10. 三个最重要的“确认点”

可以把整个机制压缩为三个关键确认点：

### 确认点 A：LLM 的调用意图确认
由 LLM 完成。

确认内容：
- 要不要调用工具
- 调哪个工具
- 参数是什么

表现形式：
- 返回结构化 `tool_call`

### 确认点 B：Agent 的合法性确认
由 Agent 完成。

确认内容：
- 工具是否存在
- 参数是否合法
- 当前状态是否允许
- 安全策略是否通过

表现形式：
- 通过校验才执行，否则拒绝或报错

### 确认点 C：执行结果确认
由 Tool + Agent + LLM 共同完成。

确认内容：
- 工具是否真的执行成功
- 返回了什么结果
- 是否还需要下一步调用

表现形式：
- tool result 回填给 LLM，LLM 再决定下一步

---

## 11. 一张简化时序图

场景：用户问“现在几点了？”

```text
User                Agent                 LLM                  Tool
 |                    |                    |                     |
 | 1.提问             |                    |                     |
 |------------------->|                    |                     |
 |                    | 2.发上下文+工具定义给 LLM                |
 |                    |------------------->|                     |
 |                    |                    | 3.判断需要工具      |
 |                    |                    | 4.输出 tool_call    |
 |                    |<-------------------|                     |
 |                    | 5.校验调用是否合法                         |
 |                    | 6.执行工具                                |
 |                    |----------------------------------------->|
 |                    | 7.得到工具结果                            |
 |                    |<-----------------------------------------|
 |                    | 8.将结果回填给 LLM                        |
 |                    |------------------->|                     |
 |                    |                    | 9.生成最终答案      |
 |                    |<-------------------|                     |
 |10.返回给用户       |                    |                     |
 |<-------------------|                    |                     |
```

---

## 12. 失败场景下的闭环

如果工具失败，Agent 一般不会静默吞掉错误，而是把失败结果回填给 LLM。例如：

```json
{
  "error": "File not found: /abc/def.txt",
  "exit_code": 1
}
```

LLM 看到失败结果后，可能：
- 修正路径
- 改用别的工具
- 先搜索文件再读取
- 或在缺少信息时向用户询问

所以失败不是终点，而是下一轮决策的输入。

成熟 Agent 的一个关键能力，就是失败后还能基于结果自我修正。

---

## 13. 多工具串联场景示例

任务：
“帮我找出项目里最大的 3 个 Python 文件，并读取最大的那个前 50 行。”

可能的执行链路：

1. `search_files`：搜索所有 `.py` 文件
2. `terminal` 或 `execute_code`：统计并排序文件大小
3. `read_file`：读取最大文件前 50 行
4. LLM 基于这些结果给出总结

这个场景说明：
- 每一步 tool result 都会影响下一步 tool call
- 工具调用确认不是单点，而是连续多轮的状态推进

---

## 14. 为什么说 LLM 并不知道工具是否真的执行成功

LLM 自己只能做两件事：

1. 发起调用请求
2. 阅读回填结果

它不能直接知道：
- 子进程是否真运行了
- 文件是否真存在
- 浏览器是否真点到了按钮
- API 是否返回了 200

这些都属于 Agent 和 Tool 所接触的真实世界。

因此：

> LLM 对工具执行状态的了解，完全依赖 Agent 返回给它的结构化结果。

---

## 15. 从代码视角看最小闭环

最简思路如下：

```python
while True:
    response = llm(messages=messages, tools=tools)

    if response.tool_calls:
        messages.append(response.assistant_message)

        for call in response.tool_calls:
            result = execute(call)
            messages.append(tool_result(call.id, result))

    else:
        return response.text
```

这段代码表达的就是：

1. 先问 LLM 下一步怎么做
2. 如果 LLM 请求工具，就去执行
3. 把执行结果塞回对话上下文
4. 再次调用 LLM
5. 直到不再需要工具，输出最终答案

---

## 16. 更接近 Hermes/Agent 框架的抽象伪代码

```python
def run_conversation(user_input):
    messages = build_initial_messages(user_input)
    tools = discover_available_tools()

    for _ in range(max_iterations):
        response = call_llm(messages, tools)

        if response.tool_calls:
            messages.append(response.assistant_message)

            for tool_call in response.tool_calls:
                validation_error = validate_tool_call(tool_call, tools)
                if validation_error:
                    tool_result = {
                        "success": False,
                        "error": validation_error
                    }
                else:
                    tool_result = dispatch_tool(tool_call)

                messages.append(make_tool_message(tool_call.id, tool_result))

            continue

        return response.text

    return "任务未能在最大迭代次数内完成。"
```

这里多了两个关键动作：

- `validate_tool_call(...)`：合法性确认
- `dispatch_tool(...)`：分发到具体工具实现

这正是成熟 Agent 框架的核心逻辑。

---

## 17. 可以把它理解成一个状态机

工具调用过程通常会在以下状态之间循环：

1. `WaitingForUser`
2. `Reasoning`
3. `ToolRequested`
4. `ToolValidated`
5. `ToolRunning`
6. `ToolReturned`
7. `ReasoningAgain`
8. `FinalAnswer`

如果失败，还可能进入：
- `Retry`
- `AskUser`
- `Abort`

这说明工具调用不是一次性动作，而是一个受控的状态机循环。

---

## 18. 一个工程类比

可以把整个机制理解成：

- LLM = 项目经理
- Agent = 调度员 + 审核员
- Tool = 执行员工
- Tool result = 回执单

链路如下：

1. 项目经理提出工单（tool call）
2. 调度员审核是否合法（validate）
3. 执行员工去干活（execute）
4. 回执单返回（tool result）
5. 项目经理根据回执决定下一步（继续调度或给用户结论）

这个类比很适合理解“LLM 为什么不直接接触真实世界”。

---

## 19. 最终总结

如果只记一条主线，可以记这 8 步：

1. Agent 把工具定义发给 LLM
2. LLM 阅读用户需求和工具 schema
3. LLM 选择某个工具
4. LLM 输出结构化 tool call
5. Agent 校验调用是否合法
6. Agent 执行工具
7. Agent 把结果回填给 LLM
8. LLM 基于结果决定继续调用还是最终回答

一句话版：

> 工具调用的确认，不是“模型说一句要调用工具”，而是“模型提出结构化调用请求，Agent 校验并执行，再把真实结果回传给模型”的闭环。

---

## 20. 适合后续继续扩展的方向

如果后续继续深入，可以在这份文档基础上扩展：

1. 真实 JSON 请求/响应样例
2. OpenAI 风格 vs 其他函数调用协议差异
3. Hermes 源码中的 tool registry / dispatch 结构
4. 浏览器工具的状态依赖与前置条件管理
5. 高风险工具调用中的审批与安全策略
6. 多工具并行调用、串行依赖与错误恢复模式

---

## 附：一句最短记忆版

```python
while True:
    resp = llm(messages, tools)
    if resp.tool_calls:
        execute_tools_and_append_results()
    else:
        return resp.text
```

核心思想只有一句：

> LLM 负责决定，Agent 负责确认与执行，工具结果再反哺 LLM 完成闭环。
