langchain TS 基本用法入门

https://bailian.console.aliyun.com/ 百炼为新用户提供北京地域专属的新人免费额度,用于体验模型调用。 未认证用户免费额度用完后无法继续使用,需要完成认证并充值后方能继续按量付费。

对于熟悉 TypeScript 的开发者来说,LangChain.js 是一个很实用的选择,它让你可以直接在 Node.js 环境下构建 AI 应用,而不必切换到 Python。

它的核心思想是提供一套标准化的"组件",让你像搭积木一样把它们组合起来。理解下面这几个核心概念,就能快速上手。

🧩 核心概念:一切皆 Runnable

LangChain.js 里几乎所有东西,比如模型、提示模板、解析器,都实现了一个统一的 Runnable 接口。这意味着它们都有统一的调用方式,可以互相连接:

  • invoke(input):单次调用,返回完整结果。
  • stream(input):流式调用,逐块返回结果。
  • batch(inputs):批量调用。

正是因为接口统一,你才能用管道(pipe)把它们串起来。

🔗 LCEL:用 .pipe() 连接组件

LCEL (LangChain Expression Language) 是组合组件的官方方式。它的核心是把上一个组件的输出,作为下一个组件的输入。

⚠️ 一个关键的坑 :Python 版本可以用 | 运算符来连接组件,但 JavaScript 不行 ,因为 JS 不支持运算符重载。在 TS 里必须使用 .pipe() 方法。

typescript 复制代码
// 这是一个典型的链式结构:提示模板 → 模型 → 输出解析器
const chain = prompt.pipe(model).pipe(outputParser);

📝 快速上手代码示例

下面是一个最小可运行的例子,它把提示模板、模型和输出解析器串成了一条链。

typescript 复制代码
// 1. 导入所需组件
import "dotenv/config"; // 加载 .env 文件中的 API Key
import { ChatOpenAI } from "@langchain/openai";
import { ChatPromptTemplate } from "@langchain/core/prompts";
import { StringOutputParser } from "@langchain/core/output_parsers";

// 2. 定义提示模板
// 使用 fromMessages 可以灵活定义系统角色和用户输入
const prompt = ChatPromptTemplate.fromMessages([
  ["system", "你是一个简洁的技术助手,回答控制在三句话以内。"],
  ["human", "{question}"], // {question} 是占位符
]);

// 3. 初始化模型
const model = new ChatOpenAI({
  modelName: "qwen-max",
  temperature: 0.7,
  apiKey: process.env.DASHSCOPE_API_KEY,
  configuration: {
    baseURL: "https://dashscope.aliyuncs.com/compatible-mode/v1",
  },
});

// 4. 定义输出解析器
// 它的作用是从模型的 AIMessage 对象里提取纯文本 content
const outputParser = new StringOutputParser();

// 5. 用 .pipe() 组合成链
const chain = prompt.pipe(model).pipe(outputParser);

// 6. 调用链并传入变量
const answer = await chain.invoke({ 
  question: "用一句话解释什么是 Runnable?" 
});
console.log(answer); // 输出的是纯字符串

🚀 下一步可以探索什么

打好基础后,你可以按这个顺序继续深入,这也是官方推荐的学习路径:

  1. 流式输出 (Streaming) :把最后的 .invoke 换成 .stream,就能实现类似 ChatGPT 的打字机效果。
  2. 函数调用与工具 (Tools):让模型能够调用你写的函数,比如查询天气、执行计算。
  3. 智能体 (Agents):组合模型和工具,让 AI 能够自己决定"下一步该做什么",比如先查资料再回答。
  4. 检索增强生成 (RAG):把你的文档(PDF、网页等)变成模型可以查询的知识库,让它基于你的私有数据回答。

LangChain.js 的灵活性很高,你可以从最简单的链开始,逐步增加复杂度。

调用工具

接下来我们学习函数调用与工具(Tools)。这是让大模型从"聊天"走向"干活"的关键一步。

核心思想:模型负责"决策",你的代码负责"执行"

首先要澄清一个关键概念:模型本身并不会真的去调用你的函数

整个流程分为三步:

  1. 决策(模型) :模型看到你提供的工具描述后,判断是否需要调用工具。如果需要,它会生成一个结构化的 tool_calls 对象,包含工具名称参数
  2. 执行(你的代码):你编写的代码接收到这个对象,用参数去执行真正的函数。
  3. 响应(模型) :你把函数执行的结果(ToolMessage)传回模型,模型据此生成最终的自然语言回答。

🛠️ 第一步:定义一个工具

使用 @langchain/core/toolstool() 函数来定义工具。它需要三个关键信息:执行函数名称描述 ,以及用 Zod 定义的参数结构

为什么必须用 Zod? Zod 不仅让 LangChain 能自动把参数结构转换成模型能理解的 JSON Schema,还能在调用时自动验证参数类型,省去手动写校验逻辑的麻烦。

typescript 复制代码
import { tool } from "@langchain/core/tools";
import { z } from "zod";

const calculatorTool = tool(
  // 1. 执行函数:接收经过 Zod 验证的参数,返回字符串
  async ({ operation, number1, number2 }) => {
    if (operation === "add") return `${number1 + number2}`;
    if (operation === "subtract") return `${number1 - number2}`;
    if (operation === "multiply") return `${number1 * number2}`;
    if (operation === "divide") return `${number1 / number2}`;
    throw new Error("Invalid operation");
  },
  // 2. 工具配置
  {
    name: "calculator",
    // 描述至关重要:模型完全依赖它来决定何时调用这个工具
    description: "Can perform mathematical operations.",
    schema: z.object({
      operation: z
        .enum(["add", "subtract", "multiply", "divide"])
        .describe("The type of operation to execute."),
      number1: z.number().describe("The first number to operate on."),
      number2: z.number().describe("The second number to operate on."),
    }),
  }
);

description 和每个参数的 .describe() 都直接传递给模型,是模型判断"该不该用"和"怎么用"的唯一依据。

🔗 第二步:绑定工具到模型

定义好工具后,用 bindTools() 告诉模型有哪些工具可用。你可以绑定多个工具,模型会自动根据用户问题选择最合适的一个。

typescript 复制代码
import { ChatOpenAI } from "@langchain/openai";

const model = new ChatOpenAI({ model: "gpt-4o", temperature: 0 });
const modelWithTools = model.bindTools([calculatorTool]);

⚙️ 第三步:处理工具调用(核心模式)

绑定工具后,模型不会直接返回文本,而是可能返回 tool_calls。你需要手动执行工具,把结果送回模型。

一个常见的坑bindTools 本身不会执行 工具,它只让模型"知道"工具的存在。tool_calls 的解析和执行需要你自己写逻辑。

typescript 复制代码
import { tool } from "@langchain/core/tools";
import { z } from "zod";
import "dotenv/config"; // 加载 .env 文件中的 API Key
import { ChatOpenAI } from "@langchain/openai";
import { HumanMessage } from "@langchain/core/messages";

const calculatorTool = tool(
  // 1. 执行函数:接收经过 Zod 验证的参数,返回字符串
  async ({ operation, number1, number2 }) => {
    if (operation === "add") return `${number1 + number2}`;
    if (operation === "subtract") return `${number1 - number2}`;
    if (operation === "multiply") return `${number1 * number2}`;
    if (operation === "divide") return `${number1 / number2}`;
    throw new Error("Invalid operation");
  },
  // 2. 工具配置
  {
    name: "calculator",
    // 描述至关重要:模型完全依赖它来决定何时调用这个工具[citation:1]
    description: "Can perform mathematical operations.",
    schema: z.object({
      operation: z
        .enum(["add", "subtract", "multiply", "divide"])
        .describe("The type of operation to execute."),
      number1: z.number().describe("The first number to operate on."),
      number2: z.number().describe("The second number to operate on."),
    }),
  }
);

const model = new ChatOpenAI({
  modelName: "qwen-max",
  temperature: 0.7,
  apiKey: process.env.DASHSCOPE_API_KEY,
  configuration: {
    baseURL: "https://dashscope.aliyuncs.com/compatible-mode/v1",
  },
});

const modelWithTools = model.bindTools([calculatorTool]);

// 1. 用户提问,模型决策
const response = await modelWithTools.invoke([
  new HumanMessage("What is 3 multiplied by 12?")
]);

// 2. 检查模型是否决定调用工具
if (response.tool_calls && response.tool_calls.length > 0) {
  const toolCall = response.tool_calls[0];
  console.log("模型选择的工具:", toolCall.name); // "calculator"
  console.log("模型生成的参数:", toolCall.args); // { operation: "multiply", number1: 3, number2: 12 }

  // 3. 你的代码执行工具
  const toolResult = await calculatorTool.invoke(toolCall);

  // 4. calculatorTool.invoke 已经返回了带 tool_call_id 的 ToolMessage
  const finalResponse = await modelWithTools.invoke([
    new HumanMessage("What is 3 multiplied by 12?"),
    response, // 模型自己的 tool_calls 响应
    toolResult,
  ]);

  console.log(finalResponse.content); // "36"
}

🎛️ 进阶控制:tool_choice 参数

默认情况下模型自行决定是否调用工具("auto")。你可以通过 tool_choice 强制干预:

选项 含义
"auto" 默认,模型自行决定
"none" 禁止调用任何工具
"any" 必须调用至少一个工具
指定工具名 强制调用某个特定工具
typescript 复制代码
// 强制使用 calculator 工具
const forcedResponse = await modelWithTools.invoke(
  "What is 3 × 12?",
  { tool_choice: "calculator" }
);

📦 补充:结构化输出 withStructuredOutput

如果你想让模型直接返回 符合特定结构的 JSON 对象(而不是先去调工具再处理),可以用 withStructuredOutput。它底层也是利用 function calling 机制来强制模型按 schema 输出。

typescript 复制代码
const sentimentSchema = z.object({
  sentiment: z.enum(["正面", "负面", "中性"]),
  score: z.number().min(0).max(1),
});

const structuredModel = model.withStructuredOutput(sentimentSchema, {
  method: "functionCalling", // 对中转网关兼容性更好
});

const result = await structuredModel.invoke("分析这条评论:这个产品太棒了!");
// result 已经是解析好的 JS 对象,带 TS 类型
console.log(result.sentiment); // "正面"

一个实用经验 :如果你使用中转网关,withStructuredOutput 最好显式加上 method: 'functionCalling'。默认的 json_schema 模式对中转支持参差不齐,可能导致 JSON.parse 报错。

🚀 下一步

掌握了工具调用后,你就可以进入 Agent(智能体) 了。Agent 本质上就是一个循环:让模型不断"决策 → 调用工具 → 观察结果 → 再决策",直到完成任务。你可以用 createAgent 快速搭建一个 Agent,把定义好的工具传进去,它就会自动处理多轮工具调用的循环。

搭建一个 Agent

接下来我们用 createAgent 搭建你的第一个 Agent。它本质上就是把之前学的"模型决策 → 工具执行"循环自动化了,而且 LangChain v1 的 API 非常简洁。

认识 createAgent

createAgent 是 LangChain v1 中构建 Agent 的标准方式,取代了旧版的 createReactAgent。你只需要告诉它三件事:用什么模型、有哪些工具、扮演什么角色,它就会自动处理"模型决定调用工具 → 执行工具 → 把结果送回模型 → 生成最终回答"的完整循环。

第一步:最小可运行的 Agent

我们直接复用上一课定义的 calculatorTool,只需几行代码就能让 Agent 自主完成计算任务。

typescript 复制代码
import "dotenv/config";
import { createAgent, tool } from "langchain";
import { ChatOpenAI } from "@langchain/openai";
import { z } from "zod";

// 1. 定义工具(和上一课一样)
const calculatorTool = tool(
  async ({ operation, number1, number2 }) => {
    if (operation === "add") return `${number1 + number2}`;
    if (operation === "multiply") return `${number1 * number2}`;
    // ... 其他操作
  },
  {
    name: "calculator",
    description: "执行数学运算:加法、乘法等。",
    schema: z.object({
      operation: z.enum(["add", "multiply"]),
      number1: z.number(),
      number2: z.number(),
    }),
  }
);

// 2. 创建 Agent
const agent = createAgent({
  model: new ChatOpenAI({
    modelName: "qwen-max",
    temperature: 0.7,
    apiKey: process.env.DASHSCOPE_API_KEY,
    configuration: {
        baseURL: "https://dashscope.aliyuncs.com/compatible-mode/v1",
    },
  }),
  tools: [calculatorTool],
  systemPrompt: "你是一个数学助手,使用工具完成计算。",
});

// 3. 调用 Agent
const result = await agent.invoke({
  messages: [{ role: "user", content: "帮我算一下 12 乘以 8 等于多少?" }],
});

// 4. 获取最终回答
console.log(result.messages.at(-1)?.content); 
// 输出:"12 乘以 8 等于 96。"

关键区别 :在上一课中,你需要手动检查 tool_calls、执行工具、构造 ToolMessage、再次调用模型。createAgent 把这些全部封装了。你只需要调用一次 agent.invoke,它内部会自动完成所有轮次,直到模型给出最终文本回答。

第二步:让 Agent 处理多工具任务

Agent 真正的价值在于自主规划。给它多个工具,它能自己决定先用哪个、再用哪个。

假设你有一个"搜索工具"和一个"计算器工具",用户可以问:"帮我搜一下当前比特币价格,然后乘以 100 算算需要多少钱。"

typescript 复制代码
import { createAgent, tool } from "langchain";
import { ChatOpenAI } from "@langchain/openai";
import { z } from "zod";
import "dotenv/config";

// 搜索工具(模拟)
const searchTool = tool(
  async ({ query }) => {
    // 实际项目中这里会调用搜索 API
    if (query.includes("比特币")) {
      return "当前比特币价格:$67,432";
    }
    return `搜索 "${query}" 的结果...`;
  },
  {
    name: "web_search",
    description: "搜索互联网获取实时信息,如价格、新闻等。",
    schema: z.object({
      query: z.string().describe("搜索关键词"),
    }),
  }
);

// 计算器工具
const calculatorTool = tool(
  async ({ expression }) => {
    // 注意:生产环境不要直接用 eval
    return eval(expression).toString();
  },
  {
    name: "calculator",
    description: "计算数学表达式,支持 +、-、*、/。",
    schema: z.object({
      expression: z.string().describe("要计算的数学表达式,如 '67432 * 100'"),
    }),
  }
);

const agent = createAgent({
  model: new ChatOpenAI({
    modelName: "qwen-max",
    temperature: 0.7,
    apiKey: process.env.DASHSCOPE_API_KEY,
    configuration: {
        baseURL: "https://dashscope.aliyuncs.com/compatible-mode/v1",
    },
  }),
  tools: [searchTool, calculatorTool],
  systemPrompt: "你是一个助手,可以搜索信息并进行计算。",
});

// 一个需要多步操作的任务
const result = await agent.invoke({
  messages: [{
    role: "user",
    content: "搜索比特币当前价格,然后告诉我 100 个比特币值多少钱。",
  }],
});

console.log(result.messages.at(-1)?.content);
// Agent 会先调用 web_search,再调用 calculator,最后给出答案。

第三步:用中间件增加控制

createAgent 最强大的特性是中间件(Middleware)。中间件让你能在 Agent 循环的特定节点插入自定义逻辑,比如限制工具调用次数、拦截错误、动态切换模型等。

一个实用的例子是限制最大工具调用次数,防止 Agent 陷入无限循环:

typescript 复制代码
import { createAgent, toolCallLimitMiddleware } from "langchain";

const agent = createAgent({
  model: new ChatOpenAI({ model: "gpt-4o-mini" }),
  tools: [searchTool, calculatorTool],
  middleware: [
    toolCallLimitMiddleware({ runLimit: 5 }), // 最多调用 5 次工具
  ],
});

另一个常见场景是错误处理中间件。当工具执行失败时,返回友好的错误信息而不是让整个 Agent 崩溃:

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

const handleToolErrors = createMiddleware({
  name: "HandleToolErrors",
  wrapToolCall: async (request, handler) => {
    try {
      return await handler(request);
    } catch (error) {
      return new ToolMessage({
        content: `工具执行出错:${error.message}。请尝试其他方法。`,
        tool_call_id: request.toolCall.id,
      });
    }
  },
});

const agent = createAgent({
  model: new ChatOpenAI({ model: "gpt-4o-mini" }),
  tools: [searchTool, calculatorTool],
  middleware: [handleToolErrors],
});

调试建议

Agent 的"思考过程"对调试很重要。你可以用 streamEvents 观察 Agent 每一步在做什么:

typescript 复制代码
const stream = await agent.streamEvents(
  { messages: [{ role: "user", content: "搜索比特币价格并乘以 100" }] },
  { version: "v3" }
);

for await (const snapshot of stream.values) {
  const latest = snapshot.messages.at(-1);
  if (latest?.tool_calls?.length) {
    console.log(`Agent 正在调用: ${latest.tool_calls.map(tc => tc.name).join(", ")}`);
  }
}

关键要点

概念 作用
createAgent 一站式创建 Agent,自动处理工具调用循环
tools 传给 Agent 的工具列表,它自主决定何时用哪个
systemPrompt 定义 Agent 的角色和行为准则
middleware 在 Agent 循环中插入自定义逻辑(限制、错误处理、日志等)

Agent 的核心思想是委托 :你把工具和角色定义清楚,剩下的"什么时候用哪个工具、用几次"交给模型自己决定。这也是为什么工具的描述质量至关重要------模型完全依赖 description 来判断该不该调用某个工具。

检索增强生成(RAG)

检索增强生成(RAG)的核心思路很直接:让模型在回答前,先去你的文档库里"查资料",然后基于查到的内容来生成答案。

之前 Agent 让模型学会了"用工具",RAG 则是给模型装上了一个专属知识库,让它能回答私有数据的问题。

为什么需要 RAG

直接问模型关于你公司内部文档的问题,它要么答不上来,要么会"编造"一个看似合理的错误答案。因为模型训练时并没有见过你的私有文档。

RAG 解决了三个关键问题:

  • 私有知识:让模型能基于你的内部文档、手册、邮件来回答。
  • 实时性:知识库更新后,答案也能同步更新,不受模型训练时间限制。
  • 可溯源:可以要求模型回答时附上引用的来源,方便核实。

RAG 的四个核心步骤

一个基础的 RAG 流程分为四个阶段:

  1. 加载与分割:把长文档(PDF、Word)读进来,切成小块(Chunk)。
  2. 嵌入与存储:把每个小块通过 Embedding 模型转成向量,存进向量数据库。
  3. 检索:用户提问时,把问题也转成向量,去数据库里找出最相似的几个文档块。
  4. 生成:把"检索到的文档块"和"用户问题"一起交给模型,让它生成答案。

🛠️ 第一步:加载与分割文档

RAG 的第一步是把文档处理成适合检索的"小块"。

为什么必须分割? 因为模型上下文窗口有限,且整篇文档作为一个向量,语义太杂,检索精度低。

LangChain 提供了多种分割器,推荐从 RecursiveCharacterTextSplitter 开始,它会优先保持段落完整,再逐级向下切分。

your-document.txt

复制代码
员工健康保险覆盖范围:
1. 眼科检查:每年一次,全额报销。
2. 牙科:每年 2000 元额度。
3. 心理咨询:每年 10 次免费。

请假制度:
员工每年享有 15 天带薪年假,需提前 3 天申请。
typescript 复制代码
import { RecursiveCharacterTextSplitter } from "@langchain/textsplitters";
import { readFileSync } from "fs";

// 1. 读取文档
const text = readFileSync("./your-document.txt", "utf8");

// 2. 创建分割器
const splitter = new RecursiveCharacterTextSplitter({
  chunkSize: 500,      // 每个块约 500 字符
  chunkOverlap: 50,    // 相邻块重叠 50 字符,避免语义断裂
});

// 3. 执行分割
const chunks = await splitter.splitText(text);
console.log(`文档被切成了 ${chunks.length} 个块`);

chunkOverlap 很重要:它让相邻块之间有重叠内容,避免关键信息刚好被切在边界上而丢失。

📦 第二步:嵌入与向量存储

分割后,需要把文本块转成向量并存储,以便后续做语义检索。

typescript 复制代码
import { OpenAIEmbeddings } from "@langchain/openai";
import { MemoryVectorStore } from "langchain/vectorstores/memory";

// 1. 创建嵌入模型(把文本转为向量)
const embeddings = new OpenAIEmbeddings({
  modelName: "text-embedding-3-small",
});

// 2. 创建内存向量库(仅用于演示/测试)
const vectorStore = new MemoryVectorStore(embeddings);

// 3. 把文本块加入向量库(自动生成向量)
await vectorStore.addDocuments(
  chunks.map(chunk => ({ pageContent: chunk }))
);

存储选择MemoryVectorStore 适合本地测试。生产环境可以考虑 Azure AI SearchPineconeChroma。切换向量库时,LangChain 的接口基本一致,不用重写检索逻辑。

🔍 第三步:检索与生成(组合成链)

这是 RAG 的核心:把检索到的文档和用户问题一起交给模型。

typescript 复制代码
import { ChatOpenAI } from "@langchain/openai";
import { ChatPromptTemplate } from "@langchain/core/prompts";
import { StringOutputParser } from "@langchain/core/output_parsers";
import { RunnablePassthrough } from "@langchain/core/runnables";
import { RecursiveCharacterTextSplitter } from "@langchain/textsplitters";
import { readFileSync } from "fs";
import { OpenAIEmbeddings } from "@langchain/openai";
import { MemoryVectorStore } from "@langchain/classic/vectorstores/memory";
import "dotenv/config";

// 🛠️ 第一步:加载与分割文档

// 1. 读取文档
const text = readFileSync("./your-document.txt", "utf8");

// 2. 创建分割器
const splitter = new RecursiveCharacterTextSplitter({
  chunkSize: 500,      // 每个块约 500 字符
  chunkOverlap: 50,    // 相邻块重叠 50 字符,避免语义断裂
});

// 3. 执行分割
const chunks = await splitter.splitText(text);
console.log(`文档被切成了 ${chunks.length} 个块`);

// 📦 第二步:嵌入与向量存储

// 1. 创建嵌入模型(把文本转为向量)
const embeddings = new OpenAIEmbeddings({
  modelName: "text-embedding-v3",
  apiKey: process.env.DASHSCOPE_API_KEY,
  configuration: {
    baseURL: "https://dashscope.aliyuncs.com/compatible-mode/v1",
  },
});

// 2. 创建内存向量库(仅用于演示/测试)
const vectorStore = new MemoryVectorStore(embeddings);

// 3. 把文本块加入向量库(自动生成向量)
await vectorStore.addDocuments(
  chunks.map(chunk => ({ pageContent: chunk }))
);

// 🔍 第三步:检索与生成(组合成链)

const model = new ChatOpenAI({
    modelName: "qwen-max",
    temperature: 0.7,
    apiKey: process.env.DASHSCOPE_API_KEY,
    configuration: {
        baseURL: "https://dashscope.aliyuncs.com/compatible-mode/v1",
    },
})

// 1. 创建检索器
const retriever = vectorStore.asRetriever({ k: 3 }); // 返回最相似的 3 个块

// 2. 定义提示模板(必须把检索到的上下文传给模型)
const prompt = ChatPromptTemplate.fromMessages([
  ["system", "根据以下上下文回答问题。如果上下文中没有答案,就说不知道。\n\n上下文:\n{context}"],
  ["human", "{question}"],
]);

// 3. 用 LCEL 组合成 RAG 链
const ragChain = RunnablePassthrough.assign({
  // 这一步:用问题去检索,把结果格式化成字符串
  context: async ({ question }) => {
    const docs = await retriever.invoke(question);
    return docs.map(d => d.pageContent).join("\n\n");
  },
}).pipe(prompt).pipe(model).pipe(new StringOutputParser());

// 4. 调用
const answer = await ragChain.invoke({
  question: "员工健康保险覆盖眼科检查吗?",
});
console.log(answer);

关键点RunnablePassthrough.assign 让原始问题既能用于检索,又能保留下来传给提示模板。链的执行顺序是:问题 → 检索 → 得到上下文 → 提示模板(问题 + 上下文)→ 模型 → 答案。

⚠️ 常见问题与调试

RAG 的"坑"通常出在检索环节,而不是生成环节。如果答案不理想,按这个顺序排查:

现象 可能原因 排查方向
检索到的文档不相关 Embedding 模型不适合你的语言/领域 尝试换嵌入模型
关键信息不在检索结果里 分割策略不好,关键信息被切碎 调整 chunkSize 或换分割器
答案不准确 模型没用好上下文,或提示词太弱 在提示词里强调"只根据上下文回答"
检索结果太少/太多 k 值不合适 调整 asRetriever({ k: 3 }) 里的 k

一个实用的调试技巧:先单独测试检索 。用几个你知道答案的问题,看 retriever.invoke(question) 返回的文档块里有没有正确的信息。如果检索这一步就错了,后面无论模型多强都救不回来。

与 Agent 的关系

RAG 可以和之前学的 Agent 结合使用。你可以把"文档检索"封装成一个工具 ,让 Agent 自己决定什么时候去查资料。这就是 Agentic RAG 的思路:Agent 可以判断问题是否需要查文档,查完后再决定是否需要进一步处理。

不过,建议先用最基础的 RAG 链跑通全流程,等理解了数据流转,再升级到 Agentic RAG。

记忆

在 LangChain 中,Agent 的记忆处理已经演进为更清晰的两层架构:短期记忆 (Checkpointer)和长期记忆 (Store)。旧的 ConversationBufferMemory 等 API 已被视为遗留方案,新项目不建议再使用。

🧠 短期记忆:用 Checkpointer 维持会话连续性

短期记忆的核心是线程(Thread)隔离 。每个独立的对话(比如一个用户的一次会话)对应一个 thread_id,Checkpointer 会自动保存和恢复该线程内的完整对话历史(包括消息、工具调用等状态)。

为什么需要它? 如果没有它,你的 Agent 在两次 invoke 之间会"失忆"。有了 Checkpointer,你只需在调用时传入相同的 thread_id,Agent 就能自动"想起"之前的对话内容。

基础用法

js 复制代码
import { MemorySaver } from "@langchain/langgraph";
import { createAgent } from "langchain";
import { ChatOpenAI } from "@langchain/openai";
import { z } from "zod";
import "dotenv/config";
import { InMemoryStore } from "@langchain/langgraph";


// 1. 创建 Checkpointer(内存版,重启后数据丢失)
const checkpointer = new MemorySaver();

// 1. 创建 Store
const store = new InMemoryStore();

// 2. 创建 Agent 时传入
const agent = createAgent({
  model: new ChatOpenAI({
    modelName: "qwen-max",
    temperature: 0.7,
    apiKey: process.env.DASHSCOPE_API_KEY,
    configuration: {
        baseURL: "https://dashscope.aliyuncs.com/compatible-mode/v1",
    },
  }),
  checkpointer, // 关键:指定检查点保存器
  store, // 长期记忆存储
});

// 3. 调用时传入 thread_id,同一线程的对话会自动关联
const config = { configurable: { thread_id: "user-123-session-1" } };

await agent.invoke(
  { messages: [{ role: "user", content: "我叫 Bob" }] },
  config
);

// 第二次调用,Agent 能记住名字
const result = await agent.invoke(
  { messages: [{ role: "user", content: "我叫什么?" }] },
  config // 相同的 thread_id
);
// result 会包含 "你叫 Bob"

console.log(result.messages.at(-1)?.content);

生产环境注意MemorySaver 将数据存在内存中,进程重启后所有对话历史都会丢失 。生产环境务必换成持久化后端,如 PostgresSaverMongoDBSaverSqliteSaver

🌐 长期记忆:用 Store 跨会话记住用户偏好

长期记忆不局限于单个线程 ,它用于存储那些应该"永远记住"的信息,比如用户偏好、个人资料、累积的知识等。Store 将数据存储为 JSON 文档,通过命名空间(namespace)和键(key) 来组织。

基础用法

js 复制代码
import { createAgent, tool, } from "langchain";
import { InMemoryStore } from "@langchain/langgraph";
import * as z from "zod";
import { ChatOpenAI } from "@langchain/openai";
import "dotenv/config";

// 1. 初始化 store 并预存一条记忆
const store = new InMemoryStore();
const namespaceForMemory = ["user_123", "memories"];

await store.put(namespaceForMemory, "food_pref", {
  food_preference: "I like pizza",
});

// 2. 定义一个读取记忆的工具
const contextSchema = z.object({ userId: z.string() });

const getFoodPreference = tool(
  async (_, runtime) => {
    const userId = runtime.context.userId;
    const namespace = [userId, "memories"];

    // 使用 search 获取该用户的所有记忆
    const memories = await runtime.store.search(namespace);

    if (!memories || memories.length === 0) {
      return "未找到用户偏好";
    }

    // 3. 关键一步:从 memories 中提取 value 并组合成可读的文本
    // 这里取最新的一条,提取它的 value 对象中的 food_preference 字段
    const latest = memories[memories.length - 1];
    const pref = latest.value.food_preference;

    return `用户喜欢的食物是:${pref}`;
  },
  {
    name: "get_food_preference",
    description: "查找用户喜欢的食物偏好",
    schema: z.object({}),
  }
);

const agent = createAgent({
  model: new ChatOpenAI({
    modelName: "qwen-max",
    temperature: 0.7,
    apiKey: process.env.DASHSCOPE_API_KEY,
    configuration: {
        baseURL: "https://dashscope.aliyuncs.com/compatible-mode/v1",
    },
  }),
  tools: [getFoodPreference],
  contextSchema,
  store, // 传入 store,工具内才能通过 runtime.store 访问
});

const result = await agent.invoke(
  { messages: [{ role: "user", content: "我喜欢的食物?" }] },
  { context: { userId: "user_123" } },
);

console.log(result.messages.at(-1)?.content);
相关推荐
小白勇闯网安圈5 小时前
第5章 流式输出与人工审核
python·langchain
小白勇闯网安圈5 小时前
第2章 状态 State 详解
python·langchain
半个落月15 小时前
LangChain.js Agent Memory 实战(下):用 Milvus 构建可检索的长期记忆
javascript·langchain
半个落月15 小时前
LangChain.js Agent Memory 实战(上):从内存对话、文件持久化到截断与摘要
javascript·langchain
梦在远山后19 小时前
Electron 与 FastAPI 如何完成流式 Agent 对话
python·langchain·agent
猿人谷1 天前
Jev:当 AI 不再生成 Token,而是直接做决策
后端·langchain·aigc
逆风飞翔的小叔1 天前
【AI智能体】Langchain 主流大模型调用与对话API使用详解
langchain·langchain 对话api·langchain 对话·langchain 对话使用·langchain 对话详解·langchain api使用
王国强20091 天前
如何通过 Deep Agents、LangChain 与 LangGraph 构建生产级智能体:从 Deep Agents Code 源码看 Agent 工程实
langchain
warrior1 天前
垂直领域问答助手开发
langchain