编码母题 / P0 / Agent 运行时 / 02
带终止条件的智能体执行循环
先读清题意与边界,独立尝试,再展开思路并对照可运行的参考代码。
重要程度必练难度中等预计完成35–45 分钟
题目是什么
模型每轮返回最终答案或工具调用。循环应把工具结果回填消息,直到完成、取消或达到 maxSteps;拒绝无动作响应。
输入输出示例
模型连续调用工具却没有最终答案时,到 maxSteps 返回上限状态。
练习起手式与验证
复制代码后补全空方法,再运行下方三组验证;复杂题所需的辅助类型与输入准备一并放在代码块中。
/*
* 题目:带终止条件的智能体执行循环
* 要求:模型每轮返回最终答案或工具调用。循环应把工具结果回填消息,直到完成、取消或达到
* maxSteps;拒绝无动作响应。
* 示例:模型连续调用工具却没有最终答案时,到 maxSteps 返回上限状态。
*/
async function runAgent({
model,
toolExecutor,
messages = [],
maxSteps = 8,
signal,
}) {}
(async () => {
let calls = 0;
const model = async () =>
++calls === 1
? {
type: "tool_calls",
calls: [{ id: "t1", name: "clock", arguments: {} }],
}
: { type: "final", content: "done" };
const result = await runAgent({
model,
toolExecutor: { execute: async () => ({ ok: true, output: "12:00" }) },
});
console.log(result.status); // 预期:"completed"
console.log(result.output); // 预期:"done"
console.log(result.messages[1].role); // 预期:"tool"
})().catch(console.error);
/*
* 题目:带终止条件的智能体执行循环
* 要求:模型每轮返回最终答案或工具调用。循环应把工具结果回填消息,直到完成、取消或达到
* maxSteps;拒绝无动作响应。
* 示例:模型连续调用工具却没有最终答案时,到 maxSteps 返回上限状态。
*/
async function runAgent({
model,
toolExecutor,
messages = [],
maxSteps = 8,
signal,
}) {}
(async () => {
let calls = 0;
const model = async () =>
++calls === 1
? {
type: "tool_calls",
calls: [{ id: "t1", name: "clock", arguments: {} }],
}
: { type: "final", content: "done" };
const result = await runAgent({
model,
toolExecutor: { execute: async () => ({ ok: true, output: "12:00" }) },
});
console.log(result.status); // 预期:"completed"
console.log(result.output); // 预期:"done"
console.log(result.messages[1].role); // 预期:"tool"
})().catch(console.error);解题思路
展开思路、不变量与常见边界
关键切入点:循环控制器掌握状态和终止条件;模型只提议下一步,工具结果按调用 id 归档。
核心模型:循环状态由 messages、step 和 tool 执行结果组成;模型只做决策, ToolExecutor 控制副作用。maxSteps 是最后防线,不应代替业务终止条件。 不变量:每轮最多调用模型一次;每个 tool_call 只执行一次并产生对应结果消息; 返回 completed 时必有 final。迁移:ReAct、Plan-and-Execute、Coding Agent。
参考代码与验证
“关键机制”注释用于解释实现,下载链接提供未经页面注释修改的原始 JS 文件。
// 关键机制:循环控制器掌握状态和终止条件;模型只提议下一步,工具结果按调用 id 归档。
async function runAgent({
model,
toolExecutor,
messages = [],
maxSteps = 8,
signal,
}) {
if (
typeof model !== "function" ||
!toolExecutor ||
!Number.isInteger(maxSteps) ||
maxSteps < 1
)
throw new TypeError("invalid options");
const history = [...messages];
for (let step = 0; step < maxSteps; step += 1) {
if (signal?.aborted)
return {
status: "cancelled",
reason: signal.reason,
messages: history,
steps: step,
};
const decision = await model({ messages: [...history], step, signal });
if (decision?.type === "final") {
history.push({ role: "assistant", content: decision.content });
return {
status: "completed",
output: decision.content,
messages: history,
steps: step + 1,
};
}
if (
decision?.type !== "tool_calls" ||
!Array.isArray(decision.calls) ||
!decision.calls.length
)
throw new Error("model returned no actionable decision");
history.push({ role: "assistant", toolCalls: decision.calls });
for (const call of decision.calls) {
const result = await toolExecutor.execute(call, { signal });
history.push({
role: "tool",
toolCallId: call.id,
name: call.name,
content: result,
});
}
}
return { status: "max_steps_exceeded", messages: history, steps: maxSteps };
}
// 三组以上可直接运行的验证;异步题使用独立执行块。
(async () => {
let calls = 0;
const model = async () =>
++calls === 1
? {
type: "tool_calls",
calls: [{ id: "t1", name: "clock", arguments: {} }],
}
: { type: "final", content: "done" };
const result = await runAgent({
model,
toolExecutor: { execute: async () => ({ ok: true, output: "12:00" }) },
});
console.log(result.status); // 预期:"completed"
console.log(result.output); // 预期:"done"
console.log(result.messages[1].role); // 预期:"tool"
const endless = await runAgent({
model: async () => ({
type: "tool_calls",
calls: [{ id: Math.random(), name: "x" }],
}),
toolExecutor: { execute: async () => ({ ok: true }) },
maxSteps: 2,
});
console.log(endless.status); // 预期:"max_steps_exceeded"
})().catch(console.error);
// 关键机制:循环控制器掌握状态和终止条件;模型只提议下一步,工具结果按调用 id 归档。
async function runAgent({
model,
toolExecutor,
messages = [],
maxSteps = 8,
signal,
}) {
if (
typeof model !== "function" ||
!toolExecutor ||
!Number.isInteger(maxSteps) ||
maxSteps < 1
)
throw new TypeError("invalid options");
const history = [...messages];
for (let step = 0; step < maxSteps; step += 1) {
if (signal?.aborted)
return {
status: "cancelled",
reason: signal.reason,
messages: history,
steps: step,
};
const decision = await model({ messages: [...history], step, signal });
if (decision?.type === "final") {
history.push({ role: "assistant", content: decision.content });
return {
status: "completed",
output: decision.content,
messages: history,
steps: step + 1,
};
}
if (
decision?.type !== "tool_calls" ||
!Array.isArray(decision.calls) ||
!decision.calls.length
)
throw new Error("model returned no actionable decision");
history.push({ role: "assistant", toolCalls: decision.calls });
for (const call of decision.calls) {
const result = await toolExecutor.execute(call, { signal });
history.push({
role: "tool",
toolCallId: call.id,
name: call.name,
content: result,
});
}
}
return { status: "max_steps_exceeded", messages: history, steps: maxSteps };
}
// 三组以上可直接运行的验证;异步题使用独立执行块。
(async () => {
let calls = 0;
const model = async () =>
++calls === 1
? {
type: "tool_calls",
calls: [{ id: "t1", name: "clock", arguments: {} }],
}
: { type: "final", content: "done" };
const result = await runAgent({
model,
toolExecutor: { execute: async () => ({ ok: true, output: "12:00" }) },
});
console.log(result.status); // 预期:"completed"
console.log(result.output); // 预期:"done"
console.log(result.messages[1].role); // 预期:"tool"
const endless = await runAgent({
model: async () => ({
type: "tool_calls",
calls: [{ id: Math.random(), name: "x" }],
}),
toolExecutor: { execute: async () => ({ ok: true }) },
maxSteps: 2,
});
console.log(endless.status); // 预期:"max_steps_exceeded"
})().catch(console.error);
复杂度分析
时间复杂度:O(S+C),不计模型和工具。最多 S 轮循环,累计处理 C 个工具调用。
空间复杂度:O(S+C)。消息轨迹和工具结果随轮次与调用数增长。