Agent 工具调用循环是把大语言模型的决策能力与外部工具的执行能力连接起来的运行机制。模型并不直接执行搜索、文件修改或 API 请求,而是生成结构化的工具调用请求;Agent 的运行框架负责校验并执行请求,再把结果作为新的观察加入上下文,让模型决定下一步。
组成与职责
- Model(模型):根据当前上下文决定直接回答、调用哪个工具以及传入什么参数。
- Harness(运行框架):维护消息和任务状态,向模型暴露工具定义,校验调用,执行权限控制,并把工具结果返回给模型。
- Tools(工具):向外部环境读取信息或执行操作,例如搜索 Wiki、调用业务 API 或写入投稿草稿。
- Environment(环境):保存工具实际作用的数据和状态,例如网站、文件系统或数据库;它位于 Agent 边界之外。
模型负责选择行动,Harness 负责让行动受到约束并真正执行。即使模型已经学习了良好的工具选择策略,工具实现、权限校验和执行结果仍属于模型之外的工程系统。
基本循环
- Harness 将系统指令、用户任务、工具定义和已有轨迹组成当前上下文。
- 模型读取上下文,返回最终文本或一个、多个结构化工具调用。
- Harness 检查工具名称、参数格式、权限和当前任务状态。
- 工具在外部环境中执行,并返回成功结果或结构化错误。
- Harness 使用对应的工具调用 ID,把执行结果追加到消息历史。
- 模型读取新的观察,决定继续调用工具、请求人工介入或结束任务。
这个过程会持续多轮。每一轮产生的模型消息、工具调用和工具结果共同构成 Agent 的运行轨迹。完整轨迹不仅用于后续决策,也用于调试、评测和复盘。
与 ReAct 的关系
ReAct(推理与行动)将推理、行动和环境观察交错起来:模型根据当前信息选择行动,环境返回观察,模型再据此调整后续计划。现代工具调用 API 将“行动”表示为结构化工具请求,将“观察”表示为工具结果。模型的内部推理不一定会暴露给应用,但工具调用与结果之间的对应关系必须被完整保留。
TypeScript 最小骨架
下面是与具体模型 SDK 无关的结构示意:
for (let step = 0; step < maxSteps; step += 1) {
const response = await model.generate({ messages, tools });
messages.push(response.message);
if (response.toolCalls.length === 0) {
return response.text;
}
for (const call of response.toolCalls) {
validateToolCall(call);
const result = await executeTool(call);
messages.push({
role: "tool",
toolCallId: call.id,
content: JSON.stringify(result),
});
}
}
throw new Error("Agent exceeded its step limit");
真实实现还需要处理并行工具调用、取消信号、上下文长度、流式响应以及不同模型提供商的消息格式。
停止条件
Agent 不应无限循环。常见停止条件包括:
- 模型返回不包含工具调用的最终回答;
- 达到最大步骤数、执行时间或成本限制;
- 遇到不可重试的工具错误;
- 即将执行需要用户确认的敏感操作;
- 用户取消任务或外部系统发出终止信号。
工程安全措施
- 使用 Schema 校验参数,并通过允许列表限制可调用工具;
- 将工具错误作为结构化观察返回,但限制同类错误的重试次数;
- 检测连续重复调用,避免模型在相同状态下反复执行;
- 区分只读、可逆写入和不可逆操作,对敏感操作增加预览与人工确认;
- 为写操作设计幂等键或查重机制,防止网络重试造成重复写入;
- 将工具返回内容视为不可信输入,防止其中的文本覆盖系统指令;
- 保存可审计轨迹,同时过滤 Token、Cookie 和其他凭据。