【TS版 2026 从零学Langchain 1.x】(三)Agent和Memory

一、智能体 Agent

我们前面已经在简单使用agent了,这部分我们详细聊聊agent。

和模型对话是一种 请求-响应的模式,这对于解决复杂问题显然不够,我们希望有一种机制能让模型自主解决问题,能记忆、调用工具、循环,于是Agent登场了。

一个最简单的agent创建如下。

ts 复制代码
import { createAgent } from "langchain";
const agent = createAgent({
  model,
  systemPrompt: SYSTEM_PROMPT,
  tools: [get_user_location, get_weather_for_location],
  responseFormat: toolStrategy(ResponseFormat),
  // debug: true, # 开启debug模式,会打印出agent的运行过程
  checkpointer=checkpointer,
  middleware=[]
});

可以看出重点的部分:

  • model: Agent的推理引擎
  • tools: Agent可以使用的工具(Tool calling)
  • responseFormat: Agent的输出格式(结构化输出)
  • checkpointer: Agent的状态检查点,用于保存和恢复Agent的状态(记忆)
  • middleware: 中间件

agent 本质上是一个有状态的有限状态机 (FSM) 。它会自动在"模型推理"和"工具执行"之间跳转/循环,直到问题解决。 而checkpointer会记录每次invoke后的完整状态(可理解为"存档")。

1. 中间件

先介绍下「中间件」的概念,因为后面很多能力都依赖「中间件」的机制(比如动态模型、短期记忆)。

对于学过web框架的同学一定对「中间件」有所了解,Langchian也是借助了一些成熟的工程设计思路,将「中间件」概念集成到Langchain,解决了以往存在的痛点:

  • 各种复杂配置
  • 侵入修改agent的逻辑

无中间件的Agent

有中间件的Agent

可以看出,Langchain中间件的本质就是钩子,在agent流程的每个步骤前后设置回调钩子,能对执行步骤进行拦截处理,从而达到更细粒度流程控制。

哪些常见的中间件呢?

  • 对话历史记录整理
  • 日志记录
  • 提示词转换
  • 工具选择
  • 重试、回退逻辑
  • 速率限制

具体怎么做呢?

使用 createMiddleware 函数创建中间件, Langchain提供了下面两种类型风格的钩子。

1.Node-style hooks:

  • beforeAgent - 代理启动前(每次invoke调用开始执行一次)
  • beforeModel - 每次模型调用前
  • afterModel - 每次模型响应后
  • afterAgent - 代理完成时(每个invoke调用结束执行一次)

例子:统计invoke调用次数,是否reach到顶。自定义中间件:

ts 复制代码
import { createMiddleware, AIMessage } from "langchain";

const createMessageLimitMiddleware = (maxMessages: number = 50) => {
  return createMiddleware({
    name: "MessageLimitMiddleware",
    beforeModel: {
      canJumpTo: ["end"],
      hook: (state) => {
        if (state.messages.length === maxMessages) {
          return {
            messages: [new AIMessage("Conversation limit reached.")],
            jumpTo: "end",
          };
        }
        return;
      }
    },
    afterModel: (state) => {
      const lastMessage = state.messages[state.messages.length - 1];
      console.log(`Model returned: ${lastMessage.content}`);
      return;
    },
  });
};

2.Wrap-style hooks:

  • wrapModelCall - 在每个模型调用周围
  • wrapToolCall - 在每个工具调用周围

例子:设置invoke失败重试的最大次数。自定义中间件如下:

ts 复制代码
import { createMiddleware } from "langchain";

const createRetryMiddleware = (maxRetries: number = 3) => {
  return createMiddleware({
    name: "RetryMiddleware",
    wrapModelCall: (request, handler) => {
      for (let attempt = 0; attempt < maxRetries; attempt++) {
        try {
          return handler(request);
        } catch (e) {
          if (attempt === maxRetries - 1) {
            throw e;
          }
          console.log(`Retry ${attempt + 1}/${maxRetries} after error: ${e}`);
        }
      }
      throw new Error("Unreachable");
    },
  });
};

下面我们会逐渐了解和学习到这些装饰器的使用。

2. 模型

模型的配置分为静态模型动态模型

  • 静态模型:在agent创建时就确定好的模型,比如OpenAI的gpt-3.5-turbogpt-4等。
  • 动态模型:在agent运行时根据上下文动态切换的模型,比如根据用户输入的问题选择不同的模型。

我们重点看下动态模型是如何实现的,这里给出一个场景:根据用户问题的复杂度,动态选择模型。

  • 使用qwen_32b模型判断问题复杂度
  • 简单,则使用deepseek V3.2;复杂,则使用GLM 4.7
  • 最终模型返回回答

动态模型的选择需要依靠「中间件」的方式来实现。

1.定义三个模型

ts 复制代码
function createChatModel(model: string, maxTokens: number) {
  return new ChatOpenAI({
    model,
    apiKey: settings.siliconflow_api_key,
    configuration: {
      baseURL: settings.siliconflow_base_url,
    },
    temperature: 0.9,
    maxTokens,
    timeout: 60_000,
  });
}

const glmModel = createChatModel(settings.glm_model, 10_000);
const dsModel = createChatModel(settings.ds_model, 10_000);
const qwenRouterModel = createChatModel(settings.qwen3_32b_model, 64);

2.工具函数,根据"模型响应"用来判断问题复杂度

ts 复制代码
/**
 * 从输入中提取最新的用户文本
 * @param input 输入内容,可能是字符串或包含消息数组的对象
 * @returns 最新的用户文本内容
 */
function extractLatestUserText(input: unknown): string {
  if (typeof input === "string") return input;

  const messages: BaseMessage[] | undefined = Array.isArray(input)
    ? (input as BaseMessage[])
    : (input as { messages?: BaseMessage[] } | null | undefined)?.messages;

  if (!messages?.length) return "";
  for (let i = messages.length - 1; i >= 0; i -= 1) {
    const msg = messages[i];
    if (msg instanceof HumanMessage) return String(msg.content);
  }
  return String(messages.at(-1)?.content ?? "");
}


/**
 * 判断用户问题的复杂度
 * @param userText 用户输入的文本
 * @returns 问题复杂度,"simple" 或 "complex"
 */
async function judgeComplexity(userText: string): Promise<"simple" | "complex"> {
  const response = await qwenRouterModel.invoke([
    {
      role: "system" as const,
      content:
        "你是问题复杂度分类器。根据用户问题判断复杂度:\n- simple:单一事实/常识问答、简单翻译/润色、很短的直接回答、无需多步推理或设计。\n- complex:需要多步推理、方案设计/架构、长文写作、复杂代码/调试、严谨数学推导、对比权衡。\n只输出:simple 或 complex。",
    },
    { role: "user" as const, content: userText },
  ]);

  const text = String((response as any)?.content ?? "")
    .trim()
    .toLowerCase();

  if (text === "simple" || text.includes("simple") || text.includes("简单")) return "simple";
  if (text === "complex" || text.includes("complex") || text.includes("复杂")) return "complex";
  return "complex";
}

3.定义中间件,拦截模型请求

ts 复制代码
const dynamicModelMiddleware = createMiddleware({
  name: "DynamicModelMiddleware",
  wrapModelCall: async (request, handler) => {
    const userText = extractLatestUserText({ messages: (request as any)?.messages });
    const complexity = await judgeComplexity(userText);
    const model = complexity === "simple" ? dsModel : glmModel;
    request.model = model
    return handler(request);
  },
});

4.测试

ts 复制代码
/** 测试动态模型选择 */
async function testDynamicModelSelection() {
  const checkpointer = new MemorySaver();

  const agent = createAgent({
    model: dsModel as any,
    checkpointer,
    contextSchema,
    middleware: [dynamicModelMiddleware],
  });

  const config = { configurable: { thread_id: "1" }, context: { userId: "1" } };

  const r1 = await agent.invoke(
    { messages: [{ role: "user", content: "1.9 和1.11 哪个数字大?" }] },
    config,
  );
  const ai1 = r1.messages.at(-1);
  console.log("响应内容:\n", ai1?.content);
  console.log("调用模型:\n", getModelNameFromMessage(ai1));
  /*
  调用模型:
  deepseek-ai/DeepSeek-V3.2-Exp
  */

  const r2 = await agent.invoke(
    {
      messages: [
        {
          role: "user",
          content: "请用langchain 1.x 设计一个简单的问答系统,用户可以向系统咨询某地的天气信息,包括天气工具调用。",
        },
      ],
    },
    config,
  );
  const ai2 = r2.messages.at(-1);
  console.log("响应内容:\n", ai2?.content);
  console.log("调用模型:\n", getModelNameFromMessage(ai2));

  /*
  调用模型:
  Pro/zai-org/GLM-4.7
  */
}

3. 工具

构建Agent的时候可以传入一个tools数组,绑定工具。

Agent的工具部分,包括三种场景:

  • 工具的错误处理
  • ReAct循环中使用
  • 动态工具

1.先说说工具的错误处理,有时候工具处理出错了,我们希望反馈给LLM 自定义的错误信息。

1.1 继续前面的例子中 "1.9 和1.11 哪个数字大?"的问题,有时候LLM回答不正确,那么我希望它能调用工具来回答。首先工具定义如下:

ts 复制代码
import { createAgent, createMiddleware, tool, toolStrategy } from "langchain";
const compareTwoNumbers = tool(
  ({ a, b }: { a: number; b: number }) => {
    if (a > b) return 1;
    if (a < b) return -1;
    return 0;
  },
  {
    name: "compare_two_numbers",
    description: "比较两个数字a,b的大小",
    schema: z.object({
      a: z.number().describe("第一个数字"),
      b: z.number().describe("第二个数字"),
    }),
  },
);

1.2 可能出现调用tool出错(比如传参错误,内部触发边界错误等等),那么,可以使用 wrapToolCall 定义tool调用阶段的钩子(中间件),来处理错误。

ts 复制代码
const handleToolErrorsMiddleware = createMiddleware({
  name: "HandleToolErrorsMiddleware",
  wrapToolCall: async (request, handler) => {
    try {
      return await handler(request);
    } catch (e) {
      // 返回自定义的错误消息给LLM
      return new ToolMessage({
        content: `Tool error: Please check your input and try again. (${e})`,
        tool_call_id: request.toolCall.id || '',
      });
    }
  },
});

1.3 测试agent

ts 复制代码
/** 2.测试工具:比较两个数字 */
async function testToolCompareTwoNumbers() {
  const checkpointer = new MemorySaver();

  const agent = createAgent({
    model: dsModel,
    checkpointer,
    tools: [compareTwoNumbers],
    contextSchema,
    middleware: [ handleToolErrorsMiddleware],
  });

  const r = await agent.invoke(
    { messages: [{ role: "user", content: "1.9 和 1.11 哪个数字大?" }] },
    { configurable: { thread_id: "1" }, context: { userId: "1" } },
  );
  const ai = r.messages.at(-1);
  console.log("响应内容:\n", ai?.content);
  console.log("调用模型:\n", getModelNameFromMessage(ai));


  /*
  响应内容:
  比较结果是 **1**,这意味着 1.9 > 1.11。

  所以,**1.9** 比 **1.11** 大。

  虽然1.11在小数点后有两位数字,但比较小数大小时是看整体数值:
  - 1.9 实际上是 1.90
  - 1.11 是 1.11
  - 1.90 > 1.11
  调用模型:
  deepseek-ai/DeepSeek-V3.2-Exp
  */
}

2.ReAct循环中使用。工具是可以在agent循环中被反复使用的。

2.1 上面的testToolCompareTwoNumbers就是一个例子:调用一次Tool compareTwoNumbers后发现 可以得出答案,就停止循环了返回结果。如果发现问题还没解决就会继续 思考/调用工具 循环,直到有最终答案。

3.动态工具。我们可以预先注册工具,然后根据上下文动态调用工具。

3.1 比如 我 希望不同用户角色,能调用的工具是不一样的。普通用户无法使用管理员才能调用的工具。

3.2 官方示例(拦截工具调用,替换tools):

ts 复制代码
import { createAgent, createMiddleware } from "langchain";

const toolSelectorMiddleware = createMiddleware({
  name: "ToolSelector",
  wrapModelCall: (request, handler) => {
    // Select a small, relevant subset of tools based on state/context
    const relevantTools = selectRelevantTools(request.state, request.runtime);
    const modifiedRequest = { ...request, tools: relevantTools };
    return handler(modifiedRequest);
  },
});

const agent = createAgent({
  model: "gpt-4.1",
  tools: allTools,
  middleware: [toolSelectorMiddleware],
});

4. 响应格式

1.createAgent提供了一个参数responseFormat,可以传入一个zod对象,用来约束输出,这块内容在 第二篇有介绍过。底层是LLM模型的结构化输出强约束和Langchain的校验兜底。

2.如果没有 responseFormat,需要从agent.invoke 的结果的messages中取最后一条消息,得到最终的回答,并且是字符串.

3.而使用 responseFormat后,agent.invoke 的结果会多一个structuredResponse字段, 并且是一个结构化对象

4.我们继续之前的比较数字大小的例子,这次对响应结果加约束。

ts 复制代码
const compareResultSchema = z.object({
  num1: z.number().describe("第一个数字"),
  num2: z.number().describe("第二个数字"),
  result: z.number().int().describe("比较结果,1 表示 num1 大于 num2,-1 表示 num1 小于 num2,0 表示相等"),
});


/** 测试响应格式 */
async function testResponseFormat() {
  const checkpointer = new MemorySaver();

  const agent = createAgent({
    model: glmModel,
    checkpointer,
    tools: [compareTwoNumbers],
    contextSchema,
    responseFormat: compareResultSchema,
    middleware: [handleToolErrorsMiddleware],
  });

  const r = await agent.invoke(
    { messages: [{ role: "user", content: "1.9 和 1.11 哪个数字大?" }] },
    { configurable: { thread_id: "1" }, context: { userId: "1" } },
  );

  console.log(r.structuredResponse);
  /*
  {
    num1: 1.9,
    num2: 1.11,
    result: 1,
  }
  */
  console.log(r.messages.at(-1)?.content);
  /*
    Returning structured response: {"num1":1.9,"num2":1.11,"result":1}
  */
}

5. 状态检查点

checkpointer 是用来保存和恢复 agent 的状态。它具备以下能力

  • 记忆能力(记忆历史消息)
  • 线程隔离
  • 故障恢复和"时空旅行"

记录历史对话记录

如果你不传递checkpointer,那么agent是没有记忆能力的,下面例子中模型将无法记住你的名字

ts 复制代码
async function testNoCheckpointer() {
  console.log("\n" + "=".repeat(50));
  console.log("测试 1: createAgent 不带 checkpointer (应该无记忆)");
  console.log("=".repeat(50));

  const agent = createAgent({ model });
  const config = { configurable: { thread_id: "1" } };

  console.log("\n【步骤 1】\n [用户]: 嗨!我叫 Bob。");
  const response1 = await agent.invoke(
    { messages: [{ role: "user", content: "嗨!我叫 Bob。" }] },
    config,
  );
  console.log(`[Agent]: ${response1.messages.at(-1)?.content ?? ""}`);

  console.log("\n【步骤 2】\n [用户]: 我叫什么名字?");
  const response2 = await agent.invoke(
    { messages: [{ role: "user", content: "我叫什么名字?" }] },
    config,
  );
  console.log(`[Agent]: ${response2.messages.at(-1)?.content ?? ""}`);
}

下面例子中模型能记住你的名字。

ts 复制代码
async function testWithCheckpointer() {
  console.log("\n" + "=".repeat(50));
  console.log("测试 2: createAgent 带 checkpointer (应该有记忆)");
  console.log("=".repeat(50));

  const checkpointer = new MemorySaver();
  const agent = createAgent({ model, checkpointer });
  const config = { configurable: { thread_id: "thread-1" } };

  console.log("\n【步骤 1】\n [用户]: 嗨!我叫 Alice。");
  const response1 = await agent.invoke(
    { messages: [{ role: "user", content: "嗨!我叫 Alice。" }] },
    config,
  );
  console.log(`[Agent]: ${response1.messages.at(-1)?.content ?? ""}`);

  console.log("\n【步骤 2】\n [用户]: 我叫什么名字?");
  const response2 = await agent.invoke(
    { messages: [{ role: "user", content: "我叫什么名字?" }] },
    config,
  );
  console.log(`[Agent]: ${response2.messages.at(-1)?.content ?? ""}`);
}

线程隔离

所谓线程隔离就是你可以用同一个agent 发起多个会话,每个会话是独立的,互不干扰的。 下面的例子演示了,什么是线程隔离。通过第二个参数 { configurable: { thread_id: "thread-A" } } 设置线程。

python 复制代码
def test_checkpointer_thread_isolation():
    """
    测试 3: create_agent 使用 checkpointer 和不同的 thread_id。
    预期:记忆通过 thread_id 隔离。
    """
    print("\n" + "="*50)
    print("测试 3: create_agent 线程隔离")
    print("="*50)
    
    memory = InMemorySaver()
    agent = create_agent(qwen3_32b_model, checkpointer=memory)
    
    # 1. 线程 A 交互
    print("\n[线程 A] 用户: 嗨!我叫 Charlie。")
    agent.invoke(
        {"messages": [{"role": "user", "content": "嗨!我叫 Charlie。"}]},
        {"configurable": {"thread_id": "thread-A"}}
    )

    # 2. 线程 B 交互 
    print("\n[线程 B] 用户: 你好!我叫 疯狂踩坑人")
    agent.invoke(
        {"messages": [{"role": "user", "content": "你好!我叫 疯狂踩坑人"}]},
        {"configurable": {"thread_id": "thread-B"}}
    )
    
    
    # 3. 线程 A 交互 (问名字)
    print("\n[线程 A] 用户: 我叫什么名字?")
    response_a = agent.invoke(
        {"messages": [{"role": "user", "content": "我叫什么名字?"}]},
        {"configurable": {"thread_id": "thread-A"}}
    )
    print(f"[线程 A] Agent: {response_a['messages'][-1].content}")


    # 4. 线程 B 交互 (问名字)
    print("\n[线程 B] 用户: 我叫什么名字?")
    response_b = agent.invoke(
        {"messages": [{"role": "user", "content": "我叫什么名字?"}]},
        {"configurable": {"thread_id": "thread-B"}}
    )
    print(f"[线程 B] Agent: {response_b['messages'][-1].content}")
    

检查点 checkpoint

运行下面代码,查看memory的变化

python 复制代码
async function testCheckpoints() {
  console.log("\n" + "=".repeat(50));
  console.log("测试 4: 检查 checkpointer 保存的 checkpoint");
  console.log("=".repeat(50));

  const checkpointer = new MemorySaver();
  const agent = createAgent({ model, checkpointer });
  const config = { configurable: { thread_id: "thread-1" } };

  console.log("\n[用户]: 嗨!我叫 疯狂踩坑人。");
  const response = await agent.invoke(
    { messages: [{ role: "user", content: "嗨!我叫 疯狂踩坑人。" }] },
    config,
  );
  console.log(`[Agent]: ${response.messages.at(-1)?.content ?? ""}`);

  const checkpoints: unknown[] = [];
  for await (const checkpoint of checkpointer.list({ configurable: { thread_id: "thread-1" } })) {
    checkpoints.push(checkpoint);
  }
  console.log("checkpoint 数量:", checkpoints.length);
  for (const c of checkpoints) {
    console.log(c, "\n");
  }
}

memory保存了很多检查点,每个检查点都有idts(时间)和channel_values.messages等信息。

每个checkpoint代表一次存档,通过某一个checkpoint就可以恢复agent的状态,这样你可以穿越回去到之前invoke的任一个状态,这非常重要。

二、记忆 Memory

短期记忆 Short-term memory

短期记忆主要指在单个会话内,LLM 能够"记得"刚刚说过的话。它的目的是保持对话的连贯性。

管理消息 - SummarizationMiddleware

1.前面提到的checkpointer 会保存你的历史消息,但是这样会存在两个问题:

  • LLM 上下文限制:发送给大模型的 Token 数量超过其最大窗口,导致报错。
  • 成本与延迟增加:每次调用都会携带冗长的历史记录,消耗更多 Token 且模型响应变慢

2.针对这些问题,业界的处理方案基本就是:

  • 裁剪最早的消息,保持一个固定大小的"窗口"
  • 对裁剪的历史消息,使用LLM进行总结,作为一条

3.下面用一个例子来演示,Langchain是如何通过SummarizationMiddleware管理对话消息的。这个中间件做的事就是上面的方案:裁剪+总结。

ts 复制代码
import { createAgent, summarizationMiddleware } from "langchain";

const systemPrompt = "你是一个人工智能助手";

function buildModels() {
  const baseConfig = {
    apiKey: settings.siliconflow_api_key,
    configuration: {
      baseURL: settings.siliconflow_base_url,
    },
    timeout: 60_000,
  } as const;

  const agentModel = new ChatOpenAI({
    ...baseConfig,
    model: settings.qwen3_32b_model,
    temperature: 0.7,
    maxTokens: 2000,
  });

  const summarizerModel = new ChatOpenAI({
    ...baseConfig,
    model: settings.qwen3_32b_model,
    temperature: 0.2,
    maxTokens: 512,
  });

  return { agentModel, summarizerModel };
}


async function testSummarizationShortMemory() {
  const { agentModel, summarizerModel } = buildModels();
  const checkpointer = new MemorySaver();
  const agent = createAgent({
    model: agentModel,
    tools: [],
    systemPrompt,
    middleware: [
      summarizationMiddleware({
        model: summarizerModel,
        trigger: { messages: 4 }, // 也可以按 tokens 来
        keep: { messages: 4 }, // 也可以按 tokens 来
        summaryPrefix: "对话摘要:",
        summaryPrompt:
          "请将以下对话历史压缩成简短的中文摘要,保留关键信息(事实、偏好、约束、决定、结论):\n{messages}",
      }),
    ],
    checkpointer,
  });

  const userInputs = [
    "我叫小明,住在北京。",
    "请记住我更喜欢用中文回答。",
    "我这周想做一个 LangChain 的学习计划。简短控制在100字",
    "计划要按天拆分,每天不超过1小时。简短控制在100字",
    "顺便提醒我:周三晚上要健身。最后请把所有安排再用要点总结一次。",
  ];

  const config = { configurable: { thread_id: "short-memory-demo" } };

  for (let idx = 0; idx < userInputs.length; idx += 1) {
    const text = userInputs[idx] || '';
    const r = await agent.invoke({ messages: [{ role: "user", content: text }] }, config);
    const messages = r.messages;
    const last = messages.at(-1);

    console.log(
      `\n[Turn ${idx + 1}] 当前上下文消息数:${messages.length}(trigger=4, keep=4)\n`,
    );
    console.log(`用户:${text}`);
    console.log(`助手:${String(last?.content ?? "")}`);
  }
}       

4.trigger: { messages: 4 },keep: { messages: 4 } 表示当消息超过4条(不包含system_prompt)时触发裁剪,裁剪的信息保留4条。 这样一来,「消息窗口」大小控制在4。

5.理论上keep可以大于trigger,这样总结消息和keep的消息在内容上就会有重叠。

6.trigger和keep 除了通过message控制,还可以通过tokens控制,比如超过多少tokens就压缩。

7.从第三轮开始,当前消息数:6. 这意味下一次对话前,就会触发压缩,实际消息窗口大小为4.

8.下面是第三轮对话的messages打印结果,可以看到前面总结的消息。可以发现Langchain默认的做法是: 使用了总结性提示'here is a summary of ...',将1,2,3,4次消息发给了LLM做总结'

9.此外,Langchain还提供了RemoveMessage 来删除指定的消息,这个结合Langgraph会比较实用。

存储介质

import { MemorySaver } from "@langchain/langgraph"; 中的MemorySaver是将状态数据保存在内存中,那么程序已结束,这些状态就会丢失。所以官方更推荐在生产环境使用 PostgresSaver

bash 复制代码
bun add @langchain/langgraph-checkpoint-postgres

在生产环境中,使用一个由数据库支持的检查点:

ts 复制代码
import { PostgresSaver } from "@langchain/langgraph-checkpoint-postgres";

const DB_URI = "postgresql://postgres:postgres@localhost:5442/postgres?sslmode=disable";
const checkpointer = PostgresSaver.fromConnString(DB_URI);

当然,除了Postgres数据库介质,还有其他存储介质,见文档,比如Sqlite。

bash 复制代码
bun add @langchain/langgraph-checkpoint-sqlite

但是这有个问题,@langchain/langgraph-checkpoint-sqlite 包依赖了Node的better-sqlite3,这个包目前不支持bun. 所以,只能用替代品:

bash 复制代码
bun add @beshkenadze/langgraph-checkpoint-libsql
ts 复制代码
import { SqliteSaver } from "@beshkenadze/langgraph-checkpoint-libsql";

async function testSqliteSaver() {
  const { agentModel } = buildModels();

  const checkpointer = SqliteSaver.fromConnString("file:checkpoints.db");
  const agent = createAgent({
    model: agentModel,
    tools: [],
    systemPrompt,
    checkpointer,
  });

  const config = { configurable: { thread_id: "test_sqlite_saver" } };

  const r1 = await agent.invoke(
    { messages: [{ role: "user", content: "你好,我叫"疯狂踩坑人"" }] },
    config,
  );
  console.log("[assistant]", String(r1.messages.at(-1)?.content ?? ""));
  // [assistant] 
  // 我是你的AI助手!不过"疯狂踩坑人"这个名字挺有...

  const r2 = await agent.invoke(
    { messages: [{ role: "user", content: "请问我叫什么名字?" }] },
    config,
  );
  console.log("[assistant]", String(r2.messages.at(-1)?.content ?? ""));
  // [assistant] 
  // 你叫"疯狂踩坑人"呀...
}

你会发现运行目录下多出一个checkpoints.db的数据库文件。 用sqlite客户端工具(比如vscode插件Database Client)打开查看checkpoints表,可以看到,这些数据都保存到了表里。

长期记忆 Long-term memory

长期记忆是指跨越多天、多周甚至数个不同会话,系统依然能记得用户的偏好、事实或历史背景。

  • 核心机制: 检索增强生成(RAG)与外部数据库。它不直接塞进当前的 Prompt 窗口,而是通过"按需检索"的方式工作。
  • LangChain 实现方式:
    • 向量数据库(Vector Stores): 如 Pinecone, Milvus, Chroma。将历史对话或知识切片并嵌入(Embedding),当用户提问时,通过语义搜索找回相关片段。
    • VectorStoreRetrieverMemory: LangChain 特有的组件,允许将向量数据库作为记忆组件挂载。
    • 实体记忆(Entity Memory): 提取对话中的特定实体(如"我的名字叫 疯狂踩坑人")并存入结构化数据库。
  • 存储位置: 外部持久化数据库(磁盘)。
  • 优势: 理论上拥有无限容量,且不会占用不必要的 Token。只有当相关信息被触发时,才会被提取出来。

这个我们后面再聊,后面会慢慢介绍向量数据库和RAG。

相关推荐
mCell14 小时前
如何零成本搭建个人站点
前端·程序员·github
mCell15 小时前
为什么 Memo Code 先做 CLI:以及终端输入框到底有多难搞
前端·设计模式·agent
恋猫de小郭15 小时前
AI 在提高你工作效率的同时,也一直在增加你的疲惫和焦虑
前端·人工智能·ai编程
少云清15 小时前
【安全测试】2_客户端脚本安全测试 _XSS和CSRF
前端·xss·csrf
deephub16 小时前
Agent Lightning:微软开源的框架无关 Agent 训练方案,LangChain/AutoGen 都能用
人工智能·microsoft·langchain·大语言模型·agent·强化学习
银烛木16 小时前
黑马程序员前端h5+css3
前端·css·css3
m0_6070766016 小时前
CSS3 转换,快手前端面试经验,隔壁都馋哭了
前端·面试·css3
听海边涛声16 小时前
CSS3 图片模糊处理
前端·css·css3
IT、木易16 小时前
css3 backdrop-filter 在移动端 Safari 上导致渲染性能急剧下降的优化方案有哪些?
前端·css3·safari
0思必得016 小时前
[Web自动化] Selenium无头模式
前端·爬虫·selenium·自动化·web自动化