高级功能

🚀 性能优化

让 LangGraph.js 应用更快、更省钱、更稳定:从监控、缓存到并发与限流

📚 学习目标

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

  • 为 Graph 的关键节点添加可读的性能指标(耗时、次数、成本估算)
  • 用缓存、批处理、并发控制降低 LLM 与工具调用的总体成本
  • 给 Graph 加上合理的“上限”(recursion、重试、超时),避免失控
  • 把性能问题拆成可定位的瓶颈:模型慢、工具慢、状态膨胀、分支过多

前置知识

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

你需要了解:

  • JavaScript 的 async/await
  • 基础的缓存概念(key/value、TTL)

1️⃣ 先别优化:先量化(Measure First)

LangGraph 的“慢”,通常来自三类:

  1. 模型调用慢:网络 + 模型推理 + 输出 token
  2. 工具调用慢:数据库/API/IO
  3. 状态变大:messages 越积越多,LLM 输入越来越长

你要做的第一件事不是改代码,而是加一个“最小可用”的监控。


2️⃣ 节点耗时监控:一个轻量装饰器

最常见的做法:对每个节点包一层 wrapper,记录执行耗时。

type NodeFn<S, U> = (state: S) => Promise<U> | U;

export function withTiming<S, U>(nodeName: string, node: NodeFn<S, U>) {
  return async (state: S): Promise<U> => {
    const start = performance.now();
    try {
      const result = await node(state);
      const ms = performance.now() - start;
      console.log(`[timing] ${nodeName}: ${ms.toFixed(1)}ms`);
      return result;
    } catch (err) {
      const ms = performance.now() - start;
      console.error(`[timing] ${nodeName} failed after ${ms.toFixed(1)}ms`, err);
      throw err;
    }
  };
}

代码解析

  1. 这段代码不依赖 LangGraph API,本质是一个函数装饰器。
  2. 你可以把它用在“工具节点”“检索节点”“模型节点”等所有耗时点。

3️⃣ 控制 Graph 上限:循环、重试与超时

性能优化里最容易被忽略的不是“快”,而是“别跑爆”。

1. recursionLimit:避免无限循环

当你使用 ReAct Loop(Agent -> Tools -> Agent)时,一定要设置上限。

await app.invoke(
  { messages: [{ role: 'user', content: '...' }] },
  {
    recursionLimit: 20,
    configurable: {
      thread_id: 'user-1:session-1',
    },
  }
);

2. 工具调用超时:把外部慢依赖隔离

function withTimeout<T>(p: Promise<T>, ms: number): Promise<T> {
  return new Promise((resolve, reject) => {
    const timer = setTimeout(() => reject(new Error(`timeout after ${ms}ms`)), ms);
    p.then(
      (v) => {
        clearTimeout(timer);
        resolve(v);
      },
      (e) => {
        clearTimeout(timer);
        reject(e);
      }
    );
  });
}

async function searchWithTimeout(query: string) {
  return withTimeout(fetch(`https://example.com?q=${encodeURIComponent(query)}`).then(r => r.json()), 5000);
}

4️⃣ 让状态别长胖:messages 裁剪与摘要

聊天应用的性能问题,很多不是“执行慢”,而是“输入越来越长”。

一个可操作的策略是:

  • 保留最近 N 轮消息
  • 更早的消息压缩为“摘要”字段
import { Annotation } from '@langchain/langgraph';
import { BaseMessage } from '@langchain/core/messages';

const State = Annotation.Root({
  messages: Annotation<BaseMessage[]>({
    reducer: (current, update) => {
      const next = current.concat(update);
      const keep = 20;
      return next.length > keep ? next.slice(next.length - keep) : next;
    },
    default: () => [],
  }),
  summary: Annotation<string>({
    default: () => '',
  }),
});

说明

  • 这不是“唯一正确答案”,但它能把最常见的成本爆炸问题压住。
  • 更完善的方案:把“摘要更新”做成一个独立节点,用模型对历史做压缩。

5️⃣ 缓存:对重复查询/重复工具调用很有效

最适合缓存的通常是:

  • 文档检索结果(RAG)
  • 结构化工具调用结果(天气、汇率、配置)
type CacheValue<T> = { value: T; expiresAt: number };

export class SimpleTTLCache<T> {
  private map = new Map<string, CacheValue<T>>();
  constructor(private ttlMs: number) {}

  get(key: string): T | undefined {
    const hit = this.map.get(key);
    if (!hit) return undefined;
    if (Date.now() > hit.expiresAt) {
      this.map.delete(key);
      return undefined;
    }
    return hit.value;
  }

  set(key: string, value: T) {
    this.map.set(key, { value, expiresAt: Date.now() + this.ttlMs });
  }
}

💡 练习题

  1. 实现题:给你的“检索节点”加上 TTL 缓存(5 分钟),缓存 key 使用 query + topK
  2. 压测题:连续调用 50 次 Graph,打印每个节点的 P50/P95(可以先用数组收集耗时)。
  3. 设计题:如果你要做“多用户同时聊天”,你会把缓存做成进程内、Redis 还是数据库?为什么?

📚 参考资源

官方文档


✅ 总结

本章要点

  • 性能优化从“可测量”开始:先给节点加 timing,再决定优化点。
  • 通过上限(recursion/timeout/retry)避免系统失控。
  • 控制状态体积(messages 裁剪/摘要)往往比“微优化”更有效。
  • 缓存与并发控制是“省钱”的关键手段。

下一步:当你准备上线,请补齐《测试策略》,确保你每次改动都能快速回归验证。

登录以继续阅读

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

立即登录

On this page