课程目标
掌握将 Agent 部署到生产环境所需的关键能力:结构化错误体系、最大迭代控制、超时机制、工具调用安全和成本控制。精读 errors.ts、constants.ts、utils.ts 和关键中间件的实现。
28.1 Agent 的常见故障模式
在生产环境中,Agent 面临多种故障风险:
| 故障模式 | 表现 | 根因 |
|---|---|---|
| 死循环 | Agent 反复调用工具但不收敛 | LLM 无法判断何时停止 |
| 幻觉工具 | LLM 调用不存在的工具 | 工具名称/参数幻觉 |
| 超时 | 工具执行时间过长 | 外部 API 响应慢 |
| 参数错误 | 工具调用参数不符合 schema | LLM 理解 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 源码精读路线
| 优先级 | 文件 | 关注点 |
|---|---|---|
| P0 | agents/errors.ts | 完整的错误类型体系 |
| P0 | agents/utils.ts:302-366 | validateLLMHasNoBoundTools — 预绑定工具检测 |
| P1 | agents/constants.ts | JumpToTarget — 跳转目标常量 |
| P1 | agents/middleware/modelCallLimit.ts | 模型调用限制中间件实现 |
| P1 | agents/middleware/toolCallLimit.ts | 工具调用限制中间件实现 |
| P2 | agents/middleware/hitl.ts | Human-in-the-loop 中间件实现 |
| P2 | agents/middleware/modelRetry.ts | 模型重试中间件 |
| P2 | agents/middleware/modelFallback.ts | 模型降级中间件 |
本课收获总结
| 级别 | 你应该掌握的 |
|---|---|
| 🟢 基础 | 理解 Agent 常见故障模式:死循环、幻觉工具、超时 |
| 🔵 中阶 | 掌握 Agent 的超时控制(AbortSignal)与最大迭代限制(modelCallLimit) |
| 🟡 高阶 | 实现工具调用白名单与参数校验(wrapToolCall);理解 MiddlewareError 的包装逻辑 |
| 🟠 资深 | 设计分级限流与成本控制方案;运行时动态调整限制参数 |
| 🔴 架构 | 构建完整生产运维方案:监控 + 审计 + human-in-the-loop + 降级 + 超时 |
下一课预告
第 29 课进入 RAG 模块 —— 精读 Document 数据模型、BaseDocumentLoader 抽象、DocumentTransformer 和 RecordManager 索引系统。