Nodejs也能写Agent - 23.LangGraph篇 - 可靠性与容错

发布时间:2026/7/22 14:50:30
Nodejs也能写Agent - 23.LangGraph篇 - 可靠性与容错 上一篇我们管住了上下文窗口该 trim 的 trim该摘要的摘要RAG 也不再一股脑乱塞。窗口干净了Agent 就稳了吗并没有。生产里 LLM 会超时、会 429、Ollama 会挂、ReAct 会空转烧光额度——喂对了内容照样可能直接崩给你看。本篇管「挂了怎么办」——重试、Fallback、防死循环让异常变成优雅降级而不是用户脸上的 stack trace。老规矩本文以官网最新文档核对过Fault tolerance、Prebuilt middleware、Agents。入口继续createAgent——别再抄createReactAgent。重试 / Fallback / 调用上限优先挂官方中间件别先上手写一整套invokeWithRetry。一、生产里 Agent 怎么死先认清「会死在哪」再谈怎么救。场景典型原因表现超时模型负载高、网络慢、prompt 过长挂起后 timeout429 限流API 配额用尽、并发过高Rate limit exceeded模型不可用Ollama 没起、模型没 pull、服务宕机ECONNREFUSED、model not found上下文超限messages RAG 超窗400 / context length exceeded失控循环模型一直吐tool_calls烧 token、拖超时最后 recursive 爆官网把错误按「谁来修」分得更清楚——不同错误不该用同一招错误类型谁来修策略官方手段瞬时故障网络、限流系统自动指数退避重试modelRetryMiddleware/toolRetryMiddlewareLLM 可恢复工具失败、解析翻车模型错误进ToolMessage让模型改主意工具返回错误串JS 尚无ToolErrorMiddleware用户可修复缺信息、指令不清人暂停等人interrupt/ HITL供应商宕机系统自动换备选模型modelFallbackMiddleware失控循环系统自动封顶调用次数modelCallLimitMiddleware/toolCallLimitMiddlewarerecursionLimit未知异常开发者往上抛别瞎 catch无中间件硬吞工具失败时把错误信息回给模型它往往能自己换招。官网 JS 侧ToolErrorMiddleware尚未提供——继续走「错误内容进 ToolMessage」即可别等一个还不存在的 API。瞬时供应商挂工具可恢复失控循环未知错误发生错误类型?Retry 退避Fallback 模型错误进 ToolMessageCallLimit / recursionLimit向上抛出二、瞬时故障重试网络抖一下、偶发 429立刻失败只会逼用户狂点刷新。策略上策略说明固定间隔每次等一样久简单但 429 时可能越重试越堵指数退避第 n 次等initialDelayMs * backoffFactor^n给服务喘息时间maxRetries一般 23 次封顶别无限重试Chat 模型自身也可能有maxRetries但挂在 Agent 上时官网主路径是中间件——模型调用和工具调用各管各的import{createAgent,modelRetryMiddleware,toolRetryMiddleware,}fromlangchain;import{ChatOllama}fromlangchain/ollama;import{tool}fromlangchain/core/tools;import*aszfromzod;constsearchWebtool(async({q}:{q:string})搜索结果${q},{name:search_web,description:搜索网页外部 API值得重试,schema:z.object({q:z.string()}),});constllmnewChatOllama({model:qwen2.5:7b,temperature:0});constagentcreateAgent({model:llm,tools:[searchWeb],systemPrompt:需要外部信息时调用 search_web。,middleware:[// 模型超时 / 限流 / 5xx 类瞬时错误modelRetryMiddleware({maxRetries:3,backoffFactor:2.0,initialDelayMs:1000,}),// 工具只重试会抖的外部调用本地 read 别无脑重试toolRetryMiddleware({maxRetries:2,tools:[search_web],backoffFactor:2.0,initialDelayMs:500,}),],});要点默认就是指数退避backoffFactor: 0才退回固定间隔。toolRetryMiddleware用tools: [...]收窄范围——官网原话文件系统read失败多半重试也没用网页搜索超时才值得再试。onFailure: continue时重试用尽可返回带错误说明的AIMessage让 Agent 有机会收尾而不是整段炸穿。手写forsleep也能懂原理生产别重复造轮子中间件已经把退避、jitter、失败策略打包好了。三、主模型挂了Fallback重试用尽、或者主模型整机不可用时换备选继续服务主模型 qwen2.5:7b 失败 → 备选 llama3.1:8b或其它已 pull 的模型import{createAgent,modelFallbackMiddleware}fromlangchain;import{ChatOllama}fromlangchain/ollama;constprimarynewChatOllama({model:qwen2.5:7b,temperature:0});constfallbacknewChatOllama({model:llama3.1:8b,temperature:0});constagentcreateAgent({model:primary,tools:[],middleware:[// 主模型失败后按顺序尝试备选可传多个modelFallbackMiddleware(fallback),],});注意备选能力可能不同——回答风格、工具遵从度都会变。日志或 UI 最好标一句「已切换备选模型」别假装什么都没发生。Fallback 解决的是「模型/供应商挂了」解决不了「问题本身无解」。盯 fallback 触发率太高说明主路径在持续抽风。四、防死循环recursionLimit Call LimitReAct 环里模型若一直返回tool_calls而不给最终答案就会空转import{GraphRecursionError}fromlangchain/langgraph;try{awaitagent.invoke({messages:[{role:user,content:帮我做一件超复杂的事}]},{recursionLimit:15}// 生产可配环境变量默认别太大);}catch(err){if(errinstanceofGraphRecursionError){// 别把 stack 甩给用户return任务步骤过多请简化问题后再试。;}throwerr;}但要分清两层闸门闸门数的是什么超限表现recursionLimitLangGraphsuper-step调度滴答不是「模型调用次数」抛GraphRecursionErrormodelCallLimitMiddleware/toolCallLimitMiddleware业务语义上的模型/工具调用次数可exitBehavior: end优雅收束挂了beforeModel/afterModel这类会编译成独立节点的中间件会多占 super-stepwrapModelCall包在原节点里一般不另计。所以recursionLimit要留余量真正「最多调几次模型」交给 Call Limit 更直观。import{createAgent,modelCallLimitMiddleware,toolCallLimitMiddleware,}fromlangchain;import{ChatOllama}fromlangchain/ollama;constagentcreateAgent({model:newChatOllama({model:qwen2.5:7b,temperature:0}),tools:[/* ... */],middleware:[modelCallLimitMiddleware({runLimit:15,// 单次 invoke 内最多 15 次模型调用exitBehavior:end,// 到顶优雅结束而不是甩异常}),toolCallLimitMiddleware({runLimit:30,// 单次 invoke 内工具调用封顶}),],});runLimit一次用户请求内计数下轮重置。threadLimit整条会话累计需要 Checkpointer。两道闸一起上Call Limit 管业务预算recursionLimit防图调度层面真失控。五、缓存与熔断轻量中间件解决「这次调用怎么扛」缓存和熔断解决「别把下游打爆 / 别重复烧钱」。精确缓存 vs 语义缓存类型命中条件MVP 思路精确缓存问题字符串完全一致可 hash内存Map TTL语义缓存embedding 相似度过阈值向量库本篇不展开typeCacheEntry{value:string;expiresAt:number};constexactCachenewMapstring,CacheEntry();constTTL_MS5*60*1000;functioncacheKey(question:string){returnquestion.trim().toLowerCase();}functiongetCached(question:string):string|undefined{consthitexactCache.get(cacheKey(question));if(!hit)returnundefined;if(Date.now()hit.expiresAt){exactCache.delete(cacheKey(question));returnundefined;}returnhit.value;}functionsetCached(question:string,value:string){exactCache.set(cacheKey(question),{value,expiresAt:Date.now()TTL_MS,});}// 「现在几点」「今天天气」——实时题别进缓存注意不同用户 / 不同thread_id是否共享缓存要想清楚带隐私或个性化的回答默认不要全局共享。Circuit Breaker 简述连续失败时与其每次都去撞已经挂掉的 Ollama不如短暂拒绝关闭 → 失败累积 → 打开直接拒→ 冷却后 → 半开放一枪试探→ 成功则关闭classSimpleBreaker{privatefailures0;privateopenUntil0;constructor(privatethreshold5,privatecoolDownMs30_000){}getisOpen(){returnDate.now()this.openUntil;}beforeCall(){if(this.isOpen){thrownewError(服务暂时不可用请稍后重试熔断开启);}}onSuccess(){this.failures0;this.openUntil0;}onFailure(){this.failures1;if(this.failuresthis.threshold){this.openUntilDate.now()this.coolDownMs;this.failures0;}}}constbreakernewSimpleBreaker();asyncfunctioninvokeWithBreaker(run:()Promiseunknown){breaker.beforeCall();try{constresultawaitrun();breaker.onSuccess();returnresult;}catch(e){breaker.onFailure();throwe;}}这是教学级 MVP。完整半开态、按依赖隔离可后续对标 resilience4j 一类库本篇目标是建立心智不是重造运维平台。六、组合生产最小可靠骨架把前面几招叠在一颗createAgent上import{createAgent,modelRetryMiddleware,toolRetryMiddleware,modelFallbackMiddleware,modelCallLimitMiddleware,toolCallLimitMiddleware,}fromlangchain;import{ChatOllama}fromlangchain/ollama;import{GraphRecursionError}fromlangchain/langgraph;import{tool}fromlangchain/core/tools;import*aszfromzod;constgetWeathertool(async({city}:{city:string})${city}晴25°C,{name:get_weather,description:查询城市天气,schema:z.object({city:z.string()}),});constprimarynewChatOllama({model:qwen2.5:7b,temperature:0});constfallbacknewChatOllama({model:llama3.1:8b,temperature:0});constagentcreateAgent({model:primary,tools:[getWeather],systemPrompt:需要天气时调用 get_weather。,middleware:[modelRetryMiddleware({maxRetries:2,backoffFactor:2.0,initialDelayMs:1000,}),toolRetryMiddleware({maxRetries:2,tools:[get_weather],}),modelFallbackMiddleware(fallback),modelCallLimitMiddleware({runLimit:15,exitBehavior:end}),toolCallLimitMiddleware({runLimit:30}),],});exportasyncfunctionsafeInvoke(userText:string){try{returnawaitagent.invoke({messages:[{role:user,content:userText}]},{recursionLimit:25}// 比 callLimit 留余量);}catch(err){if(errinstanceofGraphRecursionError){return{messages:[{role:assistant,content:任务步骤过多请简化问题后再试。,},],};}throwerr;}}retry / fallback / 触顶时你才能在 LangSmith 里看见「到底换过几次、卡在哪」。可靠性没有可观测就是盲修。常见坑无重试直接失败偶发抖动逼用户狂点至少模型侧maxRetries: 2。重试无退避429 时立刻重试只会更堵用指数退避。工具无脑全量重试本地/确定性失败不值得重试tools: [...]收窄。只有recursionLimit、没有 Call Limitsuper-step ≠ 业务调用次数两道闸一起上更稳。Fallback 不告知备选质量不同日志/UI 应标注。缓存误用天气、时间、用户私有回答不该进全局精确缓存。熔断阈值乱调太敏感则误杀太钝则雪崩。瞎 catch 未知异常官网建议处理不了的往上抛方便调试。

相关新闻

最新新闻

日新闻

周新闻

月新闻