本文解释 Model、Harness 与 Tool 的职责边界,以及一次工具调用从模型决策到真实执行的完整链路。
一句话理解工具调用
大模型本身不能直接访问文件系统、终端、数据库和外部 API。它只能根据上下文生成结构化的工具调用请求;Harness 负责注册工具、选择本轮可见工具、校验调用、执行真实代码、回填结果并控制下一轮循环。
用户任务
↓
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 不会预先写死:
如果出现登录 Bug,就读取 LoginService.java。
而是模型看到搜索结果后,语义判断最相关的文件并生成 read_file 调用。
固定 Workflow、ReAct 和混合控制
- 固定 Workflow:代码决定下一节点,适合流程稳定、监管严格的任务。
- ReAct-style:模型根据每次 Observation 动态选择下一 Action,适合开放式探索。
- 生产 Agent:通常是混合模式。代码控制权限、预算、关键状态和强制流程,模型处理难以枚举的语义选择。
Agent Loop 和 ReAct 的区别
二者不是同一层级。
- Agent Loop 是运行时控制结构:调用模型、执行工具、回填结果、继续或终止。
- ReAct 是 Loop 内部的一种决策范式:Reasoning、Action、Observation 交替发生。
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 内通常包含两部分:
给模型看的 Tool Definition ←→ Harness 真正执行的 Handler
例如:
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 字段:
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 描述、由应用程序执行的能力,并建议尽量使用严格模式约束参数。
一次工具调用如何发生
模型生成结构化调用
{
"type": "function_call",
"name": "read_file",
"arguments": {
"path": "src/auth/LoginService.java"
},
"call_id": "call_001"
}
Harness 校验并分发
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 回填结果
{
"type": "function_call_output",
"call_id": "call_001",
"output": "LoginService.java 的文件内容……"
}
call_id 用来关联某次工具请求与对应结果。模型看到结果后,才决定下一步继续读取、搜索、修改、测试还是结束。
标准流程是:
- Harness 将候选工具交给模型;
- 模型返回零个、一个或多个 Tool Call;
- Harness 执行应用侧代码;
- Harness 将 Tool Result 回填;
- 模型返回最终回答或新的 Tool Call。
Harness 怎么知道下一轮需要哪些工具
严格地说:
Harness 不知道模型下一轮“需要调用什么”,只知道本轮“允许调用什么”。
模型响应可能是:
final message
→ 没有后续工具工作,Loop 结束
function_call
→ Harness 执行并回填,继续 Loop
handoff
→ 切换到其他 Agent,继续 Loop
approval interruption
→ 保存状态,暂停等待审批
完整伪代码:
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,否则模型即使已经完成任务,也可能被迫继续调用工具。
参考资料
- OpenAI:Unrolling the Codex agent loop
- OpenAI Docs:Function calling
- OpenAI Docs:Using tools
- OpenAI Docs:Tool search
- OpenAI Docs:Running agents
- OpenAI Docs:Guardrails and human review
- OpenAI Docs:Build skills
- MCP Specification:Tools
- MCP TypeScript SDK:Build your first client
- Anthropic:Effective context engineering for AI agents
- Anthropic:Effective harnesses for long-running agents
- ReAct:Synergizing Reasoning and Acting in Language Models
相关笔记
- 工具发现与动态加载 — 大规模工具集的检索与加载
- 智能体运行时可靠性 — 预算、幂等、重试与恢复
- 结构化输出与Schema校验 — 参数和结果契约
- 智能体工具调用面试速记 — 高频口述答案
- 工具与运行时 MOC