LangGraph.js实战:构建企业级AI工作流与多Agent协作系统
最近在帮一个电商团队做客服自动化升级时,遇到了一个典型问题:他们原本的AI助手能处理简单问答,但一旦用户问题涉及“查订单状态→确认库存→推荐替代商品→生成优惠券”这样的连续操作,系统就会卡壳。要么漏步骤,要么在状态切换时丢失上下文。这让我重新审视了市面上各种AI Agent框架的差异——很多方案能跑通单点任务,但真正要处理企业级复杂工作流,需要的是更底层的状态控制和流程编排能力。
正是在这个背景下,我深入测试了LangGraph.js。与常见的高层Agent框架不同,它更像一个“AI工作流引擎”,把复杂任务分解为可控的状态节点,通过明确的状态机机制确保每一步都可靠执行。这篇文章不会只讲概念,而是结合电商客服、内容审核、数据报表生成三个实际场景,带你从零构建可落地的多Agent工作流。
1. 为什么单次对话解决不了复杂任务?先理解状态机的价值
很多开发者第一次接触AI Agent时,容易陷入一个误区:认为只要把足够多的上下文塞给大模型,它就能自动处理复杂任务。但实际企业场景中,任务往往需要分步骤执行,且每一步都可能依赖外部系统调用、条件判断或人工审核。这种“有状态”的任务流程,正是LangGraph.js要解决的核心问题。
1.1 从临时对话到可复用工作流
传统聊天式AI在处理简单问答时表现不错,但当任务步骤超过3步,就会出现几个典型问题:
- 上下文丢失:模型可能会“忘记”几步前的关键信息
- 步骤跳跃:模型可能跳过必要的验证环节直接给出结果
- 外部调用混乱:多个API调用之间缺乏状态同步
- 错误恢复困难:某一步失败后很难从断点继续执行
LangGraph.js通过引入状态机概念,把一次性的对话变成了可复用、可调试的工作流。每个步骤都是一个明确的“节点”,节点之间的流转由“边”控制,整个系统的状态被持久化存储。这意味着:
- 你可以准确知道任务执行到哪一步
- 单步失败时可以重试或人工介入
- 相同类型任务可以复用同一套流程
- 整个执行过程可以被监控和优化
1.2 状态机:不只是if-else,而是可控的任务流
状态机在编程中不是新概念,但在AI Agent场景下有了新的价值。一个典型的状态机包含:
- 状态(State):描述系统当前所处的位置,比如“等待用户输入”、“调用库存API”、“生成推荐中”
- 转移(Transition):定义从一个状态到另一个状态的条件和动作
- 动作(Action):进入某个状态时执行的具体操作
在LangGraph.js中,状态机的这些概念被具象化为:
- State对象:存储当前任务的所有上下文数据
- Node节点:执行具体动作的单元,可以是LLM调用、工具调用或判断逻辑
- Edge边:控制流程走向的条件判断
这种设计让复杂任务变得可预测和可调试。比如电商客服场景,你可以明确看到用户请求从“验证订单”状态转移到“检查库存”状态,而不是依赖模型“灵光一现”的推理。
2. LangGraph.js核心架构:如何用图结构组织AI工作流
LangGraph.js的核心创新在于用“图”的概念来组织AI工作流。与传统的线性链式调用不同,图结构允许更复杂的流程控制,包括循环、分支、并行等模式。
2.1 理解三个核心概念:State、Node、Edge
State(状态)是工作流的“记忆单元”。在LangGraph.js中,State通常是一个TypeScript接口,定义工作流需要维护的所有数据:
interface CustomerServiceState { // 用户输入 user_query: string; // 解析后的意图 intent: 'query_order' | 'complaint' | 'product_recommendation'; // 中间结果 order_info?: Order; inventory_status?: Inventory; recommended_products?: Product[]; // 最终输出 response?: string; // 执行状态 current_step: string; error?: string; }Node(节点)是执行具体任务的单元。每个节点接收当前State,执行操作,返回更新后的State:
const orderLookupNode = async (state: CustomerServiceState) => { // 从用户查询中提取订单号 const orderId = extractOrderId(state.user_query); // 调用订单系统API try { const orderInfo = await orderAPI.lookup(orderId); return { ...state, order_info: orderInfo, current_step: 'order_fetched' }; } catch (error) { return { ...state, error: `订单查询失败: ${error.message}`, current_step: 'failed' }; } };Edge(边)控制流程的走向。根据当前State决定下一个执行哪个Node:
const routeByIntent = (state: CustomerServiceState) => { if (state.error) { return "end"; // 有错误直接结束 } switch (state.current_step) { case 'start': return "order_lookup"; case 'order_fetched': return state.intent === 'product_recommendation' ? "inventory_check" : "generate_response"; case 'inventory_check': return "recommend_products"; default: return "end"; } };2.2 工作流组装:从节点到完整图
把各个组件组装成工作流的过程很直观:
import { StateGraph } from "langgraph"; // 创建图实例 const workflow = new StateGraph(CustomerServiceState); // 添加节点 workflow.addNode("order_lookup", orderLookupNode); workflow.addNode("inventory_check", inventoryCheckNode); workflow.addNode("recommend_products", recommendProductsNode); workflow.addNode("generate_response", generateResponseNode); // 设置边 workflow.addEdge("start", "order_lookup"); workflow.addConditionalEdges("order_lookup", routeByIntent); workflow.addEdge("inventory_check", "recommend_products"); workflow.addEdge("recommend_products", "generate_response"); workflow.addEdge("generate_response", "end"); // 编译成可执行图 const app = workflow.compile();这种声明式的构建方式让复杂工作流变得清晰可维护。你可以一眼看出整个任务的执行路径,而不是在层层嵌套的回调中迷失。
3. 企业级实战:构建电商客服多Agent工作流
现在我们来构建一个真实的电商客服工作流。这个场景涉及订单查询、库存检查、商品推荐三个主要环节,正好展示LangGraph.js在处理复杂、有状态任务时的优势。
3.1 定义业务状态和工具集
首先明确业务需要的数据结构:
interface EcommerceState { // 输入 customer_id: string; user_message: string; // 解析结果 parsed_intent: { type: 'order_status' | 'return_request' | 'product_help'; confidence: number; entities: Record<string, string>; }; // 业务数据 order_details?: Order; inventory_data?: Inventory; product_recommendations?: Product[]; // 对话历史 conversation_history: Array<{role: string; content: string}>; // 系统状态 current_step: string; needs_human_help: boolean; final_response?: string; } // 业务工具函数 const tools = { async parseUserIntent(message: string, history: any[]) { // 调用LLM进行意图解析 const result = await llm.invoke(` 分析用户意图。历史对话:${JSON.stringify(history)} 当前消息:${message} 返回JSON:{type: "order_status|return_request|product_help", confidence: 0.95, entities: {订单号: "12345"}} `); return JSON.parse(result); }, async fetchOrderDetails(orderId: string) { // 调用订单系统API return await orderSystem.getOrder(orderId); }, async checkInventory(productId: string) { // 调用库存系统 return await inventorySystem.getStock(productId); }, async generateRecommendations(customerId: string, productId: string) { // 基于用户历史生成推荐 return await recommendationEngine.getRecommendations(customerId, productId); } };3.2 实现关键节点逻辑
每个节点专注做好一件事,保持单一职责:
// 意图解析节点 const intentParsingNode = async (state: EcommerceState) => { const intent = await tools.parseUserIntent( state.user_message, state.conversation_history ); return { ...state, parsed_intent: intent, current_step: 'intent_parsed' }; }; // 订单查询节点 const orderLookupNode = async (state: EcommerceState) => { const orderId = state.parsed_intent.entities.订单号; if (!orderId) { return { ...state, needs_human_help: true, current_step: 'need_order_number' }; } try { const order = await tools.fetchOrderDetails(orderId); return { ...state, order_details: order, current_step: 'order_fetched' }; } catch (error) { return { ...state, needs_human_help: true, current_step: 'order_lookup_failed' }; } }; // 库存检查节点(条件执行) const inventoryCheckNode = async (state: EcommerceState) => { const productId = state.order_details?.product_id; if (!productId) { return { ...state, current_step: 'inventory_skip' }; } const inventory = await tools.checkInventory(productId); return { ...state, inventory_data: inventory, current_step: 'inventory_checked' }; }; // 响应生成节点 const responseGenerationNode = async (state: EcommerceState) => { if (state.needs_human_help) { return { ...state, final_response: "您的问题需要人工客服协助,请稍等...", current_step: 'end' }; } // 基于所有收集的信息生成最终响应 const prompt = ` 你是一个电商客服助手。根据以下信息回复用户: 用户问题:${state.user_message} 订单信息:${JSON.stringify(state.order_details)} 库存状态:${JSON.stringify(state.inventory_data)} 推荐商品:${JSON.stringify(state.product_recommendations)} 要求:专业、友好、准确 `; const response = await llm.invoke(prompt); return { ...state, final_response: response, current_step: 'end' }; };3.3 设计流程控制逻辑
关键的控制逻辑体现在边的定义中:
const conditionalEdges = (state: EcommerceState) => { // 错误处理优先 if (state.needs_human_help) { return "generate_response"; } switch (state.current_step) { case "intent_parsed": if (state.parsed_intent.type === 'order_status') { return "order_lookup"; } else if (state.parsed_intent.type === 'product_help') { return "inventory_check"; } else { return "generate_response"; // 直接处理 } case "order_fetched": // 根据订单状态决定下一步 if (state.order_details?.status === 'shipped') { return "generate_response"; } else { return "inventory_check"; // 待发货订单检查库存 } case "inventory_checked": if (state.inventory_data?.stock_level < 10) { return "product_recommendation"; // 库存低时推荐替代品 } else { return "generate_response"; } case "recommendation_generated": return "generate_response"; default: return "end"; } };3.4 完整工作流集成
最后组装成完整应用:
const workflow = new StateGraph(EcommerceState); // 添加所有节点 workflow.addNode("parse_intent", intentParsingNode); workflow.addNode("order_lookup", orderLookupNode); workflow.addNode("inventory_check", inventoryCheckNode); workflow.addNode("product_recommendation", productRecommendationNode); workflow.addNode("generate_response", responseGenerationNode); // 设置流程 workflow.setEntryPoint("parse_intent"); workflow.addConditionalEdges("parse_intent", conditionalEdges); workflow.addConditionalEdges("order_lookup", conditionalEdges); workflow.addConditionalEdges("inventory_check", conditionalEdges); workflow.addConditionalEdges("product_recommendation", conditionalEdges); workflow.addEdge("generate_response", "end"); const app = workflow.compile(); // 使用示例 const result = await app.invoke({ customer_id: "12345", user_message: "我的订单123456到哪里了?", conversation_history: [], current_step: "start" });这个工作流现在可以处理从意图识别到最终回复的完整链条,每个步骤都有明确的状态跟踪和错误处理。
4. 多Agent协作:超越单任务的复杂问题解决
单个Agent能处理的任务有限,真正的企业级场景往往需要多个专业Agent协作。LangGraph.js的多Agent支持让这种协作变得可行。
4.1 设计多Agent协作架构
考虑一个内容审核场景,需要多个专业Agent共同决策:
- 文本分析Agent:检测敏感词和违规内容
- 图像识别Agent:分析图片是否合规
- 上下文理解Agent:结合历史行为判断风险等级
- 决策Agent:综合所有信息做出最终判断
interface ModerationState { content: { text: string; images: string[]; // base64或URL user_id: string; }; // 各Agent的分析结果 text_analysis?: { has_sensitive_words: boolean; risk_level: 'low' | 'medium' | 'high'; details: string[]; }; image_analysis?: { has_inappropriate_content: boolean; risk_level: 'low' | 'medium' | 'high'; details: string[]; }; user_context?: { previous_violations: number; trust_score: number; }; // 最终决策 final_decision?: 'approve' | 'reject' | 'human_review'; decision_reason?: string; }4.2 实现并行执行和结果聚合
LangGraph.js支持并行节点执行,大幅提升处理效率:
// 并行执行文本和图片分析 const parallelAnalysisNode = async (state: ModerationState) => { const [textResult, imageResult, contextResult] = await Promise.all([ textAnalysisAgent.analyze(state.content.text), imageAnalysisAgent.analyze(state.content.images), contextAnalysisAgent.getUserContext(state.content.user_id) ]); return { ...state, text_analysis: textResult, image_analysis: imageResult, user_context: contextResult, current_step: 'analysis_complete' }; }; // 决策节点综合所有信息 const decisionNode = async (state: ModerationState) => { const risks = []; if (state.text_analysis?.risk_level === 'high') risks.push('文本高风险'); if (state.image_analysis?.risk_level === 'high') risks.push('图片高风险'); if (state.user_context?.previous_violations > 3) risks.push('用户历史违规'); let decision: 'approve' | 'reject' | 'human_review' = 'approve'; let reason = ''; if (risks.length === 0) { decision = 'approve'; reason = '内容符合规范'; } else if (risks.length >= 2) { decision = 'reject'; reason = `多重风险: ${risks.join(', ')}`; } else { decision = 'human_review'; reason = `需要人工审核: ${risks.join(', ')}`; } return { ...state, final_decision: decision, decision_reason: reason, current_step: 'complete' }; };4.3 处理Agent间的依赖和冲突
多Agent协作中,Agent之间可能存在依赖关系或结果冲突。需要设计协调机制:
const conflictResolutionNode = async (state: ModerationState) => { // 检查各Agent结果的一致性 const textRisk = state.text_analysis?.risk_level; const imageRisk = state.image_analysis?.risk_level; const userRisk = state.user_context?.trust_score < 0.5 ? 'high' : 'low'; // 如果结果冲突,需要特殊处理 if (textRisk === 'low' && imageRisk === 'high') { // 文本安全但图片高风险,可能需要重新评估上下文 return { ...state, current_step: 'need_context_review', needs_senior_moderator: true }; } return { ...state, current_step: 'ready_for_decision' }; };这种设计让多Agent系统既能够并行处理提升效率,又能在出现分歧时优雅降级或请求人工干预。
5. 生产级部署:从原型到可靠系统
构建工作流只是第一步,要让LangGraph.js应用真正服务于生产环境,还需要考虑部署、监控、维护等工程化问题。
5.1 状态持久化和恢复
生产环境的工作流可能需要运行较长时间,状态持久化至关重要:
// 基于Redis的状态存储示例 class RedisStateManager { constructor(redisClient) { this.redis = redisClient; } async saveState(workflowId: string, state: any) { await this.redis.set( `workflow:${workflowId}`, JSON.stringify(state), 'EX', 3600 // 1小时过期 ); } async loadState(workflowId: string) { const data = await this.redis.get(`workflow:${workflowId}`); return data ? JSON.parse(data) : null; } } // 使用持久化状态的包装器 const createPersistentWorkflow = (app, stateManager) => { return { async startNew(initialState) { const workflowId = generateId(); await stateManager.saveState(workflowId, initialState); return workflowId; }, async continue(workflowId, userInput) { const currentState = await stateManager.loadState(workflowId); if (!currentState) { throw new Error('Workflow not found'); } // 更新状态并继续执行 const updatedState = { ...currentState, ...userInput }; const result = await app.invoke(updatedState); await stateManager.saveState(workflowId, result); return result; } }; };5.2 错误处理和重试机制
企业级应用需要健壮的错误处理:
const withRetry = async (fn, maxRetries = 3, delay = 1000) => { for (let attempt = 1; attempt <= maxRetries; attempt++) { try { return await fn(); } catch (error) { if (attempt === maxRetries) throw error; console.log(`Attempt ${attempt} failed, retrying in ${delay}ms`); await new Promise(resolve => setTimeout(resolve, delay * attempt)); } } }; // 安全节点执行 const safeNodeExecution = async (node, state) => { try { return await withRetry(() => node(state)); } catch (error) { // 记录详细错误信息 console.error(`Node execution failed:`, error); return { ...state, current_step: 'error', last_error: error.message, error_timestamp: new Date().toISOString() }; } };5.3 监控和可观测性
了解工作流运行状态对于生产系统至关重要:
// 监控装饰器 const withMonitoring = (node, nodeName) => { return async (state) => { const startTime = Date.now(); try { const result = await node(state); const duration = Date.now() - startTime; // 记录成功指标 metrics.recordNodeSuccess(nodeName, duration); return result; } catch (error) { const duration = Date.now() - startTime; metrics.recordNodeFailure(nodeName, duration, error); throw error; } }; }; // 添加监控到所有节点 workflow.addNode( "order_lookup", withMonitoring(orderLookupNode, "order_lookup") );5.4 性能优化建议
根据实际使用经验,有几个性能优化点值得关注:
- 节点懒加载:不是所有节点都需要在初始化时加载,可以按需初始化重型依赖
- 状态序列化优化:只持久化必要的状态字段,避免存储过大对象
- 缓存策略:对耗时的外部调用结果进行缓存
- 批量处理:适合批量处理的任务尽量集中处理,减少IO开销
6. 与其他方案的对比和选型建议
在AI Agent框架选择时,需要根据具体需求做出技术决策。以下是LangGraph.js与其他常见方案的对比:
6.1 与LangChain的对比
LangChain更适合快速构建简单的链式任务,而LangGraph.js专为复杂工作流设计:
| 特性 | LangChain | LangGraph.js |
|---|---|---|
| 架构模式 | 链式结构 | 图结构 |
| 状态管理 | 有限的状态传递 | 完整的State对象 |
| 流程控制 | 线性为主 | 支持循环、分支、并行 |
| 适用场景 | 简单问答、文档处理 | 复杂业务流程、多步骤任务 |
| 学习曲线 | 相对平缓 | 需要理解状态机概念 |
6.2 与低代码平台的对比
像Coze、Dify这样的低代码平台提供了可视化的工作流构建,但与LangGraph.js有不同的适用场景:
选择低代码平台当:
- 需求相对标准,不需要高度定制化
- 团队技术背景有限,需要快速上线
- 工作流逻辑简单,主要是线性流程
选择LangGraph.js当:
- 需要深度定制业务流程逻辑
- 需要与现有系统深度集成
- 工作流包含复杂状态转换和错误处理
- 团队有较强的技术能力进行二次开发
6.3 技术选型决策框架
建议按照以下维度评估需求:
- 复杂度:任务步骤是否超过3步?是否需要条件分支?
- 集成度:是否需要与现有系统深度集成?
- 可控性:是否需要细粒度的错误处理和状态管理?
- 团队能力:团队是否有能力维护代码式工作流?
- 性能要求:是否需要高性能的并行处理?
根据评分结果,可以做出更理性的技术选型。
LangGraph.js代表了一种更工程化的AI Agent构建方式。它不追求一次性解决所有问题的"万能Agent",而是通过状态机和工作流的概念,把复杂问题分解为可控的步骤。这种思路特别适合需要可靠性、可维护性的企业级应用。
真正的价值不在于单次任务的自动化,而在于构建可复用、可监控、可演进的工作流体系。当你的AI应用需要从"能跑通"进化到"能放心使用"时,LangGraph.js提供的底层控制能力就显得尤为重要。
