实战30 分钟
Agentic Loop:从裸循环到图编排
Agent 的心脏是一个 while 循环:Reason → Act → Observe → 再循环。从 easy-agent 的 20 行裸实现拆到 LangGraph 的 StateGraph 图编排,一次讲透。
Agent 的心脏不是模型,是循环。这一章从 20 行裸代码拆到 LangGraph 的图编排。
最简 Agentic Loop:20 行讲清楚
easy-agent Step 4 用一个 while 循环实现了 Agent 的核心——这 20 行就是所有 Agent 框架的心脏:
while (turnCount < maxTurns) {
// 1. 把全部消息发给模型
const result = await streamMessage({ messages, tools });
// 2. 模型的回答放进历史
messages.push(result.assistantMessage);
// 3. 如果模型不想调工具了 → 任务完成,退出循环
if (result.stopReason !== "tool_use") {
return { reason: "completed" };
}
// 4. 模型想调工具 → 执行 → 结果放回历史 → 回到第 1 步
const toolResults = await runTools(result.assistantMessage.content);
messages.push(toolResults);
}
ReAct:不是什么高深算法,就是一个模式名
你刚才看懂的那个循环,有个正式名字叫 ReAct(Reason + Act):
| 阶段 | 谁在做 | 做什么 |
|---|---|---|
| Reason | 模型 | 看完所有消息,决定「我需要先读那个文件」 |
| Act | 你的代码 | 执行模型指定的工具(读文件、跑命令、查数据库) |
| Observe | 模型 | 看到工具返回的结果,决定「接下来我要改这行代码」 |
| 循环 | while | 重复直到模型说「做完了」(stop_reason = end_turn) |
前端类比:while (!done) { 想; 做; 看结果 }——就这么直白。
stop_reason:模型在告诉你它想干什么
模型的每次回答都带一个 stop_reason,这是循环的转向灯:
| stop_reason | 含义 | 你的代码应该 |
|---|---|---|
end_turn | 「我做完了」 | 退出循环,把最终回答给用户 |
tool_use | 「我想调用工具」 | 执行工具,结果塞回去,继续循环 |
max_tokens | 「 token 用完了」 | 截断了,内容不完整 |
循环的退出条件不是你决定的,是模型自己决定的。 它觉得任务完成了就输出 end_turn,你的代码只是尊重这个决定。
maxTurns:安全网
模型可能陷入无限循环——反复调用同一个工具,或者一直说「让我再检查一下」。maxTurns 就是防这个的:
while (turnCount < maxTurns) { // 默认 8 次
// ...
}
// 如果到这里,说明超过轮次了
return { reason: "max_turns" };
easy-agent 默认设 8 轮。Claude Code 内部也有类似的上限。永远要设安全网——模型没有「我已经循环太多次了」的自我意识。
工具执行:不是并发,是顺序
看 easy-agent 的 runTools 函数:
export async function runTools(contentBlocks, toolContext) {
const results = [];
for (const block of contentBlocks) {
if (block.type !== "tool_use") continue;
const tool = findToolByName(block.name);
const result = await tool.call(block.input, toolContext);
results.push({
type: "tool_result",
tool_use_id: block.id,
content: result.content,
});
}
return { role: "user", content: results };
}
三个细节:
for...of顺序执行——不是Promise.all。因为工具之间可能有依赖(先读文件才能改文件)tool_use_id回填——模型发的每个工单有唯一 ID,结果要标记是对哪张工单的回复- 结果以
role: "user"塞回去——API 约定 tool_result 放在 user 角色里
那 LangGraph 做了什么?
LangGraph 把这个 while 循环画成了图:
import { StateGraph } from "@langchain/langgraph";
const graph = new StateGraph(AgentState);
graph.addNode("agent", callModel);
graph.addNode("tools", executeTools);
graph.addConditionalEdges("agent", shouldContinue, {
continue: "tools",
end: END,
});
graph.addEdge("tools", "agent"); // 工具结果 → 回到模型
const app = graph.compile();
对比一下:
| 裸 while 循环 | LangGraph 图 |
|---|---|
while (turn < max) | addConditionalEdges("agent", shouldContinue) |
if (stopReason === "tool_use") | shouldContinue() 函数返回 "continue" 或 "end" |
messages.push(toolResults) | 图的 edge 自动传递 state |
| 没有 | 检查点(checkpoint)——中途暂停/恢复 |
| 没有 | 人工审批——在某个节点暂停等人确认 |
| 没有 | 时间旅行——回到之前任一步 |
LangGraph 的价值不是「能跑通」,是「跑通之后还能管控」——检查点、审批、回溯,这些在裸 while 循环里要自己写很多代码才能实现。
本章要点
- Agent 的心脏 = while 循环:模型 → 工具 → 结果回填 → 再循环
- ReAct = Reason + Act + Observe,不是算法,是模式名
stop_reason是转向灯:end_turn退出,tool_use继续maxTurns是安全网——模型没有循环自我意识- 工具顺序执行(不是并发),结果按
tool_use_id回填 - LangGraph 把 while 画成图,核心增值是检查点/审批/回溯
→ 下一章:Human-in-the-loop 与检查点