跳到主要内容
SUN / 笔记
← 全部文章

智能体工具调用原理

本文解释 Model、Harness 与 Tool 的职责边界,以及一次工具调用从模型决策到真实执行的完整链路。

一句话理解工具调用

大模型本身不能直接访问文件系统、终端、数据库和外部 API。它只能根据上下文生成结构化的工具调用请求;Harness 负责注册工具、选择本轮可见工具、校验调用、执行真实代码、回填结果并控制下一轮循环。

text
用户任务
   ↓
Harness 组装上下文和候选工具
   ↓
模型输出最终答案或结构化 Tool Call
   ↓
Harness 校验权限、参数和风险
   ↓
执行本地函数、Shell、API 或 MCP Tool
   ↓
将 Tool Result 回填给模型
   ↓
模型根据新 Observation 继续决策

可以概括成:

模型负责语义决策,Harness 负责能力边界、真实执行和运行治理。


如何理解“Model + Harness = Agent”

模型提供:

  • 对用户目标和环境反馈的语义理解;
  • 规划与下一步行动判断;
  • 工具选择和参数生成;
  • 根据工具结果调整方案;
  • 判断是否可以形成最终回答。

Harness 提供:

  • 系统指令和任务上下文组装;
  • Tool Registry 与 Tool Schema;
  • Agent Loop;
  • 工具分发和真实执行;
  • 文件系统、Shell、浏览器、数据库和 MCP 接入;
  • 权限、沙箱、审批和 Guardrail;
  • 状态、记忆、Checkpoint 和上下文压缩;
  • 重试、超时、预算、循环检测与终止;
  • 日志、Tracing、评测和可观测性。

因此只写 Prompt、调用一次模型 API,通常只是一次 LLM 调用;只有把模型放进一个能够观察环境、调用工具、更新状态并持续运行的执行系统中,才形成完整 Agent。

OpenAI 对 Codex Agent Loop 的说明将 Harness 描述为连接用户、模型和工具的核心执行循环。


Harness 如何决定下一步操作

它既不是纯外部代码决定,也不是纯 Prompt 决定,而是混合控制。

Harness 决定硬边界

外部代码负责确定:

  • 当前注册了哪些工具;
  • 本轮允许暴露哪些工具;
  • 工具参数和返回值 Schema;
  • 哪些目录、网络和账号可以访问;
  • 哪些操作必须人工审批;
  • 最大轮数、工具调用数、运行时间、Token 和费用;
  • 什么情况必须暂停或终止。

模型决定语义动作

模型根据以下信息判断下一步:

  • 用户原始目标;
  • 系统约束和验收标准;
  • 当前任务计划与进度;
  • 候选工具的名称、描述和参数 Schema;
  • 最近的工具调用和执行结果;
  • 当前文件、测试错误和环境状态。

例如,Harness 不会预先写死:

text
如果出现登录 Bug,就读取 LoginService.java。

而是模型看到搜索结果后,语义判断最相关的文件并生成 read_file 调用。

固定 Workflow、ReAct 和混合控制

  • 固定 Workflow:代码决定下一节点,适合流程稳定、监管严格的任务。
  • ReAct-style:模型根据每次 Observation 动态选择下一 Action,适合开放式探索。
  • 生产 Agent:通常是混合模式。代码控制权限、预算、关键状态和强制流程,模型处理难以枚举的语义选择。

Agent Loop 和 ReAct 的区别

二者不是同一层级。

  • Agent Loop 是运行时控制结构:调用模型、执行工具、回填结果、继续或终止。
  • ReAct 是 Loop 内部的一种决策范式:Reasoning、Action、Observation 交替发生。
text
Agent Loop
├── ReAct
├── Plan-and-Execute
├── Reflection
├── 固定状态机
├── 多 Agent Handoff
└── 审批暂停与恢复

所以:

ReAct Agent 通常运行在 Agent Loop 上,但使用 Agent Loop 的 Agent 不一定采用 ReAct。

现代 Tool Calling Agent 也不一定把完整 Thought 明文输出。模型可以内部推理,只输出结构化 Tool Call;只要它根据工具结果继续调整行动,就具有 ReAct-style 特征,但不等于严格复现原论文的显式 Thought → Action → Observation 格式。


工具是如何注册给模型的

每个工具在 Harness 内通常包含两部分:

text
给模型看的 Tool Definition  ←→  Harness 真正执行的 Handler

例如:

python
tool_registry = {
    "read_file": {
        "definition": {
            "type": "function",
            "name": "read_file",
            "description": "读取工作区中的指定文件。修改前先读取最新内容。",
            "parameters": {
                "type": "object",
                "properties": {
                    "path": {
                        "type": "string",
                        "description": "目标文件路径"
                    }
                },
                "required": ["path"],
                "additionalProperties": False
            },
            "strict": True
        },
        "handler": read_file_from_disk
    }
}

模型能够看到:

  • name;
  • description;
  • parameters / inputSchema;
  • 参数说明;
  • 可选的返回值 Schema 和行为说明。

模型看不到 Handler 的具体实现,也不能直接操作操作系统。

调用模型时,Harness 将本轮候选工具定义放进 API 请求的 tools 字段:

python
response = model.generate(
    input=current_context,
    tools=[
        tool_registry["search_code"]["definition"],
        tool_registry["read_file"]["definition"],
        tool_registry["apply_patch"]["definition"],
        tool_registry["run_test"]["definition"]
    ],
    tool_choice="auto"
)

这不是重新训练模型,而是在本次推理请求中向模型声明当前可以使用的能力。OpenAI Function Calling 文档将 Function Tool 定义为由 JSON Schema 描述、由应用程序执行的能力,并建议尽量使用严格模式约束参数。


一次工具调用如何发生

模型生成结构化调用

json
{
  "type": "function_call",
  "name": "read_file",
  "arguments": {
    "path": "src/auth/LoginService.java"
  },
  "call_id": "call_001"
}

Harness 校验并分发

python
call = response.tool_call
tool = tool_registry.get(call.name)

validate_schema(call.arguments, tool.definition)
check_permission(call.name, call.arguments)
result = tool.handler(**call.arguments)

Harness 回填结果

json
{
  "type": "function_call_output",
  "call_id": "call_001",
  "output": "LoginService.java 的文件内容……"
}

call_id 用来关联某次工具请求与对应结果。模型看到结果后,才决定下一步继续读取、搜索、修改、测试还是结束。

标准流程是:

  1. Harness 将候选工具交给模型;
  2. 模型返回零个、一个或多个 Tool Call;
  3. Harness 执行应用侧代码;
  4. Harness 将 Tool Result 回填;
  5. 模型返回最终回答或新的 Tool Call。

Harness 怎么知道下一轮需要哪些工具

严格地说:

Harness 不知道模型下一轮“需要调用什么”,只知道本轮“允许调用什么”。

模型响应可能是:

text
final message
→ 没有后续工具工作,Loop 结束

function_call
→ Harness 执行并回填,继续 Loop

handoff
→ 切换到其他 Agent,继续 Loop

approval interruption
→ 保存状态,暂停等待审批

完整伪代码:

python
while within_budget():
    active_tools = select_available_tools(
        task_state,
        user_permissions,
        environment
    )

    response = call_model(
        context=build_context(),
        tools=[t.definition for t in active_tools],
        tool_choice="auto"
    )

    if response.is_final_message:
        return verify_and_finish(response)

    for call in response.tool_calls:
        validate_tool_exists(call)
        validate_arguments(call)
        authorize(call)
        result = execute(call)
        persist(call, result)
        append_tool_result(call.call_id, result)

当前 OpenAI Agents SDK 的循环也是:检查模型输出,遇到工具调用就执行并继续,只有得到没有后续工具工作的最终答案才返回结果。OpenAI Docs:Running agents


Tool Choice 如何限制模型

常见模式:

  • auto:模型决定调用零个、一个或多个工具;
  • required:必须调用至少一个工具,但由模型选择;
  • forced function:强制调用指定工具;
  • allowed_tools:只允许调用完整工具集中的一个子集;
  • none:禁止调用工具。

编码 Agent 通常以 auto 为主;确定性业务步骤可以使用 required 或强制工具;权限和阶段控制可以使用 allowed_tools。

不要长期把 tool_choice 设为 required,否则模型即使已经完成任务,也可能被迫继续调用工具。


参考资料

相关笔记

相关文章

强化学习笔记 →上下文工程 →Mission: 掌握多模态 LoRA 与 QLoRA →