实用功能

错误处理

在图中优雅地捕获和处理异常,构建健壮的 Agent

📚 学习目标

学完这篇文章后,你将能够:

  • 在节点内部捕获并处理异常
  • 使用 retry 策略自动重试失败的节点
  • 设计“Fallback 节点”来处理不可恢复的错误

前置知识

在开始学习之前,建议先阅读:

你需要了解:

  • JavaScript try/catch 机制

1 节点级 Try/Catch

最基础的方法是在节点函数内部包裹 try/catch。

import { ToolMessage } from '@langchain/core/messages';

const safeToolNode = async (state) => {
  try {
    const result = await riskyTool.invoke(...);
    return { result };
  } catch (error) {
    console.error("Tool failed:", error);
    // 返回一个错误标识,而不是抛出异常让整个图崩溃
    return {
      error: error.message,
      //以此通知 LLM 工具调用失败
      messages: [new ToolMessage({
        content: `Error: ${error.message}`,
        tool_call_id: ...
      })]
    };
  }
};

📝 提醒

上面的 tool_call_id 需要来自“LLM 的工具调用请求”。如果你使用 ToolNode,它会帮你把 tool result/错误包装成 ToolMessage 并返回给模型。


2 自动重试 (Retry Policy)

LangGraph 允许你为节点配置重试策略。这对于不稳定的网络请求(如 LLM API 调用)非常有用。

注意:目前 LangGraph JS 版本主要通过在 compile 时传入 retry 策略,或在某些预置节点(如 ToolNode)中支持。

// 示例:在 ToolNode 中处理错误
// 如果工具抛出错误,ToolNode 默认会捕获并返回包含错误信息的 ToolMessage
// 这样 LLM 可以看到错误并尝试修复参数重新调用

对于通用节点,你可以自己实现重试逻辑。建议用“指数退避 + 抖动”,避免服务雪崩:

function sleep(ms: number) {
  return new Promise((r) => setTimeout(r, ms));
}

const retryNode = async (state) => {
  let retries = 3;
  let backoffMs = 200;
  while (retries > 0) {
    try {
      return await doSomething();
    } catch (e) {
      retries--;
      if (retries === 0) throw e;
      const jitter = Math.floor(Math.random() * 100);
      await sleep(backoffMs + jitter);
      backoffMs *= 2;
    }
  }
};

2.1 常见错误类型

  • 工具调用失败:外部 API 超时或权限错误
  • 状态错误:写入字段冲突或缺失
  • 网络错误:连接中断或请求失败

💡 说明

先分类,再选策略(重试 / 降级 / 转人工)。

2.2 错误监控与日志

建议在错误发生时记录关键上下文:

const logError = (error: Error, state: unknown) => {
  console.error('GraphError', { message: error.message, state });
};

ℹ️ 说明

日志里不要写敏感信息(token、密钥)。


3 递归限制:防止循环失控

当你的图里存在循环边(例如 ReAct Loop)时,务必设置 recursionLimit

try {
  await app.invoke(inputs, { recursionLimit: 20 });
} catch (e) {
  console.log('可能触发了 recursionLimit 或其他运行时错误');
}

4 Fallback 分支

你可以设计专门的错误处理分支。

// 1. 定义状态包含 error 字段
const State = Annotation.Root({
  // ...
  error: Annotation<string>(),
});

// 2. 路由逻辑
const routeError = (state) => {
  if (state.error) {
    return 'human_help'; // 错误 -> 转人工
  }
  return 'next_step';
};

// 3. 构建图
graph.addConditionalEdges('node_a', routeError);

这样,当节点 A 发生逻辑错误(它捕获异常并设置 state.error)时,系统会自动路由到人工干预节点。


5 用户友好提示

错误信息不要直接暴露给用户,建议转成“可理解的提示”:

const fallbackNode = async () => ({
  messages: [{ role: 'assistant', content: '当前服务繁忙,请稍后再试。' }],
});

⚠️ 注意

保留内部错误日志,外部提示保持友好与可恢复。


💡 练习题

  1. 设计题:设计一个 robust 的网络搜索节点。如果搜索 API超时,它应该重试 3 次;如果依然失败,它应该返回一个“搜索不可用”的占位符,而不是让程序崩溃。

    点击查看答案

    在节点内增加重试计数,超过上限后返回 fallback 文本。

  2. 操作题:为一个工具调用节点加上指数退避重试策略。

    点击查看答案

    每次失败后等待 backoffMs 并逐步翻倍。

  3. 思考题:什么场景下应该转人工?

    点击查看答案

    当错误不可恢复、或影响业务风险较高时。

  4. 操作题:实现一个 fallback 节点输出友好提示。

    点击查看答案

    在 fallback 节点里返回 { messages: [{ role: 'assistant', content: '...' }] }

  5. 思考题:为什么日志不应包含敏感信息?

    点击查看答案

    日志可能被收集或共享,敏感信息泄露会造成安全风险。


✅ 总结

本章要点

  • 永远不要让未捕获的异常中断 Graph 的运行(除非是致命错误)。
  • 将错误信息反馈给 LLM(通过 ToolMessage),让 LLM 尝试自我修复。
  • 使用条件边构建错误恢复流程。

下一步:学习高级流控制工具:Command 对象

登录以继续阅读

解锁完整文档、代码示例及更多高级功能。

立即登录

On this page