第 28 课: 生产级 Agent — 错误处理、限流与安全

3 阅读4分钟

课程目标

掌握将 Agent 部署到生产环境所需的关键能力:结构化错误体系、最大迭代控制、超时机制、工具调用安全和成本控制。精读 errors.tsconstants.tsutils.ts 和关键中间件的实现。


28.1 Agent 的常见故障模式

在生产环境中,Agent 面临多种故障风险:

故障模式表现根因
死循环Agent 反复调用工具但不收敛LLM 无法判断何时停止
幻觉工具LLM 调用不存在的工具工具名称/参数幻觉
超时工具执行时间过长外部 API 响应慢
参数错误工具调用参数不符合 schemaLLM 理解 schema 有误
成本失控API 调用费用暴增无限循环或批量处理

28.2 错误类型体系

LangChain.js Agent 定义了结构化的错误类型,每种错误携带特定的上下文信息。

源码位置: libs/langchain/src/agents/errors.ts

28.2.1 MultipleToolsBoundError

当 LLM 已经预绑定了工具时抛出:

export class MultipleToolsBoundError extends Error {
  constructor() {
    super(
      "The provided LLM already has bound tools. " +
      "Please provide an LLM without bound tools to createAgent."
    );
  }
}

这个错误在 ReactAgent 构造函数中由 validateLLMHasNoBoundTools() 触发,防止工具被绑定两次。

28.2.2 ToolInvocationError

工具调用过程中的错误:

export class ToolInvocationError extends Error {
  public readonly toolCall: ToolCall;
  public readonly toolError: Error;

  constructor(toolError: unknown, toolCall: ToolCall) {
    const error = toolError instanceof Error ? toolError : new Error(String(toolError));
    super(
      `Error invoking tool '${toolCall.name}' with kwargs ${JSON.stringify(toolCall.args)} ` +
      `with error: ${error.stack}\n Please fix the error and try again.`
    );
    this.toolCall = toolCall;
    this.toolError = error;
  }
}

关键设计:错误消息中包含工具名称和参数,这些信息会被反馈给 LLM,帮助它修正下一次工具调用。

28.2.3 StructuredOutputParsingError

结构化输出解析失败:

export class StructuredOutputParsingError extends Error {
  public readonly toolName: string;
  public readonly errors: string[];

  constructor(toolName: string, errors: string[]) {
    super(
      `Failed to parse structured output for tool '${toolName}':` +
      `${errors.map(e => `\n  - ${e}`).join("")}.`
    );
  }
}

28.2.4 MultipleStructuredOutputsError

LLM 返回了多个结构化输出工具调用(只期望一个):

export class MultipleStructuredOutputsError extends Error {
  public readonly toolNames: string[];

  constructor(toolNames: string[]) {
    super(
      `The model has called multiple tools: ${toolNames.join(", ")} ` +
      "to return a structured output. This is not supported."
    );
  }
}

28.2.5 MiddlewareError

中间件执行过程中的错误,带有特殊的包装逻辑:

export class MiddlewareError extends Error {
  static readonly "~brand" = "MiddlewareError";

  private constructor(error: unknown, middlewareName: string) {
    const errorMessage = error instanceof Error ? error.message : String(error);
    super(errorMessage);
    this.name = error instanceof Error
      ? error.name
      : `${middlewareName[0].toUpperCase() + middlewareName.slice(1)}Error`;
  }

  static wrap(error: unknown, middlewareName: string): Error {
    // GraphBubbleUp 错误(如 GraphInterrupt)不包装,直接透传
    if (isGraphBubbleUp(error)) {
      return error;
    }
    return new MiddlewareError(error, middlewareName);
  }

  static isInstance(error: unknown): error is MiddlewareError {
    return error instanceof Error && "~brand" in error
      && error["~brand"] === "MiddlewareError";
  }
}

核心原则GraphBubbleUp 类型的错误(如人机协作的中断信号 GraphInterrupt)绝不包装,必须原样冒泡到图引擎。


28.3 最大迭代控制

28.3.1 recursionLimit — LangGraph 层面

LangGraph 的 recursionLimit 是防止无限循环的最后安全阀:

const result = await agent.invoke(
  { messages: [{ role: "user", content: "..." }] },
  { recursionLimit: 25 }  // 默认 25
);

28.3.2 modelCallLimitMiddleware — 精细控制

内置的 modelCallLimitMiddleware 提供更精细的模型调用限制:

import { createAgent, modelCallLimitMiddleware } from "langchain";

const agent = createAgent({
  model: "openai:gpt-4o",
  tools: [searchTool],
  middleware: [
    modelCallLimitMiddleware({
      runLimit: 5,         // 单次调用最多 5 次 LLM
      threadLimit: 100,    // 整个线程最多 100 次 LLM
      exitBehavior: "end", // 达到限制时优雅退出(vs "error" 抛异常)
    }),
  ],
});

两种退出行为:

  • "end":优雅退出,返回一条提示消息说明已达限制
  • "error":抛出异常,由调用方处理

28.3.3 toolCallLimitMiddleware — 工具调用限制

import { toolCallLimitMiddleware } from "langchain";

const agent = createAgent({
  model: "openai:gpt-4o",
  tools: [searchTool],
  middleware: [
    toolCallLimitMiddleware({
      maxToolCalls: 10,  // 单次运行最多 10 次工具调用
    }),
  ],
});

28.4 超时机制

28.4.1 AbortSignal

Agent 支持通过 AbortSignal 实现超时控制:

// 方式 1:在 createAgent 时设置全局 signal
const controller = new AbortController();
setTimeout(() => controller.abort(), 30000); // 30 秒超时

const agent = createAgent({
  model: "openai:gpt-4o",
  tools: [...],
  signal: controller.signal,
});

// 方式 2:在 invoke 时设置单次 signal
const result = await agent.invoke(
  { messages: [{ role: "user", content: "..." }] },
  { signal: AbortSignal.timeout(10000) }  // 10 秒超时
);

28.4.2 timeout 配置

LangGraph 也支持直接设置超时:

const result = await agent.invoke(
  { messages: [...] },
  { timeout: 30000 }  // 毫秒
);

28.5 工具调用安全

28.5.1 工具参数验证

ToolNode 在执行工具前会进行参数验证:

源码位置: libs/langchain/src/agents/nodes/ToolNode.ts

当 LLM 提供无效参数时,ToolInputParsingException 会被捕获并转化为 ToolMessage,反馈给 LLM 让它重试。

28.5.2 无效工具名称处理

如果 LLM 调用了不存在的工具:

const getInvalidToolError = (toolName: string, availableTools: string[]): string =>
  `Error: ${toolName} is not a valid tool, try one of [${availableTools.join(", ")}].`;

错误信息中列出所有可用工具,帮助 LLM 自我修正。

28.5.3 wrapToolCall 实现权限控制

通过中间件的 wrapToolCall 实现工具白名单和参数校验:

import { createMiddleware, ToolMessage } from "langchain";

const securityMiddleware = createMiddleware({
  name: "security",
  wrapToolCall: async (request, handler) => {
    // 工具白名单
    const allowedTools = ["search", "calculator"];
    if (!allowedTools.includes(request.toolCall.name)) {
      return new ToolMessage({
        content: `工具 ${request.toolCall.name} 不在白名单中,拒绝执行`,
        tool_call_id: request.toolCall.id,
      });
    }

    // 参数校验
    if (request.toolCall.name === "search") {
      const args = request.toolCall.args;
      if (args.query && args.query.length > 200) {
        return new ToolMessage({
          content: "搜索查询过长,请缩短到 200 字符以内",
          tool_call_id: request.toolCall.id,
        });
      }
    }

    return handler(request);
  },
});

28.6 LLM 预绑定工具的检测

validateLLMHasNoBoundTools() 防止工具被重复绑定:

源码位置: libs/langchain/src/agents/utils.ts:302

export function validateLLMHasNoBoundTools(llm: LanguageModelLike): void {
  if (typeof llm === "function") return;  // 函数形式无法验证

  let model = llm;
  // 处理 RunnableSequence 情况
  if (RunnableSequence.isRunnableSequence(model)) {
    model = model.steps.find(step => RunnableBinding.isRunnableBinding(step)) || model;
  }

  // 检查 RunnableBinding 是否绑定了工具
  if (RunnableBinding.isRunnableBinding(model)) {
    const hasToolsInKwargs = model.kwargs?.tools?.length > 0;
    const hasToolsInConfig = model.config?.tools?.length > 0;
    if (hasToolsInKwargs || hasToolsInConfig) {
      throw new MultipleToolsBoundError();
    }
  }
}

28.7 成本控制策略

28.7.1 模型调用限制

通过 modelCallLimitMiddleware 直接限制 LLM 调用次数。

28.7.2 运行时限制覆盖

可以在运行时动态调整限制:

const result = await agent.invoke(
  { messages: [...] },
  {
    context: {
      runLimit: 3,            // 覆盖默认限制
      exitBehavior: "end",
    },
  }
);

28.7.3 模型降级

通过 modelFallbackMiddleware 在主力模型失败时切换到成本更低的模型:

import { modelFallbackMiddleware } from "langchain";

const agent = createAgent({
  model: "openai:gpt-4o",
  tools: [...],
  middleware: [
    modelFallbackMiddleware({
      fallbackModels: ["openai:gpt-4o-mini"],
    }),
  ],
});

28.8 Human-in-the-Loop — 人机协作

对于敏感操作,通过 humanInTheLoopMiddleware 实现人工审批:

import { humanInTheLoopMiddleware, createAgent } from "langchain";
import { MemorySaver, Command } from "@langchain/langgraph";

const agent = createAgent({
  model: "openai:gpt-4o",
  tools: [deleteTool, transferTool],
  checkpointer: new MemorySaver(),
  middleware: [
    humanInTheLoopMiddleware({
      interruptOn: {
        delete_record: true,    // 删除操作需要审批
        transfer_money: true,   // 转账操作需要审批
      },
    }),
  ],
});

// 执行到敏感操作时会中断
const stream = await agent.stream(
  { messages: [...] },
  { configurable: { thread_id: "tx-001" } }
);

// 审批后恢复
await agent.stream(
  new Command({ resume: { [interruptId]: { decisions: [{ type: "approve" }] } } }),
  { configurable: { thread_id: "tx-001" } }
);

审批决策类型:

  • { type: "approve" }:批准执行
  • { type: "edit", editedAction: {...} }:修改参数后执行
  • { type: "reject" }:拒绝执行

28.9 实战练习:为 Agent 添加生产级防护

import { z } from "zod";
import {
  createAgent, createMiddleware, tool,
  modelCallLimitMiddleware, toolCallLimitMiddleware,
  humanInTheLoopMiddleware,
} from "langchain";
import { MemorySaver } from "@langchain/langgraph";

const dangerousTool = tool(
  async ({ action }) => `执行了操作: ${action}`,
  {
    name: "dangerous_action",
    description: "执行可能有风险的操作",
    schema: z.object({ action: z.string() }),
  }
);

const safeTool = tool(
  async ({ query }) => `查询结果: ${query}`,
  {
    name: "safe_query",
    description: "安全的查询操作",
    schema: z.object({ query: z.string() }),
  }
);

// 自定义安全审计中间件
const auditMiddleware = createMiddleware({
  name: "securityAudit",
  wrapToolCall: async (request, handler) => {
    console.log(`[审计] 工具调用: ${request.toolCall.name}`);
    console.log(`[审计] 参数: ${JSON.stringify(request.toolCall.args)}`);
    const startTime = Date.now();
    const result = await handler(request);
    console.log(`[审计] 耗时: ${Date.now() - startTime}ms`);
    return result;
  },
});

const agent = createAgent({
  model: "openai:gpt-4o",
  tools: [dangerousTool, safeTool],
  checkpointer: new MemorySaver(),
  middleware: [
    auditMiddleware,
    modelCallLimitMiddleware({ runLimit: 10, exitBehavior: "end" }),
    toolCallLimitMiddleware({ maxToolCalls: 5 }),
    humanInTheLoopMiddleware({
      interruptOn: { dangerous_action: true },
    }),
  ],
});

// 测试:超时控制
try {
  await agent.invoke(
    { messages: [{ role: "user", content: "查询并执行操作" }] },
    {
      signal: AbortSignal.timeout(15000),
      configurable: { thread_id: "safe-run-1" },
    }
  );
} catch (error) {
  console.error("Agent 执行失败:", (error as Error).message);
}

28.10 源码精读路线

优先级文件关注点
P0agents/errors.ts完整的错误类型体系
P0agents/utils.ts:302-366validateLLMHasNoBoundTools — 预绑定工具检测
P1agents/constants.tsJumpToTarget — 跳转目标常量
P1agents/middleware/modelCallLimit.ts模型调用限制中间件实现
P1agents/middleware/toolCallLimit.ts工具调用限制中间件实现
P2agents/middleware/hitl.tsHuman-in-the-loop 中间件实现
P2agents/middleware/modelRetry.ts模型重试中间件
P2agents/middleware/modelFallback.ts模型降级中间件

本课收获总结

级别你应该掌握的
🟢 基础理解 Agent 常见故障模式:死循环、幻觉工具、超时
🔵 中阶掌握 Agent 的超时控制(AbortSignal)与最大迭代限制(modelCallLimit)
🟡 高阶实现工具调用白名单与参数校验(wrapToolCall);理解 MiddlewareError 的包装逻辑
🟠 资深设计分级限流与成本控制方案;运行时动态调整限制参数
🔴 架构构建完整生产运维方案:监控 + 审计 + human-in-the-loop + 降级 + 超时

下一课预告

第 29 课进入 RAG 模块 —— 精读 Document 数据模型、BaseDocumentLoader 抽象、DocumentTransformerRecordManager 索引系统。