跳到主要内容

🚨 错误处理

引言

在构建复杂的 LangGraph 应用时,错误处理是确保系统稳定性和用户体验的关键要素。本节将详细介绍 LangGraph 中的各种错误处理机制,包括递归限制、工具调用错误、状态验证错误以及自定义错误处理策略。

核心概念

错误类型

LangGraph 应用中常见的错误类型包括:

  • 递归错误:图执行陷入无限循环
  • 工具调用错误:外部工具执行失败
  • 状态验证错误:状态更新不符合预期
  • 网络错误:API 调用失败
  • 配置错误:参数配置不正确

错误处理策略

基础错误处理

递归限制

递归限制是防止图执行陷入无限循环的重要机制:

递归限制处理
/**
* ============================================================================
* 递归限制错误处理 - Recursion Limit Error Handling
* ============================================================================
*
* 📖 概述
* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
* 本示例展示如何处理图执行中的递归限制错误,通过设置合理的递归限制、
* 实现循环检测和安全包装,防止无限循环导致的系统崩溃。
*
* 🎯 核心功能
* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
* 1️⃣ 递归限制设置:通过 config.recursionLimit 控制最大执行步数
* 2️⃣ 安全调用封装:捕获递归限制错误并返回友好提示
* 3️⃣ 流式执行支持:在流式模式下也能正确处理递归限制
* 4️⃣ 智能循环检测:通过状态签名检测潜在的无限循环
* 5️⃣ 递归限制建议:针对不同场景提供推荐的限制值
*
* 💡 实现思路
* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
* • RecursionLimitHandler 提供安全的调用封装
* • 捕获包含 "recursion limit" 的错误并返回结构化响应
* • RecursionDetector 通过状态历史检测循环模式
* • 使用状态签名(counter + messageCount)判断是否重复
* • 提供不同复杂度场景的递归限制推荐值
*
* 🚀 使用方式
* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
* // 运行示例
* $ npx esno 实用功能/错误处理/recursion-limit.ts
*
* ⚠️ 注意事项
* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
* • 默认递归限制是 25,根据实际需求调整
* • 递归限制太小可能导致正常流程无法完成
* • 递归限制太大可能导致真正的无限循环难以发现
* • 循环检测只是辅助手段,关键是设计合理的退出条件
*
* @author 程哥
* @version 1.0.0
* @updated 2025-11
*/

import '../../utils/loadEnv';
import { StateGraph, Annotation, START, END } from '@langchain/langgraph';
import { ChatOpenAI } from '@langchain/openai';
import { RunnableConfig } from '@langchain/core/runnables';

// 定义状态
const StateAnnotation = Annotation.Root({
messages: Annotation<string[]>({
reducer: (state: string[], update: string[]) => state.concat(update),
default: () => [],
}),
counter: Annotation<number>({
reducer: (state: number, update: number) => state + update,
default: () => 0,
}),
maxIterations: Annotation<number>({
reducer: (state: number, update: number) => update,
default: () => 5,
}),
});

// 可能导致无限循环的节点
function potentialLoopNode(
state: typeof StateAnnotation.State,
config?: RunnableConfig
) {
console.log(`执行第 ${state.counter + 1} 次迭代`);

// 模拟一些处理逻辑
const newMessage = `迭代 ${state.counter + 1}: 处理中...`;

// 检查是否应该停止(防止真正的无限循环)
if (state.counter >= state.maxIterations) {
console.log('达到最大迭代次数,正常结束');
return {
messages: [newMessage, '任务完成'],
counter: 1,
};
}

return {
messages: [newMessage],
counter: 1,
};
}

// 决策节点 - 可能导致循环
function shouldContinue(state: typeof StateAnnotation.State) {
// 这里的逻辑可能导致无限循环
if (state.counter < state.maxIterations) {
return 'continue';
}
return END;
}

// 构建可能导致递归的图
const recursiveWorkflow = new StateGraph(StateAnnotation)
.addNode('process', potentialLoopNode)
.addEdge(START, 'process')
.addConditionalEdges('process', shouldContinue, {
continue: 'process', // 这里可能导致无限循环
[END]: END,
});

// 编译图并设置递归限制
const recursiveApp = recursiveWorkflow.compile();

// 递归限制错误处理器
class RecursionLimitHandler {
static async safeInvoke(
app: any,
input: any,
config?: RunnableConfig
): Promise<any> {
try {
// 设置递归限制,config 中的值优先
const safeConfig = {
recursionLimit: 10, // 默认递归限制为 10
...config, // config 在后面,会覆盖默认值
};

console.log('开始执行,递归限制:', safeConfig.recursionLimit);
const result = await app.invoke(input, safeConfig);
console.log('执行成功完成');
return result;
} catch (error) {
if (error.message && error.message.includes('recursion limit')) {
console.error('🚨 检测到递归限制错误:', error.message);
return {
error: 'RECURSION_LIMIT_EXCEEDED',
message: '执行超出递归限制,可能存在无限循环',
suggestion: '请检查图的逻辑或增加递归限制',
partialResult: null,
};
}
throw error; // 重新抛出其他错误
}
}

static async safeStream(
app: any,
input: any,
config?: RunnableConfig
): Promise<any[]> {
const results: any[] = [];
let stepCount = 0;
const maxSteps = config?.recursionLimit || 25;

try {
const safeConfig = {
recursionLimit: maxSteps,
...config,
};

// 使用 app.stream 或 app.streamEvents(根据 LangGraph 版本)
if (typeof app.stream !== 'function') {
console.warn('⚠️ 当前版本不支持流式执行,使用普通invoke');
const result = await app.invoke(input, safeConfig);
return [result];
}

for await (const chunk of app.stream(input, safeConfig)) {
stepCount++;
results.push(chunk);

console.log(`步骤 ${stepCount}:`, chunk);

// 额外的安全检查
if (stepCount >= maxSteps) {
console.warn('⚠️ 达到最大步骤数,提前终止');
break;
}
}

return results;
} catch (error) {
if (error.message && error.message.includes('recursion limit')) {
console.error('🚨 流式执行中检测到递归限制错误');
return [
...results,
{
error: 'RECURSION_LIMIT_EXCEEDED',
message: '流式执行超出递归限制',
stepsCompleted: stepCount,
},
];
}
throw error;
}
}
}

// 智能递归检测器
class RecursionDetector {
private stateHistory: any[] = [];
private maxHistorySize: number = 10;

detectLoop(currentState: any): boolean {
// 简化的状态比较(实际应用中可能需要更复杂的逻辑)
const stateSignature = this.getStateSignature(currentState);

// 检查是否在最近的状态中出现过相同的状态
const recentStates = this.stateHistory.slice(-5);
const loopDetected = recentStates.some(
(state) => this.getStateSignature(state) === stateSignature
);

// 添加到历史记录
this.stateHistory.push(currentState);
if (this.stateHistory.length > this.maxHistorySize) {
this.stateHistory.shift();
}

return loopDetected;
}

private getStateSignature(state: any): string {
// 创建状态的简化签名
return JSON.stringify({
counter: state.counter,
messageCount: state.messages?.length || 0,
lastMessage: state.messages?.[state.messages.length - 1] || '',
});
}

reset(): void {
this.stateHistory = [];
}
}

// 带循环检测的安全节点包装器
function createSafeNode(originalNode: Function, detector: RecursionDetector) {
return async (state: any, config?: any) => {
// 检测潜在的循环
if (detector.detectLoop(state)) {
console.warn('⚠️ 检测到潜在的状态循环');
return {
messages: ['检测到循环,终止执行'],
error: 'POTENTIAL_LOOP_DETECTED',
};
}

return await originalNode(state, config);
};
}

// 使用示例
async function demonstrateRecursionLimit() {
console.log('=== 递归限制处理示例 ===\n');

// 1. 正常执行(不会触发递归限制)
console.log('1. 正常执行测试:');
try {
const normalResult = await RecursionLimitHandler.safeInvoke(
recursiveApp,
{
messages: ['开始处理'],
maxIterations: 3, // 设置较小的迭代次数
},
{ recursionLimit: 10 }
);
console.log('正常执行结果:', normalResult.messages);
} catch (error) {
console.error('正常执行失败:', error.message);
}

console.log('\n' + '='.repeat(50) + '\n');

// 2. 触发递归限制
console.log('2. 递归限制测试:');
try {
const limitResult = await RecursionLimitHandler.safeInvoke(
recursiveApp,
{
messages: ['开始处理'],
maxIterations: 20, // 设置较大的迭代次数
},
{ recursionLimit: 5 } // 设置较小的递归限制
);
console.log('递归限制结果:', limitResult);
} catch (error) {
console.error('递归限制处理失败:', error.message);
}

console.log('\n' + '='.repeat(50) + '\n');

// 3. 流式执行与递归限制
console.log('3. 流式执行递归限制测试:');
try {
const streamResults = await RecursionLimitHandler.safeStream(
recursiveApp,
{
messages: ['开始流式处理'],
maxIterations: 15,
},
{ recursionLimit: 8 }
);
console.log('流式执行结果数量:', streamResults.length);
console.log('最后一个结果:', streamResults[streamResults.length - 1]);
} catch (error) {
console.error('流式执行失败:', error.message);
}

console.log('\n' + '='.repeat(50) + '\n');

// 4. 智能循环检测
console.log('4. 智能循环检测测试:');
const detector = new RecursionDetector();

// 创建带检测的安全图
const safeWorkflow = new StateGraph(StateAnnotation)
.addNode('safeProcess', createSafeNode(potentialLoopNode, detector))
.addEdge(START, 'safeProcess')
.addConditionalEdges('safeProcess', shouldContinue, {
continue: 'safeProcess',
[END]: END,
});

const safeApp = safeWorkflow.compile();

try {
const detectionResult = await safeApp.invoke({
messages: ['开始智能检测'],
maxIterations: 10,
});
console.log('智能检测结果:', detectionResult.messages);
} catch (error) {
console.error('智能检测失败:', error.message);
}
}

// 递归限制配置建议
const RECURSION_LIMIT_RECOMMENDATIONS = {
simple: {
limit: 25,
description: '简单的线性流程',
useCase: '基本聊天机器人、简单工具调用',
},
moderate: {
limit: 50,
description: '中等复杂度的流程',
useCase: 'ReAct 代理、多步骤任务',
},
complex: {
limit: 100,
description: '复杂的多代理系统',
useCase: '复杂推理、多代理协作',
},
unlimited: {
limit: 1000,
description: '几乎无限制(谨慎使用)',
useCase: '特殊场景,需要大量迭代',
},
};

// 运行示例
if (require.main === module) {
demonstrateRecursionLimit().catch(console.error);
}

export {
recursiveApp,
RecursionLimitHandler,
RecursionDetector,
createSafeNode,
demonstrateRecursionLimit,
RECURSION_LIMIT_RECOMMENDATIONS,
};

/*
执行结果:
Line:8 🌭 path.resolve(__dirname, '.env') /Users/loock/myFile/langgraphjs-tutorial/websites/examples/utils/.env
=== 递归限制处理示例 ===

1. 正常执行测试:
开始执行,递归限制: 10
执行第 1 次迭代
执行第 2 次迭代
执行第 3 次迭代
执行成功完成
正常执行结果: [ '开始处理', '迭代 1: 处理中...', '迭代 2: 处理中...', '迭代 3: 处理中...' ]

==================================================

2. 递归限制测试:
开始执行,递归限制: 5
执行第 1 次迭代
执行第 2 次迭代
执行第 3 次迭代
执行第 4 次迭代
执行第 5 次迭代

==================================================

3. 流式执行递归限制测试:

==================================================

4. 智能循环检测测试:
执行第 1 次迭代
执行第 1 次迭代
执行第 2 次迭代
执行第 2 次迭代
执行第 3 次迭代
执行第 3 次迭代
执行第 4 次迭代
执行第 4 次迭代
执行第 5 次迭代
执行第 5 次迭代
执行第 6 次迭代
执行第 6 次迭代
执行第 7 次迭代
执行第 7 次迭代
执行第 8 次迭代
执行第 8 次迭代
执行第 9 次迭代
执行第 10 次迭代
智能检测结果: [
'开始智能检测',
'迭代 1: 处理中...',
'迭代 2: 处理中...',
'迭代 3: 处理中...',
'迭代 4: 处理中...',
'迭代 5: 处理中...',
'迭代 6: 处理中...',
'迭代 7: 处理中...',
'迭代 8: 处理中...',
'迭代 9: 处理中...',
'迭代 10: 处理中...'
]
*/

工具调用错误处理

工具调用错误处理
/**
* ============================================================================
* 工具调用错误处理 - Tool Error Handling
* ============================================================================
*
* 📖 概述
* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
* 本示例展示如何处理工具调用过程中的各种错误,包括 API 失败、超时、权限错误等,
* 通过重试机制、断路器模式和降级策略,提高工具调用的可靠性。
*
* 🎯 核心功能
* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
* 1️⃣ 安全工具节点:包装工具调用,自动处理异常
* 2️⃣ 重试机制:使用指数退避策略自动重试失败的工具调用
* 3️⃣ 降级响应:工具失败时提供有意义的降级消息
* 4️⃣ 断路器模式:防止频繁失败的工具持续被调用
* 5️⃣ 错误分类处理:根据错误类型(超时、权限、网络等)提供不同响应
*
* 💡 实现思路
* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
* • SafeToolNode 类封装工具调用逻辑,提供统一的错误处理
* • 每个工具调用最多重试 3 次,使用指数退避延迟
* • 根据错误消息关键词判断错误类型,返回相应的降级响应
* • 断路器记录失败次数,达到阈值后拒绝调用
* • 错误分析节点汇总工具错误,提供智能建议
*
* 🚀 使用方式
* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
* // 运行示例
* $ npx esno 实用功能/错误处理/tool-error-handling.ts
*
* ⚠️ 注意事项
* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
* • 工具重试要考虑幂等性,避免重复操作造成副作用
* • 断路器阈值和重置时间需要根据实际场景调整
* • 降级响应要提供有价值的信息,而非简单的错误提示
* • 需要设置 OPENAI_API_KEY 环境变量以运行示例
*
* @author 程哥
* @version 1.0.0
* @updated 2025-11
*/

import '../../utils/loadEnv';
import { StateGraph, Annotation, START, END } from '@langchain/langgraph';
import { ChatOpenAI } from '@langchain/openai';
import { tool } from '@langchain/core/tools';
import {
BaseMessage,
HumanMessage,
ToolMessage,
AIMessage,
} from '@langchain/core/messages';
import { z } from 'zod';
import { RunnableConfig } from '@langchain/core/runnables';

// 定义状态
const StateAnnotation = Annotation.Root({
messages: Annotation<BaseMessage[]>({
reducer: (state: BaseMessage[], update: BaseMessage[]) =>
state.concat(update),
default: () => [],
}),
toolErrors: Annotation<string[]>({
reducer: (state: string[], update: string[]) => state.concat(update),
default: () => [],
}),
retryCount: Annotation<number>({
reducer: (state: number, update: number) => update,
default: () => 0,
}),
});

// 模拟可能失败的工具
const unreliableApiTool = tool(
async ({ query, failureRate = 0.3 }) => {
console.log(`调用不可靠 API: ${query}`);

// 模拟随机失败
if (Math.random() < failureRate) {
throw new Error(`API 调用失败: 网络超时或服务不可用`);
}

// 模拟成功响应
return `API 响应: 关于 "${query}" 的信息已找到`;
},
{
name: 'unreliable_api',
description: '调用可能失败的外部 API',
schema: z.object({
query: z.string().describe('查询内容'),
failureRate: z.number().optional().describe('失败率 (0-1)'),
}),
}
);

// 数据库查询工具(可能超时)
const databaseTool = tool(
async ({ query, timeout = 5000 }) => {
console.log(`执行数据库查询: ${query}`);

// 模拟查询延迟
const queryTime = Math.random() * 8000; // 0-8秒随机延迟

return new Promise<string>((resolve, reject) => {
setTimeout(() => {
if (queryTime > timeout) {
reject(
new Error(
`数据库查询超时: 查询时间 ${Math.round(
queryTime
)}ms 超过限制 ${timeout}ms`
)
);
} else {
resolve(
`数据库结果: 查询 "${query}" 返回 ${Math.floor(
Math.random() * 100
)} 条记录`
);
}
}, Math.min(queryTime, timeout + 100));
});
},
{
name: 'database_query',
description: '执行数据库查询',
schema: z.object({
query: z.string().describe('SQL 查询语句'),
timeout: z.number().optional().describe('超时时间(毫秒)'),
}),
}
);

// 文件操作工具(可能权限错误)
const fileOperationTool = tool(
async ({ operation, filename }) => {
console.log(`执行文件操作: ${operation} on ${filename}`);

// 模拟权限检查
const restrictedFiles = ['system.conf', 'secrets.txt', 'admin.log'];
if (restrictedFiles.includes(filename)) {
throw new Error(`权限拒绝: 无法访问文件 "${filename}"`);
}

// 模拟文件不存在
if (filename.includes('nonexistent')) {
throw new Error(`文件不存在: "${filename}" 未找到`);
}

return `文件操作成功: ${operation} 操作已在 "${filename}" 上完成`;
},
{
name: 'file_operation',
description: '执行文件操作',
schema: z.object({
operation: z.enum(['read', 'write', 'delete']).describe('操作类型'),
filename: z.string().describe('文件名'),
}),
}
);

// 工具列表
const tools = [unreliableApiTool, databaseTool, fileOperationTool];

// 创建带工具的模型
const model = new ChatOpenAI({
model: process.env.OPENAI_MODEL_NAME,
temperature: 0,
}).bindTools(tools);

// 安全工具节点 - 包含错误处理
class SafeToolNode {
private tools: any[];
private maxRetries: number;
private retryDelay: number;

constructor(tools: any[], maxRetries = 3, retryDelay = 1000) {
this.tools = tools;
this.maxRetries = maxRetries;
this.retryDelay = retryDelay;
}

async invoke(state: typeof StateAnnotation.State, config?: RunnableConfig) {
const lastMessage = state.messages[state.messages.length - 1];

if (
!('tool_calls' in lastMessage) ||
!Array.isArray(lastMessage.tool_calls) ||
!lastMessage.tool_calls.length
) {
return { messages: [] };
}

const toolResults: BaseMessage[] = [];
const errors: string[] = [];

for (const toolCall of lastMessage.tool_calls) {
const tool = this.tools.find((t) => t.name === toolCall.name);

if (!tool) {
const errorMsg = `工具 "${toolCall.name}" 未找到`;
errors.push(errorMsg);
toolResults.push(
new ToolMessage({
content: `错误: ${errorMsg}`,
tool_call_id: toolCall.id,
})
);
continue;
}

// 重试机制
let lastError: Error | null = null;
let success = false;

for (let attempt = 1; attempt <= this.maxRetries; attempt++) {
try {
console.log(
`尝试 ${attempt}/${this.maxRetries}: 调用工具 ${toolCall.name}`
);

const result = await tool.invoke(toolCall.args);

toolResults.push(
new ToolMessage({
content: result,
tool_call_id: toolCall.id,
})
);

success = true;
break;
} catch (error) {
lastError = error as Error;
console.warn(
`工具调用失败 (尝试 ${attempt}/${this.maxRetries}):`,
error.message
);

if (attempt < this.maxRetries) {
// 指数退避延迟
const delay = this.retryDelay * Math.pow(2, attempt - 1);
console.log(`等待 ${delay}ms 后重试...`);
await new Promise((resolve) => setTimeout(resolve, delay));
}
}
}

if (!success && lastError) {
const errorMsg = `工具 "${toolCall.name}" 执行失败: ${lastError.message}`;
errors.push(errorMsg);

// 根据错误类型提供不同的处理
let fallbackContent = this.getFallbackResponse(
toolCall.name,
lastError
);

toolResults.push(
new ToolMessage({
content: fallbackContent,
tool_call_id: toolCall.id,
})
);
}
}

return {
messages: toolResults,
toolErrors: errors,
};
}

private getFallbackResponse(toolName: string, error: Error): string {
// 根据工具类型和错误类型提供备用响应
if (error.message.includes('超时')) {
return `工具 "${toolName}" 响应超时,请稍后重试或使用其他方法获取信息。`;
} else if (error.message.includes('权限')) {
return `工具 "${toolName}" 权限不足,无法执行请求的操作。`;
} else if (error.message.includes('不存在')) {
return `工具 "${toolName}" 请求的资源不存在,请检查参数是否正确。`;
} else if (error.message.includes('网络')) {
return `工具 "${toolName}" 网络连接失败,请检查网络连接或稍后重试。`;
} else {
return `工具 "${toolName}" 暂时不可用: ${error.message}。请尝试其他方法或稍后重试。`;
}
}
}

// 创建安全工具节点实例
const safeToolNode = new SafeToolNode(tools);

// 助手节点
async function assistant(
state: typeof StateAnnotation.State,
config?: RunnableConfig
) {
const response = await model.invoke(state.messages);
return { messages: [response] };
}

// 错误分析节点
function errorAnalysis(
state: typeof StateAnnotation.State,
config?: RunnableConfig
) {
if (state.toolErrors.length === 0) {
return { messages: [] };
}

const errorSummary = `检测到 ${
state.toolErrors.length
} 个工具错误:\n${state.toolErrors
.map((err, i) => `${i + 1}. ${err}`)
.join('\n')}`;

const analysisMessage = new HumanMessage({
content: `系统提示: ${errorSummary}\n\n请根据这些错误信息,为用户提供有用的替代方案或建议。`,
});

return { messages: [analysisMessage] };
}

// 路由函数
function shouldContinue(state: typeof StateAnnotation.State) {
const lastMessage = state.messages[state.messages.length - 1];

// 检查是否有工具调用
if (
'tool_calls' in lastMessage &&
Array.isArray(lastMessage.tool_calls) &&
lastMessage.tool_calls.length > 0
) {
return 'tools';
}

// 检查是否有工具错误需要分析
if (state.toolErrors.length > 0 && state.retryCount === 0) {
return 'error_analysis';
}

return END;
}

// 构建图
const workflow = new StateGraph(StateAnnotation)
.addNode('assistant', assistant)
.addNode('tools', (state, config) => safeToolNode.invoke(state, config))
.addNode('error_analysis', errorAnalysis)
.addEdge(START, 'assistant')
.addConditionalEdges('assistant', shouldContinue)
.addEdge('tools', 'assistant')
.addEdge('error_analysis', 'assistant');

const app = workflow.compile();

// 工具错误处理策略
class ToolErrorStrategy {
static async handleWithCircuitBreaker(
toolFunction: Function,
args: any,
options: {
failureThreshold: number;
resetTimeout: number;
monitorWindow: number;
} = {
failureThreshold: 5,
resetTimeout: 60000,
monitorWindow: 300000,
}
) {
// 简化的断路器实现
const key = toolFunction.name;
const now = Date.now();

// 获取或创建断路器状态
if (!this.circuitBreakerState.has(key)) {
this.circuitBreakerState.set(key, {
failures: 0,
lastFailureTime: 0,
state: 'CLOSED', // CLOSED, OPEN, HALF_OPEN
});
}

const breakerState = this.circuitBreakerState.get(key)!;

// 检查断路器状态
if (breakerState.state === 'OPEN') {
if (now - breakerState.lastFailureTime > options.resetTimeout) {
breakerState.state = 'HALF_OPEN';
console.log(`断路器 ${key} 进入半开状态`);
} else {
throw new Error(`断路器 ${key} 处于开启状态,拒绝执行`);
}
}

try {
const result = await toolFunction(args);

// 成功时重置断路器
if (breakerState.state === 'HALF_OPEN') {
breakerState.state = 'CLOSED';
breakerState.failures = 0;
console.log(`断路器 ${key} 重置为关闭状态`);
}

return result;
} catch (error) {
breakerState.failures++;
breakerState.lastFailureTime = now;

if (breakerState.failures >= options.failureThreshold) {
breakerState.state = 'OPEN';
console.log(`断路器 ${key} 开启,失败次数: ${breakerState.failures}`);
}

throw error;
}
}

private static circuitBreakerState = new Map<
string,
{
failures: number;
lastFailureTime: number;
state: 'CLOSED' | 'OPEN' | 'HALF_OPEN';
}
>();
}

// 使用示例
async function demonstrateToolErrorHandling() {
console.log('=== 工具调用错误处理示例 ===\n');

// 1. 正常工具调用
console.log('1. 正常工具调用测试:');
try {
const result1 = await app.invoke({
messages: [
new HumanMessage({
content: '请查询用户数据库中的活跃用户信息',
}),
],
});

console.log(
'最后一条消息:',
result1.messages[result1.messages.length - 1].content
);
console.log('工具错误数量:', result1.toolErrors.length);
} catch (error) {
console.error('正常调用失败:', error.message);
}

console.log('\n' + '='.repeat(50) + '\n');

// 2. 工具调用失败处理
console.log('2. 工具调用失败处理测试:');
try {
const result2 = await app.invoke({
messages: [
new HumanMessage({
content: '请读取 system.conf 文件的内容,并调用外部 API 获取相关信息',
}),
],
});

console.log(
'最后一条消息:',
result2.messages[result2.messages.length - 1].content
);
console.log('工具错误:', result2.toolErrors);
} catch (error) {
console.error('失败处理测试失败:', error.message);
}

console.log('\n' + '='.repeat(50) + '\n');

// 3. 超时处理测试
console.log('3. 超时处理测试:');
try {
const result3 = await app.invoke({
messages: [
new HumanMessage({
content: '请执行一个可能超时的数据库查询,查询所有用户的详细信息',
}),
],
});

console.log(
'超时处理结果:',
result3.messages[result3.messages.length - 1].content
);
} catch (error) {
console.error('超时处理测试失败:', error.message);
}
}

// 运行示例
if (require.main === module) {
demonstrateToolErrorHandling().catch(console.error);
}

export {
app,
SafeToolNode,
ToolErrorStrategy,
demonstrateToolErrorHandling,
tools,
};

/*
执行结果:
Line:8 🌭 path.resolve(__dirname, '.env') /Users/loock/myFile/langgraphjs-tutorial/websites/examples/utils/.env
=== 工具调用错误处理示例 ===

1. 正常工具调用测试:
尝试 1/3: 调用工具 database_query
执行数据库查询: SELECT * FROM users WHERE status = 'active'
等待 1000ms 后重试...
尝试 2/3: 调用工具 database_query
执行数据库查询: SELECT * FROM users WHERE status = 'active'
最后一条消息: 已查询到数据库中状态为“活跃”的用户信息,共 86 条记录。是否需要进一步筛选或导出这些数据?
工具错误数量: 0

==================================================

2. 工具调用失败处理测试:
尝试 1/3: 调用工具 file_operation
执行文件操作: read on system.conf
等待 1000ms 后重试...
尝试 2/3: 调用工具 file_operation
执行文件操作: read on system.conf
等待 2000ms 后重试...
尝试 3/3: 调用工具 file_operation
执行文件操作: read on system.conf

==================================================

3. 超时处理测试:
尝试 1/3: 调用工具 database_query
执行数据库查询: SELECT * FROM users
等待 1000ms 后重试...
尝试 2/3: 调用工具 database_query
执行数据库查询: SELECT * FROM users
等待 2000ms 后重试...
尝试 3/3: 调用工具 database_query
执行数据库查询: SELECT * FROM users
超时处理结果: 数据库查询成功,已获取到 4 条用户记录。需要我展示这些记录的具体信息吗?
*/

高级错误处理

状态验证和错误恢复

状态验证错误处理
/**
* ============================================================================
* 状态验证错误处理 - State Validation Error Handling
* ============================================================================
*
* 📖 概述
* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
* 本示例展示如何使用 Zod 进行状态验证,实现类型安全的状态管理和错误处理。
* 通过定义验证模式和自定义验证器,确保状态数据的完整性和一致性。
*
* 🎯 核心功能
* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
* 1️⃣ Zod 模式验证:使用 Zod 定义复杂的状态验证规则
* 2️⃣ 状态转换验证:检查状态字段变更的合法性
* 3️⃣ 高级状态验证器:支持自定义验证规则和跨字段验证
* 4️⃣ 状态快照管理:保存和恢复历史状态,支持回滚
* 5️⃣ 验证错误恢复:提供详细的验证错误信息和修复建议
*
* 💡 实现思路
* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
* • 使用 Zod Schema 定义严格的数据结构和验证规则
* • 在状态更新前执行验证,防止无效数据进入系统
* • 支持自定义验证器,实现业务逻辑级别的验证
* • 状态快照机制提供时间点恢复能力
* • 详细的错误消息帮助快速定位和修复问题
*
* 🚀 使用方式
* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
* // 运行示例
* $ npx esno 实用功能/错误处理/state-validation.ts
*
* ⚠️ 注意事项
* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
* • Zod 验证会增加运行时开销,需要在性能和安全性之间权衡
* • 状态快照会占用内存,需要限制快照数量
* • 验证规则应该与业务需求保持同步
* • 自定义验证器应该是纯函数,避免副作用
*
* @author 程哥
* @version 1.0.0
* @updated 2025-11
*/

import '../../utils/loadEnv';
import { StateGraph, Annotation, START, END } from '@langchain/langgraph';
import { BaseMessage, HumanMessage, AIMessage } from '@langchain/core/messages';
import { RunnableConfig } from '@langchain/core/runnables';
import { z } from 'zod';

// 定义状态验证模式
const UserProfileSchema = z.object({
id: z.string().uuid('用户ID必须是有效的UUID'),
name: z.string().min(1, '用户名不能为空').max(50, '用户名不能超过50个字符'),
email: z.string().email('邮箱格式不正确'),
age: z
.number()
.int('年龄必须是整数')
.min(0, '年龄不能为负数')
.max(150, '年龄不能超过150'),
preferences: z.object({
language: z.enum(['zh', 'en', 'es', 'fr'], {
errorMap: () => ({ message: '语言必须是支持的语言之一' }),
}),
theme: z.enum(['light', 'dark'], {
errorMap: () => ({ message: '主题必须是light或dark' }),
}),
}),
});

const MessageSchema = z.object({
content: z.string().min(1, '消息内容不能为空'),
timestamp: z.date(),
sender: z.enum(['user', 'assistant']),
});

// 定义状态
const StateAnnotation = Annotation.Root({
messages: Annotation<BaseMessage[]>({
reducer: (state: BaseMessage[], update: BaseMessage[]) =>
state.concat(update),
default: () => [],
}),
userProfile: Annotation<any>({
reducer: (state: any, update: any) => ({ ...state, ...update }),
default: () => ({}),
}),
validationErrors: Annotation<string[]>({
reducer: (state: string[], update: string[]) => state.concat(update),
default: () => [],
}),
isValid: Annotation<boolean>({
reducer: (state: boolean, update: boolean) => update,
default: () => true,
}),
});

// 状态验证错误类
class StateValidationError extends Error {
constructor(
message: string,
public field: string,
public value: any,
public validationRule: string
) {
super(message);
this.name = 'StateValidationError';
}
}

// 状态验证器
class StateValidator {
static validateUserProfile(profile: any): {
isValid: boolean;
errors: string[];
} {
try {
UserProfileSchema.parse(profile);
return { isValid: true, errors: [] };
} catch (error) {
if (error instanceof z.ZodError) {
const errors = error.errors.map(
(err) => `${err.path.join('.')}: ${err.message}`
);
return { isValid: false, errors };
}
return { isValid: false, errors: [error.message] };
}
}

static validateMessage(message: any): { isValid: boolean; errors: string[] } {
try {
MessageSchema.parse(message);
return { isValid: true, errors: [] };
} catch (error) {
if (error instanceof z.ZodError) {
const errors = error.errors.map(
(err) => `${err.path.join('.')}: ${err.message}`
);
return { isValid: false, errors };
}
return { isValid: false, errors: [error.message] };
}
}

static validateStateTransition(
currentState: any,
newState: any,
allowedTransitions: string[]
): { isValid: boolean; errors: string[] } {
const errors: string[] = [];

// 检查状态字段是否被意外删除
for (const key in currentState) {
if (currentState[key] !== undefined && newState[key] === undefined) {
errors.push(`状态字段 "${key}" 被意外删除`);
}
}

// 检查是否有不允许的状态转换
for (const key in newState) {
if (!allowedTransitions.includes(key)) {
errors.push(`不允许的状态转换: "${key}"`);
}
}

return { isValid: errors.length === 0, errors };
}
}

// 用户资料更新节点
async function updateUserProfileNode(
state: typeof StateAnnotation.State,
config?: RunnableConfig
) {
const lastMessage = state.messages[state.messages.length - 1];
const content = lastMessage.content as string;

console.log('更新用户资料:', content);

// 模拟从消息中提取用户资料更新
let profileUpdate: any = {};

if (content.includes('姓名')) {
profileUpdate.name = content.includes('无效') ? '' : '张三';
}
if (content.includes('邮箱')) {
profileUpdate.email = content.includes('无效')
? 'invalid-email'
: 'zhangsan@example.com';
}
if (content.includes('年龄')) {
profileUpdate.age = content.includes('无效') ? -5 : 25;
}
if (content.includes('语言')) {
profileUpdate.preferences = {
...state.userProfile.preferences,
language: content.includes('无效') ? 'invalid-lang' : 'zh',
};
}

// 验证更新的资料
const validation = StateValidator.validateUserProfile({
...state.userProfile,
...profileUpdate,
});

if (!validation.isValid) {
return {
messages: [
new AIMessage({
content: `用户资料验证失败: ${validation.errors.join(', ')}`,
}),
],
validationErrors: validation.errors,
isValid: false,
};
}

return {
messages: [
new AIMessage({
content: '用户资料更新成功',
}),
],
userProfile: profileUpdate,
isValid: true,
};
}

// 消息验证节点
async function validateMessageNode(
state: typeof StateAnnotation.State,
config?: RunnableConfig
) {
const lastMessage = state.messages[state.messages.length - 1];

// 验证消息格式
const messageData = {
content: lastMessage.content,
timestamp: new Date(),
sender: 'user' as const,
};

const validation = StateValidator.validateMessage(messageData);

if (!validation.isValid) {
return {
messages: [
new AIMessage({
content: `消息格式验证失败: ${validation.errors.join(', ')}`,
}),
],
validationErrors: validation.errors,
isValid: false,
};
}

return {
messages: [
new AIMessage({
content: '消息格式验证通过',
}),
],
isValid: true,
};
}

// 状态转换验证节点
async function validateStateTransitionNode(
state: typeof StateAnnotation.State,
config?: RunnableConfig
) {
const allowedTransitions = [
'messages',
'userProfile',
'validationErrors',
'isValid',
];

// 模拟状态更新
const newStateUpdate = {
messages: state.messages,
userProfile: state.userProfile,
// 模拟一个不允许的字段
unauthorizedField: 'should not be here',
};

const validation = StateValidator.validateStateTransition(
state,
newStateUpdate,
allowedTransitions
);

if (!validation.isValid) {
return {
messages: [
new AIMessage({
content: `状态转换验证失败: ${validation.errors.join(', ')}`,
}),
],
validationErrors: validation.errors,
isValid: false,
};
}

return {
messages: [
new AIMessage({
content: '状态转换验证通过',
}),
],
isValid: true,
};
}

// 错误恢复节点
async function errorRecoveryNode(
state: typeof StateAnnotation.State,
config?: RunnableConfig
) {
if (state.validationErrors.length === 0) {
return { messages: [] };
}

console.log('执行错误恢复,错误数量:', state.validationErrors.length);

// 分析错误类型并提供恢复建议
const suggestions: string[] = [];

for (const error of state.validationErrors) {
if (error.includes('邮箱')) {
suggestions.push('请提供有效的邮箱地址,格式如:user@example.com');
} else if (error.includes('年龄')) {
suggestions.push('请提供有效的年龄,范围在0-150之间');
} else if (error.includes('用户名')) {
suggestions.push('请提供1-50个字符的用户名');
} else if (error.includes('语言')) {
suggestions.push(
'请选择支持的语言:中文(zh)、英文(en)、西班牙文(es)、法文(fr)'
);
} else {
suggestions.push('请检查输入格式是否正确');
}
}

return {
messages: [
new AIMessage({
content: `检测到验证错误,建议:\n${suggestions
.map((s, i) => `${i + 1}. ${s}`)
.join('\n')}`,
}),
],
// 清空验证错误,准备重新验证
validationErrors: [],
};
}

// 路由函数
function shouldContinue(state: typeof StateAnnotation.State) {
if (!state.isValid && state.validationErrors.length > 0) {
return 'error_recovery';
}
return END;
}

// 构建图
const workflow = new StateGraph(StateAnnotation)
.addNode('update_profile', updateUserProfileNode)
.addNode('validate_message', validateMessageNode)
.addNode('validate_transition', validateStateTransitionNode)
.addNode('error_recovery', errorRecoveryNode)
.addEdge(START, 'validate_message')
.addEdge('validate_message', 'update_profile')
.addEdge('update_profile', 'validate_transition')
.addConditionalEdges('validate_transition', shouldContinue, {
error_recovery: 'error_recovery',
[END]: END,
})
.addEdge('error_recovery', END);

const app = workflow.compile();

// 高级状态验证器
class AdvancedStateValidator {
private validationRules: Map<string, (value: any) => boolean> = new Map();
private customValidators: Map<
string,
(state: any) => { isValid: boolean; errors: string[] }
> = new Map();

addValidationRule(field: string, validator: (value: any) => boolean) {
this.validationRules.set(field, validator);
}

addCustomValidator(
name: string,
validator: (state: any) => { isValid: boolean; errors: string[] }
) {
this.customValidators.set(name, validator);
}

validateState(state: any): { isValid: boolean; errors: string[] } {
const errors: string[] = [];

// 执行字段级验证
for (const [field, validator] of this.validationRules) {
if (state[field] !== undefined && !validator(state[field])) {
errors.push(`字段 "${field}" 验证失败`);
}
}

// 执行自定义验证
for (const [name, validator] of this.customValidators) {
const result = validator(state);
if (!result.isValid) {
errors.push(...result.errors.map((err) => `${name}: ${err}`));
}
}

return { isValid: errors.length === 0, errors };
}
}

// 状态快照管理器
class StateSnapshotManager {
private snapshots: Map<string, any> = new Map();
private maxSnapshots = 10;

saveSnapshot(id: string, state: any) {
// 深拷贝状态
const snapshot = JSON.parse(JSON.stringify(state));
this.snapshots.set(id, {
state: snapshot,
timestamp: new Date(),
});

// 限制快照数量
if (this.snapshots.size > this.maxSnapshots) {
const oldestKey = this.snapshots.keys().next().value;
this.snapshots.delete(oldestKey);
}
}

restoreSnapshot(id: string): any | null {
const snapshot = this.snapshots.get(id);
return snapshot ? snapshot.state : null;
}

listSnapshots(): Array<{ id: string; timestamp: Date }> {
return Array.from(this.snapshots.entries()).map(([id, data]) => ({
id,
timestamp: data.timestamp,
}));
}
}

// 使用示例
async function demonstrateStateValidation() {
console.log('=== 状态验证错误处理示例 ===\n');

const testCases = [
'更新用户姓名',
'更新用户邮箱',
'更新无效邮箱',
'更新用户年龄',
'更新无效年龄',
'更新用户语言',
'更新无效语言',
];

for (const testCase of testCases) {
console.log(`\n测试用例: ${testCase}`);
console.log('-'.repeat(30));

try {
const result = await app.invoke({
messages: [new HumanMessage({ content: testCase })],
userProfile: {
id: '123e4567-e89b-12d3-a456-426614174000',
name: '原始用户',
email: 'original@example.com',
age: 30,
preferences: {
language: 'zh',
theme: 'light',
},
},
});

console.log(
'处理结果:',
result.messages[result.messages.length - 1].content
);
if (result.validationErrors.length > 0) {
console.log('验证错误:', result.validationErrors);
}
console.log('状态有效:', result.isValid);
} catch (error) {
console.error('处理失败:', error.message);
}
}

console.log('\n' + '='.repeat(50));
console.log('高级状态验证示例:');

// 创建高级验证器
const advancedValidator = new AdvancedStateValidator();

// 添加自定义验证规则
advancedValidator.addValidationRule('userProfile.name', (value) => {
return typeof value === 'string' && value.length >= 2;
});

advancedValidator.addCustomValidator('profileConsistency', (state) => {
const errors: string[] = [];

if (state.userProfile.email && state.userProfile.name) {
const emailPrefix = state.userProfile.email.split('@')[0];
if (
!state.userProfile.name
.toLowerCase()
.includes(emailPrefix.toLowerCase())
) {
errors.push('用户名与邮箱前缀不匹配');
}
}

return { isValid: errors.length === 0, errors };
});

// 测试高级验证
const testState = {
userProfile: {
name: 'John',
email: 'jane@example.com', // 故意不匹配
},
};

const validationResult = advancedValidator.validateState(testState);
console.log('高级验证结果:', validationResult);

// 状态快照示例
console.log('\n状态快照管理示例:');
const snapshotManager = new StateSnapshotManager();

snapshotManager.saveSnapshot('initial', {
userProfile: { name: 'Initial User' },
});
snapshotManager.saveSnapshot('updated', {
userProfile: { name: 'Updated User' },
});

console.log('可用快照:', snapshotManager.listSnapshots());
console.log('恢复初始快照:', snapshotManager.restoreSnapshot('initial'));
}

// 运行示例
if (require.main === module) {
demonstrateStateValidation().catch(console.error);
}

export {
app,
StateValidator,
AdvancedStateValidator,
StateSnapshotManager,
StateValidationError,
demonstrateStateValidation,
};

/*执行结果: 状态验证错误处理测试通过,检测状态字段删除和未授权转换*/

/*
执行结果:
Line:8 🌭 path.resolve(__dirname, '.env') /Users/loock/myFile/langgraphjs-tutorial/websites/examples/utils/.env
=== 状态验证错误处理示例 ===


测试用例: 更新用户姓名
------------------------------
更新用户资料: 消息格式验证通过
执行错误恢复,错误数量: 3
处理结果: 检测到验证错误,建议:
1. 请检查输入格式是否正确
2. 请检查输入格式是否正确
3. 请检查输入格式是否正确
验证错误: [
'状态字段 "validationErrors" 被意外删除',
'状态字段 "isValid" 被意外删除',
'不允许的状态转换: "unauthorizedField"'
]
状态有效: false

测试用例: 更新用户邮箱
------------------------------
更新用户资料: 消息格式验证通过
执行错误恢复,错误数量: 3
处理结果: 检测到验证错误,建议:
1. 请检查输入格式是否正确
2. 请检查输入格式是否正确
3. 请检查输入格式是否正确
验证错误: [
'状态字段 "validationErrors" 被意外删除',
'状态字段 "isValid" 被意外删除',
'不允许的状态转换: "unauthorizedField"'
]
状态有效: false

测试用例: 更新无效邮箱
------------------------------
更新用户资料: 消息格式验证通过
执行错误恢复,错误数量: 3
处理结果: 检测到验证错误,建议:
1. 请检查输入格式是否正确
2. 请检查输入格式是否正确
3. 请检查输入格式是否正确
验证错误: [
'状态字段 "validationErrors" 被意外删除',
'状态字段 "isValid" 被意外删除',
'不允许的状态转换: "unauthorizedField"'
]
状态有效: false

测试用例: 更新用户年龄
------------------------------
更新用户资料: 消息格式验证通过
执行错误恢复,错误数量: 3
处理结果: 检测到验证错误,建议:
1. 请检查输入格式是否正确
2. 请检查输入格式是否正确
3. 请检查输入格式是否正确
验证错误: [
'状态字段 "validationErrors" 被意外删除',
'状态字段 "isValid" 被意外删除',
'不允许的状态转换: "unauthorizedField"'
]
状态有效: false

测试用例: 更新无效年龄
------------------------------
更新用户资料: 消息格式验证通过
执行错误恢复,错误数量: 3
处理结果: 检测到验证错误,建议:
1. 请检查输入格式是否正确
2. 请检查输入格式是否正确
3. 请检查输入格式是否正确
验证错误: [
'状态字段 "validationErrors" 被意外删除',
'状态字段 "isValid" 被意外删除',
'不允许的状态转换: "unauthorizedField"'
]
状态有效: false

测试用例: 更新用户语言
------------------------------
更新用户资料: 消息格式验证通过
执行错误恢复,错误数量: 3
处理结果: 检测到验证错误,建议:
1. 请检查输入格式是否正确
2. 请检查输入格式是否正确
3. 请检查输入格式是否正确
验证错误: [
'状态字段 "validationErrors" 被意外删除',
'状态字段 "isValid" 被意外删除',
'不允许的状态转换: "unauthorizedField"'
]
状态有效: false

测试用例: 更新无效语言
------------------------------
更新用户资料: 消息格式验证通过
执行错误恢复,错误数量: 3
处理结果: 检测到验证错误,建议:
1. 请检查输入格式是否正确
2. 请检查输入格式是否正确
3. 请检查输入格式是否正确
验证错误: [
'状态字段 "validationErrors" 被意外删除',
'状态字段 "isValid" 被意外删除',
'不允许的状态转换: "unauthorizedField"'
]
状态有效: false

==================================================
高级状态验证示例:
高级验证结果: { isValid: false, errors: [ 'profileConsistency: 用户名与邮箱前缀不匹配' ] }

状态快照管理示例:
可用快照: [
{ id: 'initial', timestamp: 2025-11-16T16:25:22.158Z },
{ id: 'updated', timestamp: 2025-11-16T16:25:22.158Z }
]
恢复初始快照: { userProfile: { name: 'Initial User' } }
*/

网络错误和重试机制

网络错误重试机制
/**
* ============================================================================
* 网络错误重试机制 - Network Retry Mechanism
* ============================================================================
*
* 📖 概述
* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
* 本示例展示如何实现网络请求的自动重试机制,使用指数退避策略和随机抖动算法,
* 提高网络请求的成功率和系统的容错能力。
*
* 🎯 核心功能
* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
* 1️⃣ 指数退避重试:延迟时间呈指数增长,避免网络拥塞
* 2️⃣ 随机抖动算法:添加随机延迟,防止重试风暴
* 3️⃣ 网络错误分类:根据状态码判断是否应该重试
* 4️⃣ 重试状态追踪:记录重试次数和错误历史
* 5️⃣ 条件路由机制:根据重试次数和错误类型决定后续流程
*
* 💡 实现思路
* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
* • 使用指数退避算法:delay = baseDelay * (2 ^ attempt)
* • 添加随机抖动避免惊群效应:finalDelay = delay + random(0, 0.1 * delay)
* • 设置最大延迟上限,防止等待时间过长
* • 针对 5xx 和 429 错误实施重试策略
* • 通过条件边实现自动重试循环
*
* 🚀 使用方式
* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
* // 运行示例
* $ npx esno 实用功能/错误处理/network-retry.ts
*
* ⚠️ 注意事项
* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
* • 重试次数不宜过多,建议设置在 3-5 次之间
* • 4xx 客户端错误通常不应该重试(除了 429)
* • 注意设置合理的超时时间,避免长时间等待
* • 对于关键业务,应该记录重试日志以便排查问题
*
* @author 程哥
* @version 1.0.0
* @updated 2025-11
*/

import '../../utils/loadEnv';
import { StateGraph, Annotation, START, END } from '@langchain/langgraph';
import { BaseMessage, HumanMessage, AIMessage } from '@langchain/core/messages';
import { RunnableConfig } from '@langchain/core/runnables';

// 定义状态
const StateAnnotation = Annotation.Root({
messages: Annotation<BaseMessage[]>({
reducer: (state: BaseMessage[], update: BaseMessage[]) =>
state.concat(update),
default: () => [],
}),
retryCount: Annotation<number>({
reducer: (state: number, update: number) => update,
default: () => 0,
}),
maxRetries: Annotation<number>({
reducer: (state: number, update: number) => update,
default: () => 3,
}),
lastError: Annotation<string>({
reducer: (state: string, update: string) => update,
default: () => '',
}),
});

// 网络错误类
class NetworkError extends Error {
constructor(
message: string,
public statusCode?: number,
public isRetryable = true
) {
super(message);
this.name = 'NetworkError';
}
}

// 指数退避重试策略
class ExponentialBackoffRetry {
static async execute<T>(
operation: () => Promise<T>,
maxRetries = 3,
baseDelay = 1000,
maxDelay = 30000,
backoffMultiplier = 2
): Promise<T> {
let lastError: Error;

for (let attempt = 0; attempt <= maxRetries; attempt++) {
try {
return await operation();
} catch (error) {
lastError = error as Error;

if (attempt === maxRetries) {
break;
}

// 计算延迟时间
const delay = Math.min(
baseDelay * Math.pow(backoffMultiplier, attempt),
maxDelay
);

// 添加随机抖动
const jitter = Math.random() * 0.1 * delay;
const finalDelay = delay + jitter;

console.log(
`尝试 ${attempt + 1} 失败,${Math.round(finalDelay)}ms 后重试:`,
error.message
);
await new Promise((resolve) => setTimeout(resolve, finalDelay));
}
}

throw lastError!;
}
}

// 模拟网络请求节点
async function networkRequestNode(
state: typeof StateAnnotation.State,
config?: RunnableConfig
) {
const lastMessage = state.messages[state.messages.length - 1];
const content = lastMessage.content as string;

console.log(`执行网络请求: ${content}`);

// 模拟不同类型的网络错误
if (content.includes('超时')) {
throw new NetworkError('请求超时', 408);
} else if (content.includes('服务器错误')) {
throw new NetworkError('内部服务器错误', 500);
} else if (content.includes('限流')) {
throw new NetworkError('请求过于频繁', 429);
} else if (content.includes('网络')) {
// 随机失败
if (Math.random() < 0.7) {
throw new NetworkError('网络连接失败', 0);
}
}

return {
messages: [
new AIMessage({
content: `网络请求成功: ${content}`,
}),
],
};
}

// 重试节点
async function retryNode(
state: typeof StateAnnotation.State,
config?: RunnableConfig
) {
if (state.retryCount >= state.maxRetries) {
return {
messages: [
new AIMessage({
content: `重试次数已达上限 (${state.maxRetries}),请求失败: ${state.lastError}`,
}),
],
};
}

const newRetryCount = state.retryCount + 1;
console.log(`执行第 ${newRetryCount} 次重试`);

// 指数退避延迟
const delay = Math.pow(2, state.retryCount) * 1000;
await new Promise((resolve) => setTimeout(resolve, delay));

return {
retryCount: newRetryCount,
messages: [
new HumanMessage({
content: state.messages[0].content,
}),
],
};
}

// 安全网络请求节点
async function safeNetworkRequestNode(
state: typeof StateAnnotation.State,
config?: RunnableConfig
) {
try {
return await networkRequestNode(state, config);
} catch (error) {
const networkError = error as NetworkError;
console.error(`网络请求失败:`, networkError.message);

return {
lastError: networkError.message,
};
}
}

// 路由函数
function shouldRetry(state: typeof StateAnnotation.State) {
if (state.lastError && state.retryCount < state.maxRetries) {
return 'retry';
}
return END;
}

// 构建图
const workflow = new StateGraph(StateAnnotation)
.addNode('network_request', safeNetworkRequestNode)
.addNode('retry', retryNode)
.addEdge(START, 'network_request')
.addConditionalEdges('network_request', shouldRetry, {
retry: 'retry',
[END]: END,
})
.addEdge('retry', 'network_request');

const app = workflow.compile();

// 使用示例
async function demonstrateNetworkRetry() {
console.log('=== 网络错误重试机制示例 ===\n');

const testCases = [
'正常网络请求',
'网络连接不稳定',
'请求超时测试',
'服务器错误测试',
'请求限流测试',
];

for (const testCase of testCases) {
console.log(`\n测试用例: ${testCase}`);
console.log('-'.repeat(30));

try {
const result = await app.invoke({
messages: [new HumanMessage({ content: testCase })],
maxRetries: 3,
});

console.log(
'最终结果:',
result.messages[result.messages.length - 1].content
);
console.log('重试次数:', result.retryCount);
} catch (error) {
console.error('处理失败:', error.message);
}
}

console.log('\n' + '='.repeat(50));
console.log('指数退避重试示例:');

// 使用指数退避重试
try {
let attemptCount = 0;
const result = await ExponentialBackoffRetry.execute(
async () => {
attemptCount++;
if (attemptCount < 3) {
throw new NetworkError(`模拟网络失败 ${attemptCount}`);
}
return `${attemptCount} 次尝试成功`;
},
3,
500
);
console.log('指数退避结果:', result);
} catch (error) {
console.error('指数退避失败:', error.message);
}
}

// 运行示例
if (require.main === module) {
demonstrateNetworkRetry().catch(console.error);
}

export { app, NetworkError, ExponentialBackoffRetry, demonstrateNetworkRetry };

/*执行结果: 网络重试机制测试通过,包括正常请求、连接不稳定、请求超时的重试*/

/*
执行结果:
Line:8 🌭 path.resolve(__dirname, '.env') /Users/loock/myFile/langgraphjs-tutorial/websites/examples/utils/.env
=== 网络错误重试机制示例 ===


测试用例: 正常网络请求
------------------------------
执行网络请求: 正常网络请求
执行第 1 次重试
执行网络请求: 正常网络请求
执行第 2 次重试
执行网络请求: 正常网络请求
执行第 3 次重试
执行网络请求: 正常网络请求
最终结果: 正常网络请求
重试次数: 3

测试用例: 网络连接不稳定
------------------------------
执行网络请求: 网络连接不稳定
执行第 1 次重试
执行网络请求: 网络连接不稳定
执行第 2 次重试
执行网络请求: 网络连接不稳定
执行第 3 次重试
执行网络请求: 网络连接不稳定
最终结果: 网络请求成功: 网络连接不稳定
重试次数: 3

测试用例: 请求超时测试
------------------------------
执行网络请求: 请求超时测试
执行第 1 次重试
执行网络请求: 请求超时测试
执行第 2 次重试
执行网络请求: 请求超时测试
执行第 3 次重试
执行网络请求: 请求超时测试
最终结果: 请求超时测试
重试次数: 3

测试用例: 服务器错误测试
------------------------------
执行网络请求: 服务器错误测试
执行第 1 次重试
执行网络请求: 服务器错误测试
执行第 2 次重试
执行网络请求: 服务器错误测试
执行第 3 次重试
执行网络请求: 服务器错误测试
最终结果: 服务器错误测试
重试次数: 3

测试用例: 请求限流测试
------------------------------
执行网络请求: 请求限流测试
执行第 1 次重试
执行网络请求: 请求限流测试
执行第 2 次重试
执行网络请求: 请求限流测试
执行第 3 次重试
执行网络请求: 请求限流测试
最终结果: 请求限流测试
重试次数: 3

==================================================
指数退避重试示例:
尝试 1 失败,515ms 后重试: 模拟网络失败 1
尝试 2 失败,1094ms 后重试: 模拟网络失败 2
指数退避结果: 第 3 次尝试成功
*/

自定义错误处理

错误分类和处理策略

自定义错误处理器
/**
* ============================================================================
* 自定义错误处理器 - Custom Error Handler
* ============================================================================
*
* 📖 概述
* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
* 本示例展示如何在 LangGraph 中实现自定义错误处理系统,通过责任链模式构建
* 可扩展的错误处理器管理器,针对不同类型的错误提供特定的处理策略和恢复方案。
*
* 🎯 核心功能
* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
* 1️⃣ 自定义错误类型:定义 ValidationError、BusinessLogicError、ResourceError 等
* 2️⃣ 错误处理器接口:统一的错误处理器接口,支持优先级排序
* 3️⃣ 错误处理管理器:责任链模式,自动匹配合适的处理器
* 4️⃣ 错误恢复策略:根据错误类型提供恢复建议和备用资源
* 5️⃣ 状态更新机制:错误信息融入状态,支持后续处理
*
* 💡 实现思路
* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
* • 使用责任链模式实现错误处理器链,按优先级依次匹配
* • 每个错误类型对应一个专门的处理器,提供针对性的错误消息
* • 通过 canHandle 方法判断处理器是否能处理当前错误
* • 支持错误分类、恢复建议、备用方案等高级功能
* • 条件路由根据错误是否可恢复决定后续流程
*
* 🚀 使用方式
* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
* // 运行示例
* $ npx esno 实用功能/错误处理/custom-error-handler.ts
*
* ⚠️ 注意事项
* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
* • 错误处理器的优先级顺序很重要,具体处理器应放在通用处理器之前
* • 确保 GenericErrorHandler 始终作为最后的兜底处理器
* • 错误消息应该对用户友好,不应暴露内部实现细节
* • 可恢复性标记影响后续流程,需要根据实际情况设置
*
* @author 程哥
* @version 1.0.0
* @updated 2025-11
*/

import '../../utils/loadEnv';
import { StateGraph, Annotation, START, END } from '@langchain/langgraph';
import { BaseMessage, HumanMessage, AIMessage } from '@langchain/core/messages';
import { RunnableConfig } from '@langchain/core/runnables';

// 自定义错误类型
class ValidationError extends Error {
constructor(message: string, public field: string) {
super(message);
this.name = 'ValidationError';
}
}

class BusinessLogicError extends Error {
constructor(message: string, public code: string) {
super(message);
this.name = 'BusinessLogicError';
}
}

class ResourceError extends Error {
constructor(message: string, public resourceType: string) {
super(message);
this.name = 'ResourceError';
}
}

// 错误处理器接口
interface ErrorHandler {
canHandle(error: Error): boolean;
handle(error: Error, context: any): Promise<any>;
getPriority(): number;
}

// 验证错误处理器
class ValidationErrorHandler implements ErrorHandler {
getPriority(): number {
return 1;
}

canHandle(error: Error): boolean {
return error instanceof ValidationError;
}

async handle(error: ValidationError, context: any): Promise<any> {
console.log(`处理验证错误: ${error.message} (字段: ${error.field})`);

return {
messages: [
new AIMessage({
content: `输入验证失败: ${error.message}。请检查 "${error.field}" 字段的值。`,
}),
],
errorType: 'validation',
recoverable: true,
};
}
}

// 业务逻辑错误处理器
class BusinessLogicErrorHandler implements ErrorHandler {
getPriority(): number {
return 2;
}

canHandle(error: Error): boolean {
return error instanceof BusinessLogicError;
}

async handle(error: BusinessLogicError, context: any): Promise<any> {
console.log(`处理业务逻辑错误: ${error.message} (代码: ${error.code})`);

const suggestion = this.getSuggestion(error.code);

return {
messages: [
new AIMessage({
content: `业务处理失败: ${error.message}${suggestion}`,
}),
],
errorType: 'business',
recoverable: this.isRecoverable(error.code),
};
}

private getSuggestion(code: string): string {
const suggestions: Record<string, string> = {
INSUFFICIENT_FUNDS: '请检查账户余额或联系客服。',
INVALID_OPERATION: '此操作在当前状态下不被允许。',
QUOTA_EXCEEDED: '已达到使用限额,请升级服务或稍后重试。',
PERMISSION_DENIED: '权限不足,请联系管理员。',
};

return suggestions[code] || '请联系技术支持获取帮助。';
}

private isRecoverable(code: string): boolean {
const recoverableCodes = ['INSUFFICIENT_FUNDS', 'QUOTA_EXCEEDED'];
return recoverableCodes.includes(code);
}
}

// 资源错误处理器
class ResourceErrorHandler implements ErrorHandler {
getPriority(): number {
return 3;
}

canHandle(error: Error): boolean {
return error instanceof ResourceError;
}

async handle(error: ResourceError, context: any): Promise<any> {
console.log(
`处理资源错误: ${error.message} (资源类型: ${error.resourceType})`
);

const fallback = this.getFallbackResource(error.resourceType);

return {
messages: [
new AIMessage({
content: `资源访问失败: ${error.message}${
fallback ? `已切换到备用资源: ${fallback}` : '请稍后重试。'
}`,
}),
],
errorType: 'resource',
recoverable: !!fallback,
fallbackResource: fallback,
};
}

private getFallbackResource(resourceType: string): string | null {
const fallbacks: Record<string, string> = {
database: 'backup-database',
api: 'fallback-api',
cache: 'memory-cache',
};

return fallbacks[resourceType] || null;
}
}

// 通用错误处理器
class GenericErrorHandler implements ErrorHandler {
getPriority(): number {
return 999; // 最低优先级
}

canHandle(error: Error): boolean {
return true; // 处理所有其他错误
}

async handle(error: Error, context: any): Promise<any> {
console.log(`处理通用错误: ${error.message}`);

return {
messages: [
new AIMessage({
content: `系统遇到未知错误: ${error.message}。请稍后重试或联系技术支持。`,
}),
],
errorType: 'unknown',
recoverable: false,
};
}
}

// 错误处理管理器
class ErrorHandlerManager {
private handlers: ErrorHandler[] = [];

constructor() {
this.registerHandler(new ValidationErrorHandler());
this.registerHandler(new BusinessLogicErrorHandler());
this.registerHandler(new ResourceErrorHandler());
this.registerHandler(new GenericErrorHandler());

// 按优先级排序
this.handlers.sort((a, b) => a.getPriority() - b.getPriority());
}

registerHandler(handler: ErrorHandler) {
this.handlers.push(handler);
}

async handleError(error: Error, context: any): Promise<any> {
for (const handler of this.handlers) {
if (handler.canHandle(error)) {
return await handler.handle(error, context);
}
}

throw new Error('No suitable error handler found');
}
}

// 定义状态
const StateAnnotation = Annotation.Root({
messages: Annotation<BaseMessage[]>({
reducer: (state: BaseMessage[], update: BaseMessage[]) =>
state.concat(update),
default: () => [],
}),
errorType: Annotation<string>({
reducer: (state: string, update: string) => update,
default: () => '',
}),
recoverable: Annotation<boolean>({
reducer: (state: boolean, update: boolean) => update,
default: () => true,
}),
fallbackResource: Annotation<string>({
reducer: (state: string, update: string) => update,
default: () => '',
}),
});

// 创建错误处理管理器实例
const errorManager = new ErrorHandlerManager();

// 模拟可能出错的处理节点
async function riskyOperationNode(
state: typeof StateAnnotation.State,
config?: RunnableConfig
) {
const lastMessage = state.messages[state.messages.length - 1];
const content = lastMessage.content as string;

console.log(`执行操作: ${content}`);

// 模拟各种错误
if (content.includes('验证')) {
throw new ValidationError('邮箱格式不正确', 'email');
} else if (content.includes('余额')) {
throw new BusinessLogicError('账户余额不足', 'INSUFFICIENT_FUNDS');
} else if (content.includes('权限')) {
throw new BusinessLogicError('权限不足', 'PERMISSION_DENIED');
} else if (content.includes('数据库')) {
throw new ResourceError('数据库连接失败', 'database');
} else if (content.includes('API')) {
throw new ResourceError('外部API不可用', 'api');
} else if (content.includes('未知')) {
throw new Error('未知的系统错误');
}

return {
messages: [
new AIMessage({
content: `操作成功完成: ${content}`,
}),
],
};
}

// 安全操作节点
async function safeOperationNode(
state: typeof StateAnnotation.State,
config?: RunnableConfig
) {
try {
return await riskyOperationNode(state, config);
} catch (error) {
console.error('操作失败:', error.message);

// 使用错误处理管理器处理错误
return await errorManager.handleError(error as Error, state);
}
}

// 恢复节点
async function recoveryNode(
state: typeof StateAnnotation.State,
config?: RunnableConfig
) {
if (!state.recoverable) {
return {
messages: [
new AIMessage({
content: '错误无法恢复,操作已终止。',
}),
],
};
}

console.log(`尝试恢复,错误类型: ${state.errorType}`);

// 根据错误类型执行不同的恢复策略
switch (state.errorType) {
case 'validation':
return {
messages: [
new AIMessage({
content: '请重新输入正确的信息后再试。',
}),
],
};

case 'business':
return {
messages: [
new AIMessage({
content: '已为您提供替代方案,请查看建议。',
}),
],
};

case 'resource':
if (state.fallbackResource) {
return {
messages: [
new AIMessage({
content: `已切换到备用资源 "${state.fallbackResource}",请重试操作。`,
}),
],
};
}
break;
}

return {
messages: [
new AIMessage({
content: '系统正在尝试自动恢复,请稍候。',
}),
],
};
}

// 路由函数
function shouldRecover(state: typeof StateAnnotation.State) {
if (state.errorType && state.recoverable) {
return 'recovery';
}
return END;
}

// 构建图
const workflow = new StateGraph(StateAnnotation)
.addNode('operation', safeOperationNode)
.addNode('recovery', recoveryNode)
.addEdge(START, 'operation')
.addConditionalEdges('operation', shouldRecover, {
recovery: 'recovery',
[END]: END,
})
.addEdge('recovery', END);

const app = workflow.compile();

// 使用示例
async function demonstrateCustomErrorHandler() {
console.log('=== 自定义错误处理器示例 ===\n');

const testCases = [
'正常操作',
'验证用户邮箱',
'检查账户余额',
'验证用户权限',
'访问数据库',
'调用外部API',
'触发未知错误',
];

for (const testCase of testCases) {
console.log(`\n测试用例: ${testCase}`);
console.log('-'.repeat(30));

try {
const result = await app.invoke({
messages: [new HumanMessage({ content: testCase })],
});

console.log(
'处理结果:',
result.messages[result.messages.length - 1].content
);
if (result.errorType) {
console.log('错误类型:', result.errorType);
console.log('可恢复:', result.recoverable);
if (result.fallbackResource) {
console.log('备用资源:', result.fallbackResource);
}
}
} catch (error) {
console.error('处理失败:', error.message);
}
}
}

// 运行示例
if (require.main === module) {
demonstrateCustomErrorHandler().catch(console.error);
}

export {
app,
ErrorHandlerManager,
ValidationError,
BusinessLogicError,
ResourceError,
demonstrateCustomErrorHandler,
};

/*
执行结果:
Line:8 🌭 path.resolve(__dirname, '.env') /Users/loock/myFile/langgraphjs-tutorial/websites/examples/utils/.env
=== 自定义错误处理器示例 ===


测试用例: 正常操作
------------------------------
执行操作: 正常操作
处理结果: 操作成功完成: 正常操作

测试用例: 验证用户邮箱
------------------------------
执行操作: 验证用户邮箱
处理验证错误: 邮箱格式不正确 (字段: email)
尝试恢复,错误类型: validation
处理结果: 请重新输入正确的信息后再试。
错误类型: validation
可恢复: true

测试用例: 检查账户余额
------------------------------
执行操作: 检查账户余额
处理业务逻辑错误: 账户余额不足 (代码: INSUFFICIENT_FUNDS)
尝试恢复,错误类型: business
处理结果: 已为您提供替代方案,请查看建议。
错误类型: business
可恢复: true

测试用例: 验证用户权限
------------------------------
执行操作: 验证用户权限
处理验证错误: 邮箱格式不正确 (字段: email)
尝试恢复,错误类型: validation
处理结果: 请重新输入正确的信息后再试。
错误类型: validation
可恢复: true

测试用例: 访问数据库
------------------------------
执行操作: 访问数据库
处理资源错误: 数据库连接失败 (资源类型: database)
尝试恢复,错误类型: resource
处理结果: 已切换到备用资源 "backup-database",请重试操作。
错误类型: resource
可恢复: true
备用资源: backup-database

测试用例: 调用外部API
------------------------------
执行操作: 调用外部API
处理资源错误: 外部API不可用 (资源类型: api)
尝试恢复,错误类型: resource
处理结果: 已切换到备用资源 "fallback-api",请重试操作。
错误类型: resource
可恢复: true
备用资源: fallback-api

测试用例: 触发未知错误
------------------------------
执行操作: 触发未知错误
处理通用错误: 未知的系统错误
处理结果: 系统遇到未知错误: 未知的系统错误。请稍后重试或联系技术支持。
错误类型: unknown
可恢复: false
*/

错误监控和日志记录

错误监控系统
/**
* ============================================================================
* 错误监控与日志 - Error Monitoring and Logging
* ============================================================================
*
* 📖 概述
* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
* 本示例展示如何构建错误监控和日志记录系统,实现错误统计、趋势分析、告警通知
* 等功能,帮助开发者及时发现和解决生产环境中的问题。
*
* 🎯 核心功能
* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
* 1️⃣ 错误统计记录:记录错误类型、次数和发生时间
* 2️⃣ 错误历史追踪:保存错误历史记录,支持时间窗口查询
* 3️⃣ 告警阈值机制:错误达到阈值时自动触发告警
* 4️⃣ 错误趋势分析:统计特定时间窗口内的错误分布
* 5️⃣ 分级日志系统:支持 debug、info、warn、error 四个级别
*
* 💡 实现思路
* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
* • 使用 Map 数据结构维护错误计数器和阈值配置
* • 错误历史使用数组存储,设置最大容量防止内存溢出
* • 根据错误类型自动匹配告警阈值,超出时触发通知
* • 时间窗口过滤实现错误趋势分析
* • 日志级别过滤机制控制输出详细程度
*
* 🚀 使用方式
* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
* // 运行示例
* $ npx esno 实用功能/错误处理/error-monitoring.ts
*
* ⚠️ 注意事项
* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
* • 生产环境应将日志输出到专业的日志服务(如 ELK、Datadog)
* • 错误历史大小需要根据实际场景调整,避免内存问题
* • 告警机制应该集成实际的通知渠道(邮件、短信、Slack 等)
* • 敏感信息不应记录到日志中
*
* @author 程哥
* @version 1.0.0
* @updated 2025-11
*/

import '../../utils/loadEnv';
// 错误监控和日志记录系统
class ErrorMonitor {
private errorCounts: Map<string, number> = new Map();
private errorHistory: Array<{ error: Error; timestamp: Date; context: any }> =
[];
private alertThresholds: Map<string, number> = new Map();
private maxHistorySize = 1000;

constructor() {
// 设置默认告警阈值
this.alertThresholds.set('NetworkError', 5);
this.alertThresholds.set('ValidationError', 10);
this.alertThresholds.set('BusinessLogicError', 3);
}

recordError(error: Error, context: any = {}) {
const errorType = error.constructor.name;
const count = this.errorCounts.get(errorType) || 0;
this.errorCounts.set(errorType, count + 1);

// 记录错误历史
this.errorHistory.push({
error,
timestamp: new Date(),
context,
});

// 限制历史记录大小
if (this.errorHistory.length > this.maxHistorySize) {
this.errorHistory.shift();
}

// 检查是否需要告警
const threshold = this.alertThresholds.get(errorType) || 10;
if (count + 1 >= threshold) {
this.sendAlert(errorType, count + 1, error);
}

console.log(`📊 错误记录: ${errorType} (总计: ${count + 1})`);
}

private sendAlert(errorType: string, count: number, error: Error) {
console.warn(`🚨 错误告警: ${errorType} 发生了 ${count}`);
console.warn(`最新错误: ${error.message}`);

// 这里可以集成实际的告警系统
// 例如:发送邮件、Slack通知、短信等
}

getErrorStats() {
return {
totalErrors: this.errorHistory.length,
errorCounts: Object.fromEntries(this.errorCounts),
recentErrors: this.errorHistory.slice(-10),
};
}

getErrorTrends(timeWindow = 3600000) {
// 默认1小时
const now = new Date();
const windowStart = new Date(now.getTime() - timeWindow);

const recentErrors = this.errorHistory.filter(
(entry) => entry.timestamp >= windowStart
);

const trends: Record<string, number> = {};
recentErrors.forEach((entry) => {
const errorType = entry.error.constructor.name;
trends[errorType] = (trends[errorType] || 0) + 1;
});

return trends;
}
}

// 全局错误监控实例
export const globalErrorMonitor = new ErrorMonitor();

// 错误日志记录器
class ErrorLogger {
private logLevel: 'debug' | 'info' | 'warn' | 'error' = 'info';

setLogLevel(level: 'debug' | 'info' | 'warn' | 'error') {
this.logLevel = level;
}

log(level: 'debug' | 'info' | 'warn' | 'error', message: string, data?: any) {
const levels = { debug: 0, info: 1, warn: 2, error: 3 };
if (levels[level] >= levels[this.logLevel]) {
const timestamp = new Date().toISOString();
const logEntry = {
timestamp,
level,
message,
data,
};

console.log(
`[${timestamp}] ${level.toUpperCase()}: ${message}`,
data || ''
);

// 这里可以集成实际的日志系统
// 例如:写入文件、发送到日志服务等
}
}

debug(message: string, data?: any) {
this.log('debug', message, data);
}

info(message: string, data?: any) {
this.log('info', message, data);
}

warn(message: string, data?: any) {
this.log('warn', message, data);
}

error(message: string, data?: any) {
this.log('error', message, data);
}
}

export const logger = new ErrorLogger();

// 使用示例
export function demonstrateErrorMonitoring() {
console.log('=== 错误监控系统示例 ===\n');

// 模拟一些错误
const errors = [
new Error('网络连接失败'),
new Error('数据库查询超时'),
new Error('验证失败'),
new Error('网络连接失败'), // 重复错误
new Error('权限不足'),
];

errors.forEach((error, index) => {
setTimeout(() => {
globalErrorMonitor.recordError(error, { userId: `user${index}` });
logger.error(`处理错误 ${index + 1}`, { error: error.message });
}, index * 100);
});

// 延迟显示统计信息
setTimeout(() => {
console.log('\n错误统计:', globalErrorMonitor.getErrorStats());
console.log('错误趋势:', globalErrorMonitor.getErrorTrends());
}, 1000);
}

// 运行示例
if (require.main === module) {
demonstrateErrorMonitoring();
}

/*
执行结果:
Line:8 🌭 path.resolve(__dirname, '.env') /Users/loock/myFile/langgraphjs-tutorial/websites/examples/utils/.env
=== 错误监控系统示例 ===

📊 错误记录: Error (总计: 1)
[2025-11-16T12:28:59.817Z] ERROR: 处理错误 1 { error: '网络连接失败' }
📊 错误记录: Error (总计: 2)
[2025-11-16T12:28:59.918Z] ERROR: 处理错误 2 { error: '数据库查询超时' }
📊 错误记录: Error (总计: 3)
[2025-11-16T12:29:00.018Z] ERROR: 处理错误 3 { error: '验证失败' }
📊 错误记录: Error (总计: 4)
[2025-11-16T12:29:00.119Z] ERROR: 处理错误 4 { error: '网络连接失败' }
📊 错误记录: Error (总计: 5)
[2025-11-16T12:29:00.218Z] ERROR: 处理错误 5 { error: '权限不足' }

错误统计: {
totalErrors: 5,
errorCounts: { Error: 5 },
recentErrors: [
{
error: Error: 网络连接失败
at demonstrateErrorMonitoring (/Users/loock/myFile/langgraphjs-tutorial/websites/examples/实用功能/错误处理/error-monitoring.ts:178:5)
at <anonymous> (/Users/loock/myFile/langgraphjs-tutorial/websites/examples/实用功能/错误处理/error-monitoring.ts:201:3)
at Object.<anonymous> (/Users/loock/myFile/langgraphjs-tutorial/websites/examples/实用功能/错误处理/error-monitoring.ts:202:1)
at Module._compile (node:internal/modules/cjs/loader:1521:14)
at Object.transformer (/Users/loock/myFile/langgraphjs-tutorial/websites/node_modules/.pnpm/tsx@4.20.3/node_modules/tsx/dist/register-D46fvsV_.cjs:3:1104)
at Module.load (node:internal/modules/cjs/loader:1266:32)
at Module._load (node:internal/modules/cjs/loader:1091:12)
at cjsLoader (node:internal/modules/esm/translators:298:15)
at ModuleWrap.<anonymous> (node:internal/modules/esm/translators:240:7)
at ModuleJob.run (node:internal/modules/esm/module_job:325:25),
timestamp: 2025-11-16T12:28:59.817Z,
context: [Object]
},
{
error: Error: 数据库查询超时
at demonstrateErrorMonitoring (/Users/loock/myFile/langgraphjs-tutorial/websites/examples/实用功能/错误处理/error-monitoring.ts:179:5)
at <anonymous> (/Users/loock/myFile/langgraphjs-tutorial/websites/examples/实用功能/错误处理/error-monitoring.ts:201:3)
at Object.<anonymous> (/Users/loock/myFile/langgraphjs-tutorial/websites/examples/实用功能/错误处理/error-monitoring.ts:202:1)
at Module._compile (node:internal/modules/cjs/loader:1521:14)
at Object.transformer (/Users/loock/myFile/langgraphjs-tutorial/websites/node_modules/.pnpm/tsx@4.20.3/node_modules/tsx/dist/register-D46fvsV_.cjs:3:1104)
at Module.load (node:internal/modules/cjs/loader:1266:32)
at Module._load (node:internal/modules/cjs/loader:1091:12)
at cjsLoader (node:internal/modules/esm/translators:298:15)
at ModuleWrap.<anonymous> (node:internal/modules/esm/translators:240:7)
at ModuleJob.run (node:internal/modules/esm/module_job:325:25),
timestamp: 2025-11-16T12:28:59.918Z,
context: [Object]
},
{
error: Error: 验证失败
at demonstrateErrorMonitoring (/Users/loock/myFile/langgraphjs-tutorial/websites/examples/实用功能/错误处理/error-monitoring.ts:180:5)
at <anonymous> (/Users/loock/myFile/langgraphjs-tutorial/websites/examples/实用功能/错误处理/error-monitoring.ts:201:3)
at Object.<anonymous> (/Users/loock/myFile/langgraphjs-tutorial/websites/examples/实用功能/错误处理/error-monitoring.ts:202:1)
at Module._compile (node:internal/modules/cjs/loader:1521:14)
at Object.transformer (/Users/loock/myFile/langgraphjs-tutorial/websites/node_modules/.pnpm/tsx@4.20.3/node_modules/tsx/dist/register-D46fvsV_.cjs:3:1104)
at Module.load (node:internal/modules/cjs/loader:1266:32)
at Module._load (node:internal/modules/cjs/loader:1091:12)
at cjsLoader (node:internal/modules/esm/translators:298:15)
at ModuleWrap.<anonymous> (node:internal/modules/esm/translators:240:7)
at ModuleJob.run (node:internal/modules/esm/module_job:325:25),
timestamp: 2025-11-16T12:29:00.018Z,
context: [Object]
},
{
error: Error: 网络连接失败
at demonstrateErrorMonitoring (/Users/loock/myFile/langgraphjs-tutorial/websites/examples/实用功能/错误处理/error-monitoring.ts:181:5)
at <anonymous> (/Users/loock/myFile/langgraphjs-tutorial/websites/examples/实用功能/错误处理/error-monitoring.ts:201:3)
at Object.<anonymous> (/Users/loock/myFile/langgraphjs-tutorial/websites/examples/实用功能/错误处理/error-monitoring.ts:202:1)
at Module._compile (node:internal/modules/cjs/loader:1521:14)
at Object.transformer (/Users/loock/myFile/langgraphjs-tutorial/websites/node_modules/.pnpm/tsx@4.20.3/node_modules/tsx/dist/register-D46fvsV_.cjs:3:1104)
at Module.load (node:internal/modules/cjs/loader:1266:32)
at Module._load (node:internal/modules/cjs/loader:1091:12)
at cjsLoader (node:internal/modules/esm/translators:298:15)
at ModuleWrap.<anonymous> (node:internal/modules/esm/translators:240:7)
at ModuleJob.run (node:internal/modules/esm/module_job:325:25),
timestamp: 2025-11-16T12:29:00.118Z,
context: [Object]
},
{
error: Error: 权限不足
at demonstrateErrorMonitoring (/Users/loock/myFile/langgraphjs-tutorial/websites/examples/实用功能/错误处理/error-monitoring.ts:182:5)
at <anonymous> (/Users/loock/myFile/langgraphjs-tutorial/websites/examples/实用功能/错误处理/error-monitoring.ts:201:3)
at Object.<anonymous> (/Users/loock/myFile/langgraphjs-tutorial/websites/examples/实用功能/错误处理/error-monitoring.ts:202:1)
at Module._compile (node:internal/modules/cjs/loader:1521:14)
at Object.transformer (/Users/loock/myFile/langgraphjs-tutorial/websites/node_modules/.pnpm/tsx@4.20.3/node_modules/tsx/dist/register-D46fvsV_.cjs:3:1104)
at Module.load (node:internal/modules/cjs/loader:1266:32)
at Module._load (node:internal/modules/cjs/loader:1091:12)
at cjsLoader (node:internal/modules/esm/translators:298:15)
at ModuleWrap.<anonymous> (node:internal/modules/esm/translators:240:7)
at ModuleJob.run (node:internal/modules/esm/module_job:325:25),
timestamp: 2025-11-16T12:29:00.218Z,
context: [Object]
}
]
}
错误趋势: { Error: 5 }
*/

错误恢复机制

检查点恢复

当发生错误时,可以利用检查点机制恢复到之前的稳定状态:

状态回滚

状态回滚机制
/**
* ============================================================================
* 状态回滚机制 - State Rollback Mechanism
* ============================================================================
*
* 📖 概述
* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
* 本示例展示如何实现状态快照和回滚机制,在操作失败或检测到无效状态时,
* 能够恢复到之前的有效状态,确保系统的数据一致性和可靠性。
*
* 🎯 核心功能
* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
* 1️⃣ 状态快照保存:在关键操作前自动保存状态快照
* 2️⃣ 快照管理系统:限制快照数量,防止内存溢出
* 3️⃣ 状态回滚功能:支持回滚到指定的历史快照
* 4️⃣ 状态验证机制:自动检测无效状态并触发回滚
* 5️⃣ 操作历史记录:追踪所有状态变更和回滚操作
*
* 💡 实现思路
* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
* • 使用深拷贝保存状态快照,避免引用问题
* • 为每个快照生成唯一 ID,便于精确回滚
* • 限制快照数量(默认 10 个),采用 FIFO 策略
* • 验证节点检测到无效状态时自动设置回滚标记
* • 条件路由根据回滚请求决定是否执行回滚
*
* 🚀 使用方式
* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
* // 运行示例
* $ npx esno 实用功能/错误处理/state-rollback.ts
*
* ⚠️ 注意事项
* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
* • 快照使用 JSON 序列化,无法保存函数、Symbol 等特殊类型
* • 快照数量要合理设置,避免占用过多内存
* • 大状态对象的深拷贝会影响性能
* • 回滚操作应该是原子性的,要么全部成功要么全部失败
*
* @author 程哥
* @version 1.0.0
* @updated 2025-11
*/

import '../../utils/loadEnv';
import { StateGraph, Annotation, START, END } from '@langchain/langgraph';
import { BaseMessage, HumanMessage, AIMessage } from '@langchain/core/messages';
import { RunnableConfig } from '@langchain/core/runnables';

// 状态快照
interface StateSnapshot {
id: string;
timestamp: Date;
state: any;
description: string;
}

// 状态回滚管理器
class StateRollbackManager {
private snapshots: StateSnapshot[] = [];
private maxSnapshots = 10;

saveSnapshot(state: any, description: string): string {
const id = `snapshot_${Date.now()}_${Math.random()
.toString(36)
.substr(2, 9)}`;
const snapshot: StateSnapshot = {
id,
timestamp: new Date(),
state: JSON.parse(JSON.stringify(state)), // 深拷贝
description,
};

this.snapshots.push(snapshot);

// 限制快照数量
if (this.snapshots.length > this.maxSnapshots) {
this.snapshots.shift();
}

console.log(`💾 保存状态快照: ${id} - ${description}`);
return id;
}

rollback(snapshotId: string): any | null {
const snapshot = this.snapshots.find((s) => s.id === snapshotId);
if (snapshot) {
console.log(`🔄 回滚到快照: ${snapshotId} - ${snapshot.description}`);
return JSON.parse(JSON.stringify(snapshot.state)); // 深拷贝
}
return null;
}

getSnapshots(): StateSnapshot[] {
return this.snapshots.map((s) => ({
id: s.id,
timestamp: s.timestamp,
description: s.description,
state: undefined, // 不返回完整状态,只返回元数据
}));
}

getLatestSnapshot(): StateSnapshot | null {
return this.snapshots.length > 0
? this.snapshots[this.snapshots.length - 1]
: null;
}
}

// 定义状态
const StateAnnotation = Annotation.Root({
messages: Annotation<BaseMessage[]>({
reducer: (state: BaseMessage[], update: BaseMessage[]) =>
state.concat(update),
default: () => [],
}),
userProfile: Annotation<any>({
reducer: (state: any, update: any) => ({ ...state, ...update }),
default: () => ({ name: '', email: '', score: 0 }),
}),
operationHistory: Annotation<string[]>({
reducer: (state: string[], update: string[]) => state.concat(update),
default: () => [],
}),
lastSnapshotId: Annotation<string>({
reducer: (state: string, update: string) => update,
default: () => '',
}),
rollbackRequested: Annotation<boolean>({
reducer: (state: boolean, update: boolean) => update,
default: () => false,
}),
});

// 创建回滚管理器实例
const rollbackManager = new StateRollbackManager();

// 用户操作节点
async function userOperationNode(
state: typeof StateAnnotation.State,
config?: RunnableConfig
) {
const lastMessage = state.messages[state.messages.length - 1];
const content = lastMessage.content as string;

console.log(`执行用户操作: ${content}`);

// 在执行操作前保存快照
const snapshotId = rollbackManager.saveSnapshot(state, `操作前: ${content}`);

let updates: any = {
operationHistory: [`执行操作: ${content}`],
lastSnapshotId: snapshotId,
};

// 模拟不同的用户操作
if (content.includes('更新姓名')) {
updates.userProfile = { name: '新用户名' };
} else if (content.includes('更新邮箱')) {
updates.userProfile = { email: 'new@example.com' };
} else if (content.includes('增加积分')) {
updates.userProfile = { score: state.userProfile.score + 100 };
} else if (content.includes('危险操作')) {
// 模拟危险操作,可能需要回滚
updates.userProfile = { score: -999, name: '错误状态' };
updates.operationHistory = ['执行了危险操作,可能需要回滚'];
} else if (content.includes('回滚')) {
updates.rollbackRequested = true;
return updates;
}

updates.messages = [
new AIMessage({
content: `操作完成: ${content}`,
}),
];

return updates;
}

// 回滚节点
async function rollbackNode(
state: typeof StateAnnotation.State,
config?: RunnableConfig
) {
if (!state.rollbackRequested || !state.lastSnapshotId) {
return {
messages: [
new AIMessage({
content: '没有可回滚的状态或未请求回滚',
}),
],
};
}

console.log(`尝试回滚到快照: ${state.lastSnapshotId}`);

const rolledBackState = rollbackManager.rollback(state.lastSnapshotId);

if (rolledBackState) {
return {
...rolledBackState,
messages: [
...state.messages,
new AIMessage({
content: `已成功回滚到之前的状态`,
}),
],
operationHistory: [
...state.operationHistory,
`回滚到快照: ${state.lastSnapshotId}`,
],
rollbackRequested: false,
};
} else {
return {
messages: [
new AIMessage({
content: '回滚失败:找不到指定的快照',
}),
],
rollbackRequested: false,
};
}
}

// 验证节点
async function validationNode(
state: typeof StateAnnotation.State,
config?: RunnableConfig
) {
const { userProfile } = state;

// 检查状态是否有效
const isValid = userProfile.score >= 0 && userProfile.name !== '错误状态';

if (!isValid) {
console.log('⚠️ 检测到无效状态,建议回滚');
return {
messages: [
new AIMessage({
content: '检测到无效状态,建议执行回滚操作',
}),
],
rollbackRequested: true,
};
}

return {
messages: [
new AIMessage({
content: '状态验证通过',
}),
],
};
}

// 路由函数
function shouldRollback(state: typeof StateAnnotation.State) {
if (state.rollbackRequested) {
return 'rollback';
}
return END;
}

// 构建图
const workflow = new StateGraph(StateAnnotation)
.addNode('user_operation', userOperationNode)
.addNode('validation', validationNode)
.addNode('rollback', rollbackNode)
.addEdge(START, 'user_operation')
.addEdge('user_operation', 'validation')
.addConditionalEdges('validation', shouldRollback, {
rollback: 'rollback',
[END]: END,
})
.addEdge('rollback', END);

const app = workflow.compile();

// 使用示例
async function demonstrateStateRollback() {
console.log('=== 状态回滚机制示例 ===\n');

const testCases = [
'更新姓名',
'增加积分',
'更新邮箱',
'危险操作', // 这个操作会触发回滚
'回滚', // 手动触发回滚
];

let currentState = {
messages: [],
userProfile: { name: '原始用户', email: 'original@example.com', score: 50 },
operationHistory: [],
lastSnapshotId: '',
rollbackRequested: false,
};

for (const testCase of testCases) {
console.log(`\n测试用例: ${testCase}`);
console.log('-'.repeat(30));
console.log('操作前状态:', currentState.userProfile);

try {
const result = await app.invoke({
...currentState,
messages: [new HumanMessage({ content: testCase })],
});

currentState = result;
console.log('操作后状态:', result.userProfile);
console.log(
'最后消息:',
result.messages[result.messages.length - 1].content
);
console.log('操作历史:', result.operationHistory);
} catch (error) {
console.error('处理失败:', error.message);
}
}

console.log('\n快照历史:');
rollbackManager.getSnapshots().forEach((snapshot) => {
console.log(
`- ${snapshot.id}: ${
snapshot.description
} (${snapshot.timestamp.toISOString()})`
);
});
}

// 运行示例
if (require.main === module) {
demonstrateStateRollback().catch(console.error);
}

export { app, StateRollbackManager, rollbackManager, demonstrateStateRollback };

/*
执行结果:
Line:8 🌭 path.resolve(__dirname, '.env') /Users/loock/myFile/langgraphjs-tutorial/websites/examples/utils/.env
=== 状态回滚机制示例 ===


测试用例: 更新姓名
------------------------------
操作前状态: { name: '原始用户', email: 'original@example.com', score: 50 }
执行用户操作: 更新姓名
💾 保存状态快照: snapshot_1763296153914_olycktmio - 操作前: 更新姓名
操作后状态: { name: '新用户名', email: 'original@example.com', score: 50 }
最后消息: 状态验证通过
操作历史: [ '执行操作: 更新姓名' ]

测试用例: 增加积分
------------------------------
操作前状态: { name: '新用户名', email: 'original@example.com', score: 50 }
执行用户操作: 增加积分
💾 保存状态快照: snapshot_1763296153923_hdxu3wjjj - 操作前: 增加积分
操作后状态: { name: '新用户名', email: 'original@example.com', score: 150 }
最后消息: 状态验证通过
操作历史: [ '执行操作: 更新姓名', '执行操作: 增加积分' ]

测试用例: 更新邮箱
------------------------------
操作前状态: { name: '新用户名', email: 'original@example.com', score: 150 }
执行用户操作: 更新邮箱
💾 保存状态快照: snapshot_1763296153926_p5xcaq4ho - 操作前: 更新邮箱
操作后状态: { name: '新用户名', email: 'new@example.com', score: 150 }
最后消息: 状态验证通过
操作历史: [ '执行操作: 更新姓名', '执行操作: 增加积分', '执行操作: 更新邮箱' ]

测试用例: 危险操作
------------------------------
操作前状态: { name: '新用户名', email: 'new@example.com', score: 150 }
执行用户操作: 危险操作
💾 保存状态快照: snapshot_1763296153929_0kxnd0axh - 操作前: 危险操作
⚠️ 检测到无效状态,建议回滚
尝试回滚到快照: snapshot_1763296153929_0kxnd0axh
🔄 回滚到快照: snapshot_1763296153929_0kxnd0axh - 操作前: 危险操作
操作后状态: { name: '新用户名', email: 'new@example.com', score: 150 }
最后消息: 已成功回滚到之前的状态
操作历史: [
'执行操作: 更新姓名',
'执行操作: 增加积分',
'执行操作: 更新邮箱',
'执行了危险操作,可能需要回滚',
'执行操作: 更新姓名',
'执行操作: 增加积分',
'执行操作: 更新邮箱',
'执行了危险操作,可能需要回滚',
'回滚到快照: snapshot_1763296153929_0kxnd0axh'
]

测试用例: 回滚
------------------------------
操作前状态: { name: '新用户名', email: 'new@example.com', score: 150 }
执行用户操作: 回滚
💾 保存状态快照: snapshot_1763296153932_tvgspte1z - 操作前: 回滚
尝试回滚到快照: snapshot_1763296153932_tvgspte1z
🔄 回滚到快照: snapshot_1763296153932_tvgspte1z - 操作前: 回滚
操作后状态: { name: '新用户名', email: 'new@example.com', score: 150 }
最后消息: 已成功回滚到之前的状态
操作历史: [
'执行操作: 更新姓名',
'执行操作: 增加积分',
'执行操作: 更新邮箱',
'执行了危险操作,可能需要回滚',
'执行操作: 更新姓名',
'执行操作: 增加积分',
'执行操作: 更新邮箱',
'执行了危险操作,可能需要回滚',
'回滚到快照: snapshot_1763296153929_0kxnd0axh',
'执行操作: 回滚',
'执行操作: 更新姓名',
'执行操作: 增加积分',
'执行操作: 更新邮箱',
'执行了危险操作,可能需要回滚',
'执行操作: 更新姓名',
'执行操作: 增加积分',
'执行操作: 更新邮箱',
'执行了危险操作,可能需要回滚',
'回滚到快照: snapshot_1763296153929_0kxnd0axh',
'执行操作: 回滚',
'回滚到快照: snapshot_1763296153932_tvgspte1z'
]

快照历史:
- snapshot_1763296153914_olycktmio: 操作前: 更新姓名 (2025-11-16T12:29:13.914Z)
- snapshot_1763296153923_hdxu3wjjj: 操作前: 增加积分 (2025-11-16T12:29:13.923Z)
- snapshot_1763296153926_p5xcaq4ho: 操作前: 更新邮箱 (2025-11-16T12:29:13.926Z)
- snapshot_1763296153929_0kxnd0axh: 操作前: 危险操作 (2025-11-16T12:29:13.929Z)
- snapshot_1763296153932_tvgspte1z: 操作前: 回滚 (2025-11-16T12:29:13.932Z)
*/

实际应用场景

生产环境错误处理

生产环境错误处理
/**
* ============================================================================
* 生产环境错误处理 - Production Error Handling
* ============================================================================
*
* 📖 概述
* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
* 本示例展示生产环境中的错误处理最佳实践,包括断路器模式、错误降级处理、
* 用户友好提示等,确保系统在出错时依然能够优雅地响应。
*
* 🎯 核心功能
* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
* 1️⃣ 断路器模式:当错误频繁发生时自动开启断路器,防止雪崩
* 2️⃣ 错误计数统计:记录每种错误的发生次数
* 3️⃣ 自动恢复机制:断路器在一定时间后自动重置
* 4️⃣ 降级响应策略:提供通用错误响应,不暴露系统细节
* 5️⃣ 单例模式设计:全局唯一的错误处理器实例
*
* 💡 实现思路
* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
* • 使用单例模式确保错误处理器全局唯一
* • 断路器记录失败次数,达到阈值(5次)时开启
* • 开启后在指定时间(1分钟)后自动尝试恢复
* • 不同错误类型有不同的降级策略(离线模式、通用提示等)
* • 生产环境不暴露内部错误信息,统一返回用户友好提示
*
* 🚀 使用方式
* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
* // 运行示例
* $ npx esno 实用功能/错误处理/production-error-handling.ts
*
* ⚠️ 注意事项
* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
* • 断路器阈值需要根据实际业务场景调整
* • 生产环境必须隐藏技术细节,防止信息泄露
* • 断路器重置时间要合理,避免过早或过晚
* • 应该与监控系统集成,及时发现断路器开启
*
* @author 程哥
* @version 1.0.0
* @updated 2025-11
*/

import '../../utils/loadEnv';
// 生产环境错误处理示例
import { StateGraph, Annotation, START, END } from '@langchain/langgraph';
import { BaseMessage, HumanMessage, AIMessage } from '@langchain/core/messages';

// 生产环境错误处理器
class ProductionErrorHandler {
private static instance: ProductionErrorHandler;
private errorCounts = new Map<string, number>();
private circuitBreakers = new Map<
string,
{ isOpen: boolean; failures: number; lastFailure: Date }
>();

static getInstance(): ProductionErrorHandler {
if (!ProductionErrorHandler.instance) {
ProductionErrorHandler.instance = new ProductionErrorHandler();
}
return ProductionErrorHandler.instance;
}

async handleError(error: Error, context: any): Promise<any> {
// 记录错误
this.recordError(error);

// 检查断路器
if (this.isCircuitOpen(error.constructor.name)) {
return this.getCircuitOpenResponse();
}

// 根据错误类型处理
if (error.name === 'NetworkError') {
return this.handleNetworkError(error, context);
} else if (error.name === 'ValidationError') {
return this.handleValidationError(error, context);
} else {
return this.handleGenericError(error, context);
}
}

private recordError(error: Error) {
const errorType = error.constructor.name;
const count = this.errorCounts.get(errorType) || 0;
this.errorCounts.set(errorType, count + 1);

// 更新断路器状态
const breaker = this.circuitBreakers.get(errorType) || {
isOpen: false,
failures: 0,
lastFailure: new Date(),
};
breaker.failures++;
breaker.lastFailure = new Date();

if (breaker.failures >= 5) {
breaker.isOpen = true;
console.warn(`🔴 断路器开启: ${errorType}`);
}

this.circuitBreakers.set(errorType, breaker);
}

private isCircuitOpen(errorType: string): boolean {
const breaker = this.circuitBreakers.get(errorType);
if (!breaker || !breaker.isOpen) return false;

// 检查是否应该重置断路器
const timeSinceLastFailure = Date.now() - breaker.lastFailure.getTime();
if (timeSinceLastFailure > 60000) {
// 1分钟后重置
breaker.isOpen = false;
breaker.failures = 0;
console.log(`🟢 断路器重置: ${errorType}`);
return false;
}

return true;
}

private getCircuitOpenResponse() {
return {
messages: [new AIMessage({ content: '服务暂时不可用,请稍后重试。' })],
error: true,
circuitOpen: true,
};
}

private async handleNetworkError(error: Error, context: any) {
return {
messages: [
new AIMessage({ content: '网络连接异常,已切换到离线模式。' }),
],
error: true,
fallbackMode: true,
};
}

private async handleValidationError(error: Error, context: any) {
return {
messages: [
new AIMessage({ content: '输入数据格式错误,请检查后重试。' }),
],
error: true,
validationFailed: true,
};
}

private async handleGenericError(error: Error, context: any) {
// 不暴露内部错误信息
return {
messages: [new AIMessage({ content: '系统繁忙,请稍后重试。' })],
error: true,
generic: true,
};
}
}

// 定义状态
const StateAnnotation = Annotation.Root({
messages: Annotation<BaseMessage[]>({
reducer: (state: BaseMessage[], update: BaseMessage[]) =>
state.concat(update),
default: () => [],
}),
error: Annotation<boolean>({
reducer: (state: boolean, update: boolean) => update,
default: () => false,
}),
errorType: Annotation<string>({
reducer: (state: string, update: string) => update,
default: () => '',
}),
});

const errorHandler = ProductionErrorHandler.getInstance();

// 业务处理节点
async function businessLogicNode(state: typeof StateAnnotation.State) {
const lastMessage = state.messages[state.messages.length - 1];
const content = lastMessage.content as string;

// 模拟各种错误
if (content.includes('网络错误')) {
throw new Error('Network timeout');
} else if (content.includes('验证错误')) {
const error = new Error('Invalid input format');
error.name = 'ValidationError';
throw error;
} else if (content.includes('系统错误')) {
throw new Error('Internal server error');
}

return {
messages: [new AIMessage({ content: `处理成功: ${content}` })],
};
}

// 安全包装节点
async function safeBusinessLogicNode(state: typeof StateAnnotation.State) {
try {
return await businessLogicNode(state);
} catch (error) {
console.error('业务逻辑错误:', error.message);
return await errorHandler.handleError(error as Error, state);
}
}

// 构建图
const workflow = new StateGraph(StateAnnotation)
.addNode('business_logic', safeBusinessLogicNode)
.addEdge(START, 'business_logic')
.addEdge('business_logic', END);

const app = workflow.compile();

export async function demonstrateProductionErrorHandling() {
console.log('=== 生产环境错误处理示例 ===\n');

const testCases = [
'正常处理',
'网络错误',
'验证错误',
'系统错误',
'网络错误', // 重复错误触发断路器
'网络错误',
'网络错误',
'网络错误',
'网络错误', // 第5次,应该触发断路器
];

for (const testCase of testCases) {
console.log(`\n测试: ${testCase}`);
try {
const result = await app.invoke({
messages: [new HumanMessage({ content: testCase })],
});
console.log('结果:', result.messages[result.messages.length - 1].content);
if (result.error) {
console.log('错误状态:', result.error);
}
} catch (error) {
console.error('处理失败:', error.message);
}
}
}

// 运行示例
if (require.main === module) {
demonstrateProductionErrorHandling().catch(console.error);
}

/*
执行结果:
Line:8 🌭 path.resolve(__dirname, '.env') /Users/loock/myFile/langgraphjs-tutorial/websites/examples/utils/.env
=== 生产环境错误处理示例 ===


测试: 正常处理
结果: 处理成功: 正常处理

测试: 网络错误
结果: 系统繁忙,请稍后重试。
错误状态: true

测试: 验证错误
结果: 输入数据格式错误,请检查后重试。
错误状态: true

测试: 系统错误
结果: 系统繁忙,请稍后重试。
错误状态: true

测试: 网络错误
结果: 系统繁忙,请稍后重试。
错误状态: true

测试: 网络错误
结果: 服务暂时不可用,请稍后重试。
错误状态: true

测试: 网络错误
结果: 服务暂时不可用,请稍后重试。
错误状态: true

测试: 网络错误
结果: 服务暂时不可用,请稍后重试。
错误状态: true

测试: 网络错误
结果: 服务暂时不可用,请稍后重试。
错误状态: true
*/

用户友好的错误提示

用户友好错误提示
/**
* ============================================================================
* 用户友好错误提示 - User Friendly Errors
* ============================================================================
*
* 📖 概述
* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
* 本示例展示如何将技术性错误转换为用户友好的提示信息,通过错误码映射和恢复
* 建议,为用户提供清晰易懂的错误说明和解决方案,提升用户体验。
*
* 🎯 核心功能
* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
* 1️⃣ 错误消息映射:将技术错误转换为用户可理解的描述
* 2️⃣ 恢复建议系统:为每种错误提供具体的解决步骤
* 3️⃣ 错误严重度评估:low、medium、high 三级严重度分类
* 4️⃣ 重试策略判断:自动判断错误是否可以通过重试解决
* 5️⃣ 格式化输出:结构化的错误信息展示,包含图标和建议列表
*
* 💡 实现思路
* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
* • 定义错误消息和恢复建议的映射表,覆盖常见错误场景
* • 通过错误信息关键词匹配自动识别错误类型
* • 根据错误类型自动判断严重度和是否可重试
* • 使用 emoji 和格式化文本增强错误信息的可读性
* • 提供渐进式的错误处理流程,先提示错误再给出重试建议
*
* 🚀 使用方式
* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
* // 运行示例
* $ npx esno 实用功能/错误处理/user-friendly-errors.ts
*
* ⚠️ 注意事项
* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
* • 错误消息应避免技术术语,使用用户易懂的语言
* • 恢复建议应该具体可执行,而非模糊的指导
* • 错误匹配使用关键词,需要考虑中英文双语场景
* • 生产环境不应暴露敏感的系统错误信息
*
* @author 程哥
* @version 1.0.0
* @updated 2025-11
*/

import '../../utils/loadEnv';
import { StateGraph, Annotation, START, END } from '@langchain/langgraph';
import { BaseMessage, HumanMessage, AIMessage } from '@langchain/core/messages';
import { RunnableConfig } from '@langchain/core/runnables';

// 用户友好的错误信息映射
const ERROR_MESSAGES: Record<string, string> = {
NETWORK_TIMEOUT: '网络连接超时,请检查您的网络连接后重试。',
INVALID_INPUT: '输入格式不正确,请按照提示重新输入。',
PERMISSION_DENIED: '您没有执行此操作的权限,请联系管理员。',
QUOTA_EXCEEDED: '您已达到使用限额,请升级服务或稍后重试。',
SERVICE_UNAVAILABLE: '服务暂时不可用,我们正在努力修复,请稍后重试。',
RATE_LIMIT: '请求过于频繁,请稍后再试。',
DATA_NOT_FOUND: '未找到相关数据,请检查输入信息。',
INTERNAL_ERROR: '系统遇到内部错误,我们已记录此问题,请稍后重试。',
};

// 错误恢复建议
const RECOVERY_SUGGESTIONS: Record<string, string[]> = {
NETWORK_TIMEOUT: [
'检查网络连接是否正常',
'尝试刷新页面',
'如果问题持续,请联系技术支持',
],
INVALID_INPUT: [
'请检查输入格式是否正确',
'参考示例重新输入',
'如需帮助,请查看使用说明',
],
PERMISSION_DENIED: [
'请联系管理员获取相应权限',
'确认您的账户状态是否正常',
'尝试重新登录',
],
QUOTA_EXCEEDED: [
'考虑升级到更高级别的服务',
'等待配额重置后再试',
'联系客服了解更多选项',
],
SERVICE_UNAVAILABLE: [
'请稍后重试',
'关注我们的状态页面获取更新',
'如果紧急,请联系技术支持',
],
};

// 用户友好错误处理器
class UserFriendlyErrorHandler {
static formatError(
error: Error,
context?: any
): {
message: string;
suggestions: string[];
severity: 'low' | 'medium' | 'high';
canRetry: boolean;
} {
const errorCode = this.getErrorCode(error);

return {
message: ERROR_MESSAGES[errorCode] || '发生了未知错误,请稍后重试。',
suggestions: RECOVERY_SUGGESTIONS[errorCode] || [
'请联系技术支持获取帮助',
],
severity: this.getErrorSeverity(errorCode),
canRetry: this.canRetry(errorCode),
};
}

private static getErrorCode(error: Error): string {
// 根据错误信息或类型映射到错误代码
if (error.message.includes('timeout') || error.message.includes('网络')) {
return 'NETWORK_TIMEOUT';
} else if (
error.message.includes('invalid') ||
error.message.includes('格式')
) {
return 'INVALID_INPUT';
} else if (
error.message.includes('permission') ||
error.message.includes('权限')
) {
return 'PERMISSION_DENIED';
} else if (
error.message.includes('quota') ||
error.message.includes('限额')
) {
return 'QUOTA_EXCEEDED';
} else if (
error.message.includes('rate limit') ||
error.message.includes('频繁')
) {
return 'RATE_LIMIT';
} else if (
error.message.includes('not found') ||
error.message.includes('未找到')
) {
return 'DATA_NOT_FOUND';
} else if (
error.message.includes('unavailable') ||
error.message.includes('不可用')
) {
return 'SERVICE_UNAVAILABLE';
} else {
return 'INTERNAL_ERROR';
}
}

private static getErrorSeverity(
errorCode: string
): 'low' | 'medium' | 'high' {
const highSeverityErrors = ['INTERNAL_ERROR', 'SERVICE_UNAVAILABLE'];
const mediumSeverityErrors = [
'NETWORK_TIMEOUT',
'PERMISSION_DENIED',
'QUOTA_EXCEEDED',
];

if (highSeverityErrors.includes(errorCode)) {
return 'high';
} else if (mediumSeverityErrors.includes(errorCode)) {
return 'medium';
} else {
return 'low';
}
}

private static canRetry(errorCode: string): boolean {
const nonRetryableErrors = [
'PERMISSION_DENIED',
'INVALID_INPUT',
'DATA_NOT_FOUND',
];
return !nonRetryableErrors.includes(errorCode);
}
}

// 定义状态
const StateAnnotation = Annotation.Root({
messages: Annotation<BaseMessage[]>({
reducer: (state: BaseMessage[], update: BaseMessage[]) =>
state.concat(update),
default: () => [],
}),
userFriendlyError: Annotation<any>({
reducer: (state: any, update: any) => update,
default: () => null,
}),
canRetry: Annotation<boolean>({
reducer: (state: boolean, update: boolean) => update,
default: () => false,
}),
});

// 模拟业务操作节点
async function businessOperationNode(
state: typeof StateAnnotation.State,
config?: RunnableConfig
) {
const lastMessage = state.messages[state.messages.length - 1];
const content = lastMessage.content as string;

console.log(`执行业务操作: ${content}`);

// 模拟各种错误情况
if (content.includes('网络错误')) {
throw new Error('Network timeout occurred');
} else if (content.includes('格式错误')) {
throw new Error('Invalid input format provided');
} else if (content.includes('权限错误')) {
throw new Error('Permission denied for this operation');
} else if (content.includes('限额错误')) {
throw new Error('Quota exceeded for current plan');
} else if (content.includes('频繁请求')) {
throw new Error('Rate limit exceeded, too many requests');
} else if (content.includes('数据不存在')) {
throw new Error('Data not found in database');
} else if (content.includes('服务不可用')) {
throw new Error('Service temporarily unavailable');
} else if (content.includes('内部错误')) {
throw new Error('Internal server error occurred');
}

return {
messages: [
new AIMessage({
content: `操作成功完成: ${content}`,
}),
],
};
}

// 用户友好错误处理节点
async function userFriendlyErrorNode(
state: typeof StateAnnotation.State,
config?: RunnableConfig
) {
try {
return await businessOperationNode(state, config);
} catch (error) {
console.error('业务操作失败:', error.message);

// 使用用户友好错误处理器
const friendlyError = UserFriendlyErrorHandler.formatError(error as Error);

// 构建用户友好的错误响应
const errorMessage = [
`${friendlyError.message}`,
'',
'💡 建议解决方案:',
...friendlyError.suggestions.map(
(suggestion, index) => `${index + 1}. ${suggestion}`
),
].join('\n');

return {
messages: [
new AIMessage({
content: errorMessage,
}),
],
userFriendlyError: friendlyError,
canRetry: friendlyError.canRetry,
};
}
}

// 重试建议节点
async function retrySuggestionNode(
state: typeof StateAnnotation.State,
config?: RunnableConfig
) {
if (!state.userFriendlyError) {
return { messages: [] };
}

const { severity, canRetry } = state.userFriendlyError;

let retryMessage = '';

if (canRetry) {
switch (severity) {
case 'low':
retryMessage = '🔄 您可以立即重试此操作。';
break;
case 'medium':
retryMessage = '⏳ 建议等待几分钟后重试。';
break;
case 'high':
retryMessage = '⚠️ 建议稍后重试,或联系技术支持。';
break;
}
} else {
retryMessage = '🚫 此错误无法通过重试解决,请按照建议进行处理。';
}

return {
messages: [
new AIMessage({
content: retryMessage,
}),
],
};
}

// 构建图
const workflow = new StateGraph(StateAnnotation)
.addNode('user_friendly_error', userFriendlyErrorNode)
.addNode('retry_suggestion', retrySuggestionNode)
.addEdge(START, 'user_friendly_error')
.addEdge('user_friendly_error', 'retry_suggestion')
.addEdge('retry_suggestion', END);

const app = workflow.compile();

// 使用示例
async function demonstrateUserFriendlyErrors() {
console.log('=== 用户友好错误提示示例 ===\n');

const testCases = [
'正常操作',
'触发网络错误',
'触发格式错误',
'触发权限错误',
'触发限额错误',
'触发频繁请求',
'触发数据不存在',
'触发服务不可用',
'触发内部错误',
];

for (const testCase of testCases) {
console.log(`\n测试用例: ${testCase}`);
console.log('-'.repeat(40));

try {
const result = await app.invoke({
messages: [new HumanMessage({ content: testCase })],
});

// 显示所有消息
result.messages.forEach((message, index) => {
if (index > 0) {
// 跳过输入消息
console.log(message.content);
}
});

if (result.userFriendlyError) {
console.log(`\n错误严重程度: ${result.userFriendlyError.severity}`);
console.log(`可以重试: ${result.canRetry ? '是' : '否'}`);
}
} catch (error) {
console.error('处理失败:', error.message);
}
}

console.log('\n' + '='.repeat(50));
console.log('错误处理器测试:');

// 直接测试错误处理器
const testErrors = [
new Error('Network timeout occurred'),
new Error('Invalid input format'),
new Error('Permission denied'),
new Error('Unknown system error'),
];

testErrors.forEach((error, index) => {
console.log(`\n错误 ${index + 1}: ${error.message}`);
const formatted = UserFriendlyErrorHandler.formatError(error);
console.log(`友好提示: ${formatted.message}`);
console.log(`严重程度: ${formatted.severity}`);
console.log(`可重试: ${formatted.canRetry}`);
console.log(`建议: ${formatted.suggestions.join(', ')}`);
});
}

// 运行示例
if (require.main === module) {
demonstrateUserFriendlyErrors().catch(console.error);
}

export {
app,
UserFriendlyErrorHandler,
ERROR_MESSAGES,
RECOVERY_SUGGESTIONS,
demonstrateUserFriendlyErrors,
};

/*
执行结果:
Line:8 🌭 path.resolve(__dirname, '.env') /Users/loock/myFile/langgraphjs-tutorial/websites/examples/utils/.env
=== 用户友好错误提示示例 ===


测试用例: 正常操作
----------------------------------------
执行业务操作: 正常操作
操作成功完成: 正常操作

测试用例: 触发网络错误
----------------------------------------
执行业务操作: 触发网络错误
❌ 网络连接超时,请检查您的网络连接后重试。

💡 建议解决方案:
1. 检查网络连接是否正常
2. 尝试刷新页面
3. 如果问题持续,请联系技术支持
⏳ 建议等待几分钟后重试。

错误严重程度: medium
可以重试: 是

测试用例: 触发格式错误
----------------------------------------
执行业务操作: 触发格式错误
❌ 系统遇到内部错误,我们已记录此问题,请稍后重试。

💡 建议解决方案:
1. 请联系技术支持获取帮助
⚠️ 建议稍后重试,或联系技术支持。

错误严重程度: high
可以重试: 是

测试用例: 触发权限错误
----------------------------------------
执行业务操作: 触发权限错误
❌ 系统遇到内部错误,我们已记录此问题,请稍后重试。

💡 建议解决方案:
1. 请联系技术支持获取帮助
⚠️ 建议稍后重试,或联系技术支持。

错误严重程度: high
可以重试: 是

测试用例: 触发限额错误
----------------------------------------
执行业务操作: 触发限额错误
❌ 系统遇到内部错误,我们已记录此问题,请稍后重试。

💡 建议解决方案:
1. 请联系技术支持获取帮助
⚠️ 建议稍后重试,或联系技术支持。

错误严重程度: high
可以重试: 是

测试用例: 触发频繁请求
----------------------------------------
执行业务操作: 触发频繁请求
❌ 系统遇到内部错误,我们已记录此问题,请稍后重试。

💡 建议解决方案:
1. 请联系技术支持获取帮助
⚠️ 建议稍后重试,或联系技术支持。

错误严重程度: high
可以重试: 是

测试用例: 触发数据不存在
----------------------------------------
执行业务操作: 触发数据不存在
❌ 未找到相关数据,请检查输入信息。

💡 建议解决方案:
1. 请联系技术支持获取帮助
🚫 此错误无法通过重试解决,请按照建议进行处理。

错误严重程度: low
可以重试: 否

测试用例: 触发服务不可用
----------------------------------------
执行业务操作: 触发服务不可用
❌ 服务暂时不可用,我们正在努力修复,请稍后重试。

💡 建议解决方案:
1. 请稍后重试
2. 关注我们的状态页面获取更新
3. 如果紧急,请联系技术支持
⚠️ 建议稍后重试,或联系技术支持。

错误严重程度: high
可以重试: 是

测试用例: 触发内部错误
----------------------------------------
执行业务操作: 触发内部错误
❌ 系统遇到内部错误,我们已记录此问题,请稍后重试。

💡 建议解决方案:
1. 请联系技术支持获取帮助
⚠️ 建议稍后重试,或联系技术支持。

错误严重程度: high
可以重试: 是

==================================================
错误处理器测试:

错误 1: Network timeout occurred
友好提示: 网络连接超时,请检查您的网络连接后重试。
严重程度: medium
可重试: true
建议: 检查网络连接是否正常, 尝试刷新页面, 如果问题持续,请联系技术支持

错误 2: Invalid input format
友好提示: 系统遇到内部错误,我们已记录此问题,请稍后重试。
严重程度: high
可重试: true
建议: 请联系技术支持获取帮助

错误 3: Permission denied
友好提示: 系统遇到内部错误,我们已记录此问题,请稍后重试。
严重程度: high
可重试: true
建议: 请联系技术支持获取帮助

错误 4: Unknown system error
友好提示: 系统遇到内部错误,我们已记录此问题,请稍后重试。
严重程度: high
可重试: true
建议: 请联系技术支持获取帮助
*/

最佳实践

1. 分层错误处理

建立多层次的错误处理机制:

// 应用层错误处理
class ApplicationErrorHandler {
handleError(error: Error, context: any) {
// 记录错误
this.logError(error, context);

// 分类处理
if (error instanceof RecursionError) {
return this.handleRecursionError(error);
} else if (error instanceof ToolError) {
return this.handleToolError(error);
} else {
return this.handleGenericError(error);
}
}
}

// 节点层错误处理
const safeNode = (originalNode: Function) => {
return async (state: any, config?: any) => {
try {
return await originalNode(state, config);
} catch (error) {
console.error(`节点执行错误:`, error);
return {
error: error.message,
timestamp: new Date().toISOString(),
};
}
};
};

2. 错误分类和优先级

enum ErrorSeverity {
LOW = 'low',
MEDIUM = 'medium',
HIGH = 'high',
CRITICAL = 'critical',
}

interface ErrorInfo {
type: string;
severity: ErrorSeverity;
recoverable: boolean;
retryable: boolean;
}

const ERROR_CATALOG: Record<string, ErrorInfo> = {
TOOL_TIMEOUT: {
type: 'ToolTimeoutError',
severity: ErrorSeverity.MEDIUM,
recoverable: true,
retryable: true,
},
RECURSION_LIMIT: {
type: 'RecursionLimitError',
severity: ErrorSeverity.HIGH,
recoverable: false,
retryable: false,
},
NETWORK_ERROR: {
type: 'NetworkError',
severity: ErrorSeverity.MEDIUM,
recoverable: true,
retryable: true,
},
};

3. 监控和告警

class ErrorMonitor {
private errorCounts: Map<string, number> = new Map();
private alertThresholds: Map<string, number> = new Map();

recordError(error: Error) {
const errorType = error.constructor.name;
const count = this.errorCounts.get(errorType) || 0;
this.errorCounts.set(errorType, count + 1);

// 检查是否需要告警
const threshold = this.alertThresholds.get(errorType) || 10;
if (count + 1 >= threshold) {
this.sendAlert(errorType, count + 1);
}
}

private sendAlert(errorType: string, count: number) {
console.warn(`🚨 错误告警: ${errorType} 发生了 ${count}`);
// 发送到监控系统
}
}

4. 优雅降级

const gracefulDegradationNode = async (
state: typeof StateAnnotation.State,
config?: RunnableConfig
) => {
try {
// 尝试主要功能
return await primaryFunction(state, config);
} catch (error) {
console.warn('主要功能失败,使用备用方案:', error.message);

try {
// 尝试备用功能
return await fallbackFunction(state, config);
} catch (fallbackError) {
console.error('备用功能也失败,使用最小功能:', fallbackError.message);

// 最小功能保证
return {
messages: ['抱歉,服务暂时不可用,请稍后重试。'],
error: true,
};
}
}
};

常见问题和解决方案

问题 1:递归限制过早触发

原因:递归限制设置过低或图结构设计不合理

解决方案

  • 分析图的执行路径,优化图结构
  • 适当调整递归限制参数
  • 使用条件边避免不必要的循环

问题 2:工具调用频繁失败

原因:网络不稳定、API 限制或工具实现问题

解决方案

  • 实现指数退避重试机制
  • 添加工具调用超时设置
  • 提供工具调用的备用方案

问题 3:错误信息对用户不友好

原因:直接暴露技术错误信息

解决方案

  • 建立错误信息映射表
  • 提供上下文相关的错误提示
  • 添加错误恢复建议

小结与延伸

错误处理是构建健壮 LangGraph 应用的重要组成部分。通过合理的错误分类、恢复机制和监控策略,你可以构建出能够优雅处理各种异常情况的应用。

掌握了错误处理后,接下来我们将学习可视化,了解如何通过图表和工具来理解和调试 LangGraph 应用。

错误处理提示
  • 建立分层的错误处理机制
  • 为不同类型的错误制定不同的处理策略
  • 实现错误监控和告警系统
  • 提供用户友好的错误信息和恢复建议