第一篇我们搞清楚了 Agent 是什么(大模型 + 工具调用能力),第二篇我们手写了那个"模型决策 + 代码执行"的循环。这一篇我们要把那个循环交给框架------看看 LangGraph 是怎么封装它的。
先放结论:LangGraph 不是另一个框架,它就是 LangChain Agent 的运行时引擎。 LangChain 1.0 的 create_agent 底层直接跑在 LangGraph 上,两者的关系从"二选一"变成了"分层协作":LangChain 帮你快速搭起来,LangGraph 帮你可靠跑下去。
为什么手写循环不够用
第二篇那个 for 循环,逻辑上完全正确:
ini
for (let round = 0; round < maxRounds && response.tool_calls?.length; round++) {
messages.push(response);
for (const call of response.tool_calls) {
const result = await readFileTool.invoke(call.args);
messages.push(new ToolMessage({ content: result, tool_call_id: call.id }));
}
response = await modelWithTools.invoke(messages);
}
但它有几个硬伤:
- 状态是隐式的 :
messages数组就是全部状态,没有结构、没有类型约束,多节点协作时很容易乱 - 没有持久化:程序一崩溃,全部重来,没法从断点恢复
- 没法人工介入:如果某个工具调用需要人审批(比如删数据库),手写循环很难优雅地暂停
- 没法并行:如果模型同时请求调两个工具,手写循环只能串行执行
- 调试困难 :出了问题只能靠
console.log,没有可视化、没有执行轨迹
LangGraph 解决的就是这些问题。
LangGraph 的三个核心概念
LangGraph 把 Agent 工作流建模成一张有向图,由三个元素组成:
State(状态) :整个图共享的数据容器。不是每个节点自己存一份,而是所有节点读写同一个中央状态。状态更新时用 Reducer 决定合并策略------比如 messages 字段用 operator.add 追加而不是覆盖,这样多轮对话历史就不会丢。
Node(节点) :最小执行单元,就是一个函数。输入是当前 State,返回状态更新(不是完整状态,是增量)。LLM 调用是一个节点,工具执行是一个节点,条件路由也可以是一个节点。
Edge(边) :定义节点之间的跳转规则。普通边是固定下一步(A → B),条件边根据 State 动态决定去向(如果还有 tool_calls 就去工具节点,否则去结束节点)。条件边是实现循环的关键------工具节点执行完后,条件边检查状态,把流程指回 LLM 节点,形成"思考 → 行动 → 观察 → 再思考"的闭环。
用 LangGraph 重写第二篇的 Agent
javascript
import { StateGraph } from '@langgraph/graph';
import { END } from '@langgraph/graph';
// 1. 定义 State
const AgentState = {
messages: {
value: (x, y) => x.concat(y), // Reducer:追加
default: () => [],
},
};
// 2. 定义节点
async function modelNode(state) {
const response = await modelWithTools.invoke(state.messages);
return { messages: [response] };
}
async function toolsNode(state) {
const lastMessage = state.messages[state.messages.length - 1];
const results = [];
for (const call of lastMessage.tool_calls) {
const result = await readFileTool.invoke(call.args);
results.push(new ToolMessage({
content: result,
tool_call_id: call.id,
}));
}
return { messages: results };
}
// 3. 条件边:决定下一步去哪
function route(state) {
const lastMessage = state.messages[state.messages.length - 1];
if (lastMessage.tool_calls?.length) {
return 'tools';
}
return END;
}
// 4. 构建图
const workflow = new StateGraph({ channels: AgentState });
workflow.addNode('model', modelNode);
workflow.addNode('tools', toolsNode);
workflow.setEntryPoint('model');
workflow.addEdge('tools', 'model');
workflow.addConditionalEdges('model', route);
// 5. 编译运行
const app = workflow.compile();
const result = await app.invoke({
messages: [
new SystemMessage('你是一个专业的代码助手...'),
new HumanMessage('请读取 tool.mjs 并解释代码'),
],
});
看起来代码量差不多,但底层能力完全不同:
- 状态是显式的 :
AgentState定义了结构,Reducer 保证了消息追加不会覆盖 - 天然支持持久化 :加一个
checkpointer就能自动存状态,程序崩溃后从断点恢复 - 天然支持人工介入 :在任意节点抛
interrupt(),图就暂停,等人审批后继续 - 天然支持并行:如果模型同时返回多个 tool_calls,LangGraph 可以并行执行工具节点
- 可视化调试:LangGraph Studio 可以实时看到图的执行轨迹和状态快照
LangChain 1.0 的 create_agent
如果你觉得上面建图还是麻烦,LangChain 1.0 提供了一个更高层的 API:
php
import { createAgent } from '@langchain/core/agents';
const agent = createAgent({
model: modelWithTools,
tools: [readFileTool],
systemPrompt: '你是一个专业的代码助手...',
});
const result = await agent.invoke({
messages: [new HumanMessage('请读取 tool.mjs 并解释代码')],
});
一行建图,内部自动帮你搭好了 model 节点、tools 节点、条件边、循环逻辑。create_agent 返回的就是一个编译好的 LangGraph 图,你可以直接用它,也可以继续往图上加节点。
四层循环栈:从"会做事"到"做得稳"
LangChain 官方博客把 Agent 的工程栈拆成了四层循环,这是很多人第一次理解"生产级 Agent"时看到的形态:
第一层 Agent Loop:就是我们前面一直在讲的"调用模型 → 检查工具 → 执行工具 → 重复"。这是基础,没有这一层 Agent 就只是一轮问答。
第二层 Verification Loop(验证循环) :在 Agent 输出后加一个校验器(Grader),用明确的标准检查结果:链接能不能打开?CI 是否通过?修改范围有没有超出需求?检查不通过就把失败原因带回给 Agent,让它修一轮。这层会增加延迟和成本,但在生产场景里通常值得。
第三层 Event-driven Loop(事件驱动循环) :解决"谁来启动它"的问题。任务来自外部事件:webhook 触发、cron 定时、Slack 频道里有人发消息。Agent 从被临时调用的脚本,变成了系统里长期运行的组件。
第四层 Learning Loop(学习循环) :从运行记录里改进。分析哪些工具调用成功了、哪些失败了、哪些提示词效果更好,持续优化 Agent 的行为策略。
小结
- LangGraph 是 LangChain Agent 的运行时 ,不是另一个框架。LangChain 1.0 的
create_agent底层跑在 LangGraph 上 - 三个核心概念:State(共享状态)、Node(执行单元)、Edge(控制流),条件边是实现循环的关键
- 四层循环栈:Agent Loop → Verification Loop → Event-driven Loop → Learning Loop,从"会做事"到"做得稳"
第二篇我们让模型能"做",这一篇我们让"做"变得可靠。下一篇我们聊聊记忆------怎么让 Agent 记住之前说过什么、做过什么。