)
大模型在实际业务中往往涉及多任务处理如旅行规划助手需判断意图、查询天气、推荐行程、生成邮件等。单Agent结构存在职责混乱、工具空间过大、流程不可控等问题。LangGraph的多Agent架构通过将复杂任务拆分为职责明确的执行单元并利用图进行流程控制有效解决了这些问题。文章详细介绍了Agent、Graph、Supervisor和Tool的核心概念并通过旅行规划助手的案例阐述了如何使用LangGraph构建多Agent系统包括状态管理、条件边、循环、人工确认和工具调用等关键知识点。最终总结为Agent是执行单元Graph是流程控制Supervisor是调度中心Tool是事实来源。大模型应用一旦进入真实业务就很少只是“一问一答”。用户可能会说我周末想去重庆玩一天。帮我看下天气推荐一个适合的行程如果天气不好就安排室内活动。最后帮我生成一封发给同行朋友的确认邮件但发出前要让我确认。这个请求看起来是一句话实际包含多个任务判断用户想做的是旅行规划。查询天气。查询城市景点或活动信息。根据天气做条件决策。生成行程。生成邮件。邮件发送前等待用户确认。如果把所有事情都交给一个 Agent它需要同时负责规划、查天气、查景点、写行程、写邮件、判断是否需要确认。提示词会越来越长工具会越来越多调试也会越来越困难。多 Agent 的价值就在这里把复杂任务拆成多个职责清楚的执行单元再用图把它们组织起来。一句话概括Agent 是执行单元Graph 是流程控制Supervisor 是调度中心Tool 是事实来源。为什么需要多 Agent单 Agent 的结构通常是用户问题 - 一个模型 - 一组工具 - 最终回答简单任务里这样很好。比如总结一段文本、查询一次库存、调用一次搜索工具单 Agent 足够。但复杂任务会遇到几个问题。第一职责混乱。一个 Agent 同时做意图判断、工具选择、业务执行、内容生成和结果审核prompt 会变得很重。模型不是不能做而是不稳定。第二工具空间过大。工具越多模型越容易选错工具、漏调工具、重复调用工具。比如旅行助手同时拥有天气、酒店、机票、地图、日历、邮件、支付工具单 Agent 很容易乱。第三流程不可控。真实业务经常有固定流程判断任务 - 查询事实 - 生成方案 - 人工确认 - 执行动作 - 返回结果如果只靠 prompt 让模型“自己记得流程”出错时很难定位。第四不容易扩展。当你想新增“预算 Agent”或“餐厅 Agent”时如果所有逻辑都在一个 Agent 里修改成本很高。多 Agent 可以把新能力作为一个独立角色接入。所以多 Agent 不是为了堆概念而是为了解决工程问题让职责边界更清楚。让流程更可控。让工具使用更稳定。让系统更容易调试和扩展。但也要注意多 Agent 不是越多越好。它会增加模型调用次数、链路长度和调试成本。一个实用判断标准是一个工具调用能完成用普通函数或单 Agent。需要分支、循环、重试、人工确认用图。任务天然有多个专业角色并且每个角色需要不同工具或提示词再用多 Agent。贯穿案例旅行规划助手后面一直围绕同一个主题展开旅行规划助手。用户的目标是我周末想去重庆玩一天。帮我看下天气推荐一个适合的行程。 如果天气不好就安排室内活动。最后帮我生成一封给同行朋友的确认邮件但发出前要让我确认。这件事会按学习顺序拆成几步这样安排是为了避免概念突然冒出来。比如createAgent会放在手写工具循环之后因为它本质上是对标准 Agent loop 的封装interrupt会放在 checkpointer 之后因为暂停恢复需要先能保存图的执行状态。LangGraph 的核心思想把流程画成图LangGraph 可以理解为一个面向 Agent 的流程引擎。它的核心概念有四个State整张图共享的状态。Node节点接收当前状态返回状态更新。Edge边决定节点之间的流转。Reducer状态合并规则决定新状态如何写回旧状态。最小的旅行规划流程如下它可以直接编译成一个可运行的 LangGraphimport { Annotation, START, END, StateGraph } fromlangchain/langgraph; // 1. 定义图的共享状态目的地、用户目标、最终行程。 const TripState Annotation.Root({ destination: Annotation({ reducer: (_prev, next) next, default: () }), userGoal: Annotation({ reducer: (_prev, next) next, default: () }), itinerary: Annotation({ reducer: (_prev, next) next, default: () }), }); // 2. 节点只返回状态更新不直接修改全局 state。 const collectRequest (state) ({ destination: state.destination || 重庆, userGoal: state.userGoal || 周末一日游, }); const draftItinerary (state) ({ itinerary: ${state.destination}${state.userGoal}上午解放碑下午山城步道晚上看两江夜景。, }); // 3. 用边把节点串成一条可执行流程。 const graph new StateGraph(TripState) // 添加节点 .addNode(collectRequest, collectRequest) .addNode(draftItinerary, draftItinerary) // 添加边 .addEdge(START, collectRequest) .addEdge(collectRequest, draftItinerary) .addEdge(draftItinerary, END) // compile() 会把“图定义”变成一个真正可运行的 graph前面就类似搭积木。 .compile(); const result await graph.invoke({ destination: 重庆, userGoal: 周末一日游, }); console.log(result.itinerary);这段代码的重点不在模型调用而在流程表达。可以先把它理解成“搭图”先定义 state再把节点和边接起来最后compile()成一个可运行的图。真正开始执行的是invoke()。运行时大概是START - collectRequest - draftItinerary - ENDcollectRequest和draftItinerary都只是普通函数。节点函数读取当前state只返回自己要更新的那一小段 state。LangGraph 再通过 reducer 把这段更新合并回完整 state。所以这里最终能拿到result.itinerary是因为draftItinerary返回了新的itinerary并且这个字段被合并进了最终 state。StateAnnotation状态结构和 reducerAnnotation.Root用来定义图的状态。旅行规划通常需要保存这些状态目的地destination用户目标userGoal天气信息weather活动偏好activityType行程itineraryconst TripState Annotation.Root({ destination: Annotation({ reducer: (_prev, next) next, default: () , }), weather: Annotation({ reducer: (_prev, next) next, default: () null, }), activityType: Annotation({ reducer: (_prev, next) next, default: () outdoor, }), itinerary: Annotation({ reducer: (_prev, next) next, default: () , }), });这里的 reducer 都是覆盖式reducer: (_prev, next) next意思是节点返回的新值直接覆盖旧值。如果字段是列表比如候选景点可以用追加式 reducercandidatePlaces: Annotation({ reducer: (prev, next) [...prev, ...next], default: () [], })在聊天 Agent 场景里通常直接用MessagesAnnotation。它内置了messages的合并规则不需要自己写消息 reducer。import { MessagesAnnotation, StateGraph } from langchain/langgraph; const graph new StateGraph(MessagesAnnotation);选择建议业务流程状态用Annotation.Root。聊天消息状态用MessagesAnnotation。最新值字段用覆盖式 reducer。历史记录、候选结果、消息列表用追加式 reducer。条件边根据天气选择路线旅行规划不应该永远生成同一条路线。如果天气好推荐室外路线如果下雨推荐室内路线。这就是条件边的用途。流程图实现如下import { Annotation, START, END, StateGraph } fromlangchain/langgraph; const TripState Annotation.Root({ destination: Annotation({ reducer: (_prev, next) next, default: () }), weather: Annotation({ reducer: (_prev, next) next, default: () null }), itinerary: Annotation({ reducer: (_prev, next) next, default: () }), }); // 真实项目里这里通常是 API 调用示例里用固定数据模拟天气服务。 const checkWeather (state) ({ weather: { city: state.destination, summary: 小雨, suitableForOutdoor: false, }, }); const outdoorPlan (state) ({ itinerary: ${state.destination}天气适合户外上午解放碑下午山城步道傍晚南滨路看江景。, }); const indoorPlan (state) ({ itinerary: ${state.destination}可能下雨上午重庆中国三峡博物馆下午重庆美术馆晚上室内火锅店。, }); // 条件边读取 state返回下一条路线的 key。 const routeByWeather (state) { return state.weather?.suitableForOutdoor ? outdoor : indoor; }; const graph new StateGraph(TripState) .addNode(checkWeather, checkWeather) .addNode(outdoorPlan, outdoorPlan) .addNode(indoorPlan, indoorPlan) .addEdge(START, checkWeather) // routeByWeather 返回 outdoor 就进 outdoorPlan返回 indoor 就进 indoorPlan。 .addConditionalEdges(checkWeather, routeByWeather, { outdoor: outdoorPlan, indoor: indoorPlan, }) .addEdge(outdoorPlan, END) .addEdge(indoorPlan, END) .compile(); const result await graph.invoke({ destination: 重庆, }); console.log(result.itinerary);addConditionalEdges的作用是把固定流程变成动态流程。是否进入室外行程或室内行程不再写死在代码顺序里而是由 state 中的天气结果决定。它接收三个东西.addConditionalEdges(从哪个节点之后判断, 路由函数, 路由映射)路由函数返回的是一个 keyLangGraph 会用这个 key 去路由映射里找下一个节点。比如routeByWeather(state) // 返回 indoor就会匹配到{ indoor: indoorPlan }最后进入indoorPlan节点。也可以用数组简写.addConditionalEdges(agent, toolsCondition, [tools, END])数组写法适合“路由函数返回值和目标节点名一致”的场景。循环让流程可以重试有些流程不是执行一次就结束。比如生成行程后还要检查是否合格不合格就重新生成。这种流程可以理解成生成 - 检查 - 不合格就重试 - 合格才结束图里用条件边回到前面的节点就能形成循环。比如旅行助手最多尝试三次生成行程直到满足要求实现如下const draftPlan (state) { const attempts state.attempts 1; return { attempts, itinerary: attempts 2 ? 上午重庆中国三峡博物馆下午重庆美术馆晚上室内火锅店。节奏轻松适合雨天。 : 上午解放碑下午山城步道。雨天可能不合适。, approved: attempts 2, }; }; const graph new StateGraph(TripState) .addNode(draftPlan, draftPlan) .addEdge(START, draftPlan) .addConditionalEdges( draftPlan, (state) (state.approved ? done : retry), { retry: draftPlan, done: END, } ) .compile();循环很有用也很危险。实际项目里一定要设置退出条件并在运行时加recursionLimitawait graph.invoke(input, { recursionLimit: 8, });checkpointer让图记住同一个会话旅行规划通常不是一次完成的。用户可能第一轮只说我周末想去重庆玩一天偏轻松一点。第二轮又补一句如果下雨就多安排室内。第二句话没有再说“重庆”和“一天”但系统应该知道这是同一个旅行会话。这个时候就需要 checkpointer。流程可以这样理解state 里保存旅行规划会用到的信息const TripSessionState Annotation.Root({ destination: Annotation({ reducer: (_prev, next) next, default: () , }), userGoal: Annotation({ reducer: (_prev, next) next, default: () , }), weatherPreference: Annotation({ reducer: (_prev, next) next, default: () , }), itinerary: Annotation({ reducer: (_prev, next) next, default: () , }), });第一个节点负责收集或补全旅行需求function collectTripRequest(state) { return { destination: state.destination || 重庆, userGoal: state.userGoal || 周末一日游节奏轻松, weatherPreference: state.weatherPreference || 如果下雨就优先安排室内活动, }; }第二个节点根据当前 state 生成行程草稿function draftTripPlan(state) { return { itinerary: ${state.destination}${state.userGoal}上午三峡博物馆下午重庆美术馆晚上室内火锅。${state.weatherPreference}。, }; }图还是普通的StateGraphimport { MemorySaver } from langchain/langgraph; const graph new StateGraph(TripSessionState) .addNode(collectTripRequest, collectTripRequest) .addNode(draftTripPlan, draftTripPlan) .addEdge(START, collectTripRequest) .addEdge(collectTripRequest, draftTripPlan) .addEdge(draftTripPlan, END); const checkpointer new MemorySaver(); const app graph.compile({ checkpointer });checkpointer不是节点它是整张图的存档器。它会把同一个thread_id下的 state 保存起来。调用时再传thread_idconst tripUser { configurable: { thread_id: trip-user-1, }, }; // 第一轮用户明确说了目的地和目标。 await app.invoke( { destination: 重庆, userGoal: 周末一日游节奏轻松, }, tripUser ); // 第二轮用户只补充下雨偏好没有再说目的地。 const result await app.invoke( { weatherPreference: 如果下雨就多安排室内活动, }, tripUser ); console.log(result.itinerary);因为两次调用用的是同一个thread_id第二轮还能拿到第一轮保存的destination和userGoal。如果换一个thread_id就是另一个旅行会话。可以把thread_id理解成“会话编号”同一个thread_id接着上次保存的 state 跑。不同thread_id互不影响。如果需要持久化可以用 SQLiteimport { SqliteSaver } from langchain/langgraph-checkpoint-sqlite; const checkpointer SqliteSaver.fromConnString(./checkpoints.sqlite); const app graph.compile({ checkpointer, });简单理解MemorySaver适合开发和演示。SQLite / Redis / 数据库适合持久化会话。thread_id是会话隔离的关键。checkpointer 也是interrupt恢复执行的基础。后面讲createAgent时还会再次看到checkpointer。那时它的作用是一样的让同一个thread_id下的多轮 Agent 对话能接着之前的状态继续。interrupt发送邮件前让用户确认旅行助手如果只是生成建议风险不高。但如果它要发送邮件就必须让用户确认。目标流程实现如下import { Annotation, Command, START, END, MemorySaver, StateGraph, interrupt, } fromlangchain/langgraph; const EmailState Annotation.Root({ itinerary: Annotation({ reducer: (_prev, next) next, default: () }), emailDraft: Annotation({ reducer: (_prev, next) next, default: () }), approval: Annotation({ reducer: (_prev, next) next, default: () }), }); // 先生成草稿不直接执行发送动作。 const draftEmail (state) ({ emailDraft: 我们周末重庆一日游安排如下${state.itinerary}, }); const waitForApproval (state) { // interrupt 会暂停图并把待确认信息暴露给调用方。 const approval interrupt({ action: send_trip_email, message: 请确认是否发送这封行程邮件。, draft: state.emailDraft, }); return { approval: String(approval), }; }; const graph new StateGraph(EmailState) .addNode(draftEmail, draftEmail) .addNode(waitForApproval, waitForApproval) .addEdge(START, draftEmail) .addEdge(draftEmail, waitForApproval) .addEdge(waitForApproval, END) .compile({ // 中断恢复依赖 checkpointer 记录暂停位置。 checkpointer: new MemorySaver(), }); const config { configurable: { thread_id: trip-email-1, }, }; const paused await graph.invoke( { itinerary: 上午重庆中国三峡博物馆下午重庆美术馆晚上室内火锅店。, }, config ); console.log(paused.__interrupt__?.[0]?.value); // 用 Command({ resume }) 把人的确认结果交回图流程从 interrupt 处继续。 const done await graph.invoke( new Command({ resume: 确认发送, }), config ); console.log(done.approval);这个人工确认流程包含几个关键点interrupt()会暂停图。暂停信息会出现在__interrupt__。恢复时用new Command({ resume })。必须配合 checkpointer图才能回到暂停位置继续执行。凡是发邮件、付款、删除、部署、修改生产数据都应该考虑这种人类确认流程。工具调用让事实来自工具而不是模型编造到目前为止天气是假写在节点里的。真实系统里天气、景点、订单、库存、用户数据都应该来自工具。进一步看工具调用。旅行 Agent 不再把天气结果写死在节点里而是根据用户问题决定是否调用天气工具。图结构这张图里有两个容易混的点bindTools发生在模型侧让模型知道“可以叫哪些工具”。ToolNode发生在图执行阶段负责“真的把工具跑起来”。标准工具循环可以这样组织import { HumanMessage } fromlangchain/core/messages; import { tool } fromlangchain/core/tools; import { START, END, MessagesAnnotation, StateGraph, } fromlangchain/langgraph; import { ToolNode, toolsCondition } fromlangchain/langgraph/prebuilt; import { ChatOpenAI } fromlangchain/openai; import { z } fromzod; // 工具是 Agent 获取事实的入口天气不再由模型猜。 const lookupWeather tool( async ({ city }) { returnJSON.stringify({ city, summary: 小雨, tempLowC: 16, tempHighC: 22, suitableForOutdoor: false, suggestion: 更适合安排博物馆、重庆美术馆等室内活动。, }); }, { name: lookup_weather, description: 查询城市天气并判断是否适合户外活动。, schema: z.object({ city: z.string().describe(城市名如重庆), }), } ); const tools [lookupWeather]; const llm new ChatOpenAI({ modelName: process.env.AI_MODEL, apiKey: process.env.AI_KEY, configuration: { baseURL: process.env.AI_BASE_URL, }, }).bindTools(tools); // LLM 节点只负责决定直接回答还是先发起 tool call。 asyncfunction travelAgent(state) { const response await llm.invoke([ { role: system, content: 你是旅行助手。涉及天气判断时必须调用 lookup_weather不要凭空猜天气。, }, ...state.messages, ]); return { messages: response, }; } const graph new StateGraph(MessagesAnnotation) .addNode(travelAgent, travelAgent) .addNode(tools, new ToolNode(tools)) .addEdge(START, travelAgent) // toolsCondition 会检查最后一条 AI 消息中是否存在 tool_calls。 .addConditionalEdges(travelAgent, toolsCondition, [tools, END]) // 工具结果回到 Agent由模型基于真实结果组织最终回答。 .addEdge(tools, travelAgent) .compile(); const result await graph.invoke({ messages: [ new HumanMessage(周末去重庆玩一天天气适合户外吗顺便给个建议。), ], }); console.log(result.messages.at(-1)?.content);这里最容易混的是这两个东西const llm new ChatOpenAI({...}).bindTools(tools);和.addNode(tools, new ToolNode(tools))它们不是重复功能职责不一样。bindTools(tools)是把工具说明交给模型。模型看到这些工具后才知道有一个工具叫lookup_weather。这个工具可以查询城市天气。调用它时需要传{ city: string }。但模型本身不会真的执行工具。它只会在 AI 消息里生成一个“我想调用工具”的请求类似{ tool_calls: [ { name: lookup_weather, args: { city: 重庆 } } ] }也就是说bindTools解决的是让模型知道可以叫哪些工具以及怎么叫。ToolNode(tools)解决的是另一件事真的执行工具。当toolsCondition发现最后一条 AI 消息里有tool_calls时流程会进入tools节点。ToolNode会做三件事读取 AI 消息里的tool_calls。根据name找到对应工具比如lookup_weather。执行工具把结果作为工具消息写回messages。所以完整过程是travelAgent 调模型 ↓ 模型生成 tool_calls说“我要查重庆天气” ↓ toolsCondition 发现有 tool_calls ↓ ToolNode 真正执行 lookup_weather ↓ 工具结果回到 messages ↓ travelAgent 再次调模型 ↓ 模型基于天气结果生成最终回答可以简单记成一句话bindTools让模型会“点工具”ToolNode负责真的“跑工具”。这个工具循环由四个部分组成MessagesAnnotation负责保存聊天消息。ToolNode负责读取tool_calls并执行工具。toolsCondition负责判断最后一条 AI 消息里有没有工具调用。.addEdge(tools, travelAgent)让工具结果回到模型模型再生成最终回答。这就是标准 Agent loop。createAgent标准工具 Agent 的快捷方式如果只是标准工具 Agent不需要手写StateGraph ToolNode toolsCondition可以用createAgent。同样的旅行天气助手可以简化成import { HumanMessage } fromlangchain/core/messages; import { MemorySaver } fromlangchain/langgraph; import { ChatOpenAI } fromlangchain/openai; import { createAgent, tool } fromlangchain; import { z } fromzod; const lookupWeather tool( async ({ city }) { returnJSON.stringify({ city, summary: 小雨, suitableForOutdoor: false, suggestion: 建议安排室内活动。, }); }, { name: lookup_weather, description: 查询城市天气并判断是否适合户外活动。, schema: z.object({ city: z.string(), }), } ); const model new ChatOpenAI({ modelName: process.env.AI_MODEL, apiKey: process.env.AI_KEY, configuration: { baseURL: process.env.AI_BASE_URL, }, }); const agent createAgent({ model, tools: [lookupWeather], systemPrompt: 你是旅行天气助手。用户问天气或户外安排时必须调用 lookup_weather。, // checkpointer 让同一个 thread_id 下的多轮对话可以延续上下文。 checkpointer: new MemorySaver(), }); const result await agent.invoke( { messages: [new HumanMessage(重庆周末适合户外玩吗)], }, { configurable: { thread_id: trip-user-1, }, } ); console.log(result.messages.at(-1)?.content);createAgent内部已经封装了常见工具循环。你仍然可以查看它生成的图const drawable await agent.graph.getGraphAsync(); console.log(drawable.drawMermaid({ withStyles: true }));建议学习底层机制时用StateGraph ToolNode toolsCondition。做普通工具 Agent 时用createAgent。需要复杂流程、多 Agent、人工确认时再回到图或 supervisor。多 Agent把旅行助手拆成专业角色旅行规划可以进一步拆成多个专业角色。旅行规划这件事可以拆成三个角色weather_agent只负责天气判断。local_agent只负责城市景点和活动建议。planner_agent只负责把天气和城市信息整合成行程。再加一个 supervisorsupervisor判断用户任务决定调用哪些子 Agent最后汇总。结构为什么这样拆天气 Agent 只拿天气工具不会乱讲景点。城市 Agent 只拿景点工具不会编天气。行程 Agent 不查事实只负责组织方案。supervisor 只调度和汇总不亲自编事实。Supervisor 可以这样组装import { HumanMessage } fromlangchain/core/messages; import { createSupervisor } fromlangchain/langgraph-supervisor; import { ChatOpenAI } fromlangchain/openai; import { createAgent, tool } fromlangchain; import { z } fromzod; const model new ChatOpenAI({ modelName: process.env.AI_MODEL, apiKey: process.env.AI_KEY, configuration: { baseURL: process.env.AI_BASE_URL, }, }); // 子 Agent A只负责天气事实。 const lookupWeather tool( async ({ city }) { returnJSON.stringify({ city, summary: 小雨, suitableForOutdoor: false, suggestion: 建议安排室内活动。, }); }, { name: lookup_weather, description: 查询城市天气并判断是否适合户外活动。, schema: z.object({ city: z.string().describe(城市名如重庆), }), } ); // 子 Agent B只负责本地活动事实。 const lookupLocalActivities tool( async ({ city, activityType }) { const indoor [重庆中国三峡博物馆, 重庆美术馆, 室内火锅店]; const outdoor [解放碑步行, 山城步道, 南滨路江景步道]; returnJSON.stringify({ city, activityType, activities: activityType indoor ? indoor : outdoor, }); }, { name: lookup_local_activities, description: 按城市和活动类型查询适合的一日游活动。, schema: z.object({ city: z.string(), activityType: z.enum([indoor, outdoor]), }), } ); const weatherAgent createAgent({ name: weather_agent, description: 判断目的地天气以及是否适合户外活动。, model, tools: [lookupWeather], systemPrompt: 你只负责天气。必须调用 lookup_weather。输出要包含是否适合户外活动。, }); const localAgent createAgent({ name: local_agent, description: 根据城市和 indoor/outdoor 类型推荐本地活动。, model, tools: [lookupLocalActivities], systemPrompt: 你只负责城市活动推荐。必须调用 lookup_local_activities不要编造工具没有的地点。, }); const plannerAgent createAgent({ name: planner_agent, description: 把天气信息和活动建议整合成一日游行程。, model, tools: [], systemPrompt: 你只负责生成结构清晰的一日游行程。不要编造天气或地点只使用已有信息。, }); // supervisor 只做调度决定先调用谁、后调用谁、何时汇总。 const workflow createSupervisor({ agents: [weatherAgent.graph, localAgent.graph, plannerAgent.graph], llm: model, addHandoffMessages: false, prompt: 你是旅行规划调度员只负责选择合适的子 Agent并在信息齐全后汇总。 调度规则 - 需要天气判断时调用 weather_agent。 - 需要景点或活动建议时调用 local_agent。 - 已经拿到天气和活动信息后调用 planner_agent 生成行程。 - 如果天气不适合户外local_agent 应使用 activityTypeindoor。 - 如果天气适合户外local_agent 应使用 activityTypeoutdoor。 - 不要自己编造天气、地点或行程细节。 - 每个子任务完成后再决定下一步信息齐全就输出最终答案。 , }); const app workflow.compile(); const input { messages: [ new HumanMessage( 我周末想去重庆玩一天。看下天气如果不适合户外就安排室内活动并给我一份行程。 ), ], }; const result await app.invoke(input, { recursionLimit: 12, }); console.log(result.messages.at(-1)?.content);这里的多 Agent 不是让多个 Agent 随机对话而是由 supervisor 按职责调度每个子 Agent 只处理自己边界内的任务。Supervisor 关键知识点handoff控制权怎么交给子 Agent在 supervisor 模式里子 Agent 不是普通函数调用。它更像控制权交接Supervisor - Handoff Tool - Child Agent - Supervisor使用createSupervisor时框架会根据子 Agent 自动生成 handoff 工具。supervisor 通过这些工具把任务交给对应 Agent。因此子 Agent 的这些信息非常重要name给 supervisor 精确识别。description告诉 supervisor 什么时候调用它。systemPrompt限制子 Agent 的职责边界。tools限定子 Agent 能做什么。如果description写得模糊supervisor 就容易选错 Agent。如果子 Agent 工具太多它也容易越界。stream怎么观察多 Agent 是否按预期运行只看最终回答不够。多 Agent 系统一定要看路径。可以用stream// stream 不只拿最终结果而是边执行边把过程吐出来。 const stream await app.stream(input, { // updates每一步哪些节点产生了更新。 // values每一步执行后的完整 state 快照。 streamMode: [updates, values], // 防止 supervisor 和子 Agent 来回交接导致无限循环。 recursionLimit: 12, }); // 用来记录执行过哪些节点比如 supervisor - weather_agent - supervisor。 const nodePath []; // 用来保存最后一次完整 state后面从里面取最终回答。 let finalState null; // stream 是异步迭代器所以这里用 for await 一步步读取事件。 forawait (const event of stream) { // event 的结构类似[updates, {...}] 或 [values, {...}] const [mode, payload] event; // updates 表示“这一步哪些节点有输出”。 // payload 的 key 就是节点名比如 weather_agent、local_agent。 if (mode updates payload typeof payload object) { nodePath.push(...Object.keys(payload)); } // values 表示“当前完整 state”。 // 每次 values 都会覆盖 finalState循环结束后留下的就是最终 state。 if (mode values) { finalState payload; } } console.log(执行路径:, nodePath.join( → )); console.log(最终回答:, finalState?.messages.at(-1)?.content);理想路径应该类似supervisor - weather_agent - supervisor - local_agent - supervisor - planner_agent - supervisor如果你看到 supervisor 反复调用同一个 Agent说明停止条件不清楚。如果你看到 planner_agent 在没有天气和活动信息时就开始规划说明调度规则不够明确。如果你看到 local_agent 编造地点说明工具边界或 system prompt 需要收紧。supervisor prompt调度规则怎么写supervisor prompt 不应该写成知识库而应该写成调度规则。好的 supervisor prompt 要写清楚每个 Agent 负责什么。什么情况下必须调用哪个 Agent。多个 Agent 的调用顺序。supervisor 不能自己做什么。什么条件下停止调度并汇总。如何避免重复调用。一个清晰版本你是旅行规划调度员不直接编造事实。 - 天气问题交给 weather_agent。 - 本地活动推荐交给 local_agent。 - 行程整合交给 planner_agent。 - 天气不适合户外时本地活动必须按 indoor 查询。 - 天气适合户外时本地活动必须按 outdoor 查询。 - 天气和活动都拿到后再调用 planner_agent。 - planner_agent 完成后输出最终答案不要继续交接。实践中多 Agent 是否稳定很大程度取决于 supervisor prompt 是否像“调度手册”而不是像“聊天提示词”。最后总结LangGraph 里的图解决的是“流程可控”。工具调用解决的是“连接真实能力”。checkpointer 解决的是“记忆和恢复”。interrupt 解决的是“人在环路”。supervisor 多 Agent 解决的是“复杂任务分工”。多 Agent 的本质不是多个模型互相聊天而是一个可观察、可恢复、可约束的协作系统。再次总结Agent 是执行单元Graph 是流程控制Supervisor 是调度中心Tool 是事实来源。如何学习大模型 AI 由于新岗位的生产效率要优于被取代岗位的生产效率所以实际上整个社会的生产效率是提升的。但是具体到个人只能说是“最先掌握AI的人将会比较晚掌握AI的人有竞争优势”。这句话放在计算机、互联网、移动互联网的开局时期都是一样的道理。我在一线科技企业深耕十二载见证过太多因技术卡位而跃迁的案例。那些率先拥抱 AI 的同事早已在效率与薪资上形成代际优势我意识到有很多经验和知识值得分享给大家也可以通过我们的能力和经验解答大家在大模型的学习中的很多困惑。我们整理出这套AI 大模型突围资料包✅ 从零到一的 AI 学习路径图✅ 大模型调优实战手册附医疗/金融等大厂真实案例✅ 百度/阿里专家闭门录播课✅ 大模型当下最新行业报告✅ 真实大厂面试真题✅ 2026 最新岗位需求图谱所有资料 ⚡️ 朋友们如果有需要《AI大模型入门进阶学习资源包》下方扫码获取~① 全套AI大模型应用开发视频教程包含提示工程、RAG、LangChain、Agent、模型微调与部署、DeepSeek等技术点② 大模型系统化学习路线作为学习AI大模型技术的新手方向至关重要。 正确的学习路线可以为你节省时间少走弯路方向不对努力白费。这里我给大家准备了一份最科学最系统的学习成长路线图和学习规划带你从零基础入门到精通③ 大模型学习书籍文档学习AI大模型离不开书籍文档我精选了一系列大模型技术的书籍和学习文档电子版它们由领域内的顶尖专家撰写内容全面、深入、详尽为你学习大模型提供坚实的理论基础。④ AI大模型最新行业报告2025最新行业报告针对不同行业的现状、趋势、问题、机会等进行系统地调研和评估以了解哪些行业更适合引入大模型的技术和应用以及在哪些方面可以发挥大模型的优势。⑤ 大模型项目实战配套源码学以致用在项目实战中检验和巩固你所学到的知识同时为你找工作就业和职业发展打下坚实的基础。⑥ 大模型大厂面试真题面试不仅是技术的较量更需要充分的准备。在你已经掌握了大模型技术之后就需要开始准备面试我精心整理了一份大模型面试题库涵盖当前面试中可能遇到的各种技术问题让你在面试中游刃有余。以上资料如何领取为什么大家都在学大模型最近科技巨头英特尔宣布裁员2万人传统岗位不断缩减但AI相关技术岗疯狂扩招有3-5年经验大厂薪资就能给到50K*20薪不出1年“有AI项目经验”将成为投递简历的门槛。风口之下与其像“温水煮青蛙”一样坐等被行业淘汰不如先人一步掌握AI大模型原理应用技术项目实操经验“顺风”翻盘这些资料真的有用吗这份资料由我和鲁为民博士(北京清华大学学士和美国加州理工学院博士)共同整理现任上海殷泊信息科技CEO其创立的MoPaaS云平台获Forrester全球’强劲表现者’认证服务航天科工、国家电网等1000企业以第一作者在IEEE Transactions发表论文50篇获NASA JPL火星探测系统强化学习专利等35项中美专利。本套AI大模型课程由清华大学-加州理工双料博士、吴文俊人工智能奖得主鲁为民教授领衔研发。资料内容涵盖了从入门到进阶的各类视频教程和实战项目无论你是小白还是有些技术基础的技术人员这份资料都绝对能帮助你提升薪资待遇转行大模型岗位。以上全套大模型资料如何领取