LangChain.js 从入门到进阶:用 JavaScript/TypeScript 构建可落地的 RAG 与智能体系统

我们不再迷信“魔法提示”。高质量的 AI 应用 = 明确任务拆解 + 可迭代的链式流程 + 可控的工具调用 + 持续记忆 + 适度微调/偏好优化。本文以 背景–原理–实践–总结 主线,系统性讲透 LangChain.js 在前端/Node/边缘环境中的工程化玩法。


目录

  1. 背景:为什么是 LangChain.js(JS/TS 生态的系统化 AI 工程)

  2. 原理:LCEL、Runnable、Retriever、Memory、Tools/Agents、Callbacks/Streaming

    • 2.1 概念解释

    • 2.2 示例与对比

  3. 实战一:最小可用 RAG(Node & 边缘兼容)

  4. 实战二:工具增强型智能体(ReAct + 搜索/计算)

  5. 实战三:LangGraph 有状态智能体(可视化流程与可恢复对话)

  6. 实战四:SSE 流式接口服务化(Express/Edge 响应,真正在生产里跑)

  7. 性能与工程要点:向量化选型、成本观、可观测性与评测闭环

  8. 总结与互动:从“写提示”到“造系统”的进阶路线


1. 背景:为什么是 LangChain.js?

解释:LangChain.js 是 JavaScript/TypeScript 生态下的“AI 应用搭建框架”。它强调“链式思维 + 组件化拼装”,让我们把大模型的推理变成可编排的流程,并在浏览器、Node、Cloudflare Workers、Vercel Edge 等环境平滑运行。

示例:同样是“问答机器人”,纯提示(prompt)方案容易不稳定、不可控;而用 LangChain.js,我们可以把问题分解为“检索 → 归并 → 生成 → 校对”,每步是独立的 Runnable,易于调试与复用。

对比

  • 直接 SDK(如直接调 OpenAI 接口):上手快,但流程不可见、可观察性弱。

  • LangChain.js:工程化能力强(可插拔 Retriever/Tools、内置回调、链式可视化),更适合团队协作与持续演进

外部参考:


2. 原理:LCEL、Runnable、Retriever、Memory、Tools/Agents、Callbacks/Streaming

2.1 核心概念解释

  • LCEL (LangChain Expression Language):把复杂流程表达为“可组合的函数链”(Runnable),像积木一样拼接,类型安全、可测试

  • Runnable:可运行组件抽象,统一 .invoke/.stream/.batch 接口;提示模板、LLM、解析器、合并器统统是 Runnable。

  • Retriever:统一检索接口,把向量库/关键词搜索封装为 .getRelevantDocuments(query)

  • Memory:缓冲或摘要历史对话,让模型“记住”上下文(工程层的记忆而非模型权重层)。

  • Tools/Agents:把“可调用动作”(搜索/计算/数据库)注册为工具,智能体通过 ReAct 回路自主选择工具。

  • Callbacks/Streaming:全链路可观测与流式反馈(token 级),产品体验更“丝滑”。

2.2 示例与对比

  • LCEL vs 传统回调地狱:LCEL 让流程语义清晰、类型友好、可测试;传统回调/Promise 链一多就乱。

  • Retriever vs 直接查库:统一抽象,方便切换向量库(本地/云端),避免将来“迁移地狱”。

  • Agents vs 固定 Chain:固定链条确定性强,适合标准化任务;智能体灵活强,但要配合步数限制/工具白名单/日志治理。


3. 实战一:最小可用 RAG(Node & 边缘兼容)

目标:构建一个“查询 → 相似文档检索 → 链接归纳 → 流式生成”的最小 RAG,演示 LCEL 的组合能力。使用内存向量库以便本地跑通,后续可替换 Pinecone/Weaviate。

// file: rag-minimal.ts
// 依赖:npm i langchain @langchain/openai
// 说明:此示例以 OpenAI 为例,可换成任何兼容的 LLM 提供商。
import { ChatOpenAI, OpenAIEmbeddings } from "@langchain/openai";
import { MemoryVectorStore } from "langchain/vectorstores/memory";
import { RecursiveCharacterTextSplitter } from "langchain/text_splitter";
import { StringOutputParser } from "@langchain/core/output_parsers";
import { RunnablePassthrough, RunnableMap } from "@langchain/core/runnables";
import { PromptTemplate } from "@langchain/core/prompts";

// 1) 预备:构建向量库(上线改为 Pinecone/Weaviate)
async function buildVectorStore(docs: string[]) {
  const splitter = new RecursiveCharacterTextSplitter({ chunkSize: 600, chunkOverlap: 80 });
  const chunks = (await Promise.all(docs.map(d => splitter.splitText(d)))).flat();
  const store = await MemoryVectorStore.fromTexts(
    chunks,
    chunks.map((_, i) => ({ id: i })),
    new OpenAIEmbeddings({ model: "text-embedding-3-small" })
  );
  return store;
}

// 2) RAG 提示
const RAG_TEMPLATE = `你是严谨的技术助理。请基于“检索到的上下文”回答“用户问题”。
// 重要:若上下文不足以回答,请明确说明“未检索到足够证据”,不要编造。
// 上下文:{context}
// 用户问题:{question}
// 结构化输出:先给要点列表,再给归纳段落,最后列出参考片段位置索引。`;

// 3) 组装 LCEL 链:query -> retrieve -> prompt -> llm -> parse
export async function createRAG() {
  const llm = new ChatOpenAI({ model: "gpt-4o-mini", temperature: 0, streaming: true });

  const vector = await buildVectorStore([
    "LangChain.js 是 JS/TS 生态的 AI 应用框架,强调可组合的链式流程。",
    "LCEL 是 LangChain 的表达式语言,用于以函数式方式拼接 Runnable 节点。",
    "Retriever 抽象统一了向量检索接口,便于替换 Pinecone/Weaviate 等后台。",
    "Callbacks/Streaming 让链条具备可观测与流式输出能力,产品体验更好。"
  ]);

  const retriever = vector.asRetriever(4);

  const prompt = PromptTemplate.fromTemplate(RAG_TEMPLATE);
  const parser = new StringOutputParser();

  // 将“检索 + 生成”封装为图式可读的 LCEL
  const chain = RunnableMap.from({
    question: new RunnablePassthrough(), // 透传用户问题
    context: new RunnablePassthrough().pipe(async (q: string) => {
      const docs = await retriever.getRelevantDocuments(q);
      return docs.map((d, i) => `[${i}] ${d.pageContent}`).join("\n");
    })
  })
    .pipe(prompt)
    .pipe(llm)
    .pipe(parser);

  return chain;
}

// 4) 运行 & 流式打印(本地调试)
if (require.main === module) {
  (async () => {
    const chain = await createRAG();
    const q = "用一句话解释 LCEL 与 Retriever 的关系,并给出两个工程化优势。";
    const stream = await chain.stream(q);
    for await (const chunk of stream) process.stdout.write(chunk);
  })();
}

要点

  • 最小改动即可替换向量库

  • Prompt 结构化输出(要点→归纳→参考索引),便于前端渲染;

  • .stream() 提升交互体验。


4. 实战二:工具增强型智能体(ReAct + 搜索/计算)

目标:给模型“手”,让它会 (Search)、会 (Math),并带上护栏:步数限制、工具白名单、日志。

// file: agent-tools.ts
// 依赖:npm i langchain @langchain/openai mathjs node-fetch
import { ChatOpenAI } from "@langchain/openai";
import { Tool } from "@langchain/core/tools";
import { DynamicStructuredTool } from "@langchain/core/tools";
import { z } from "zod";
import fetch from "node-fetch";
import { createReactAgent } from "langchain/agents"; // 若版本不同,可改为 initializeAgentExecutor

// 1) 工具:Web 搜索(示意:调用一个公开搜索 API)
const webSearchTool: Tool = new DynamicStructuredTool({
  name: "web_search",
  description: "搜索网络以获取最新信息。输入关键词,返回前3条摘要。",
  schema: z.object({ query: z.string() }),
  func: async ({ query }) => {
    // 这里仅做演示,真实生产替换为你的搜索服务/Tavily/SerpAPI
    const url = `https://duckduckgo.com/?q=${encodeURIComponent(query)}&format=json`;
    try {
      const res = await fetch(url);
      const text = await res.text(); // 演示:不依赖具体返回结构
      return `RawSearchResult(length=${text.length})`;
    } catch (e) {
      return `Search failed: ${(e as Error).message}`;
    }
  },
});

// 2) 工具:数学计算(mathjs)
import { evaluate } from "mathjs";
const mathTool: Tool = new DynamicStructuredTool({
  name: "calculator",
  description: "高精度数学表达式计算器,支持()与幂次。",
  schema: z.object({ expr: z.string() }),
  func: async ({ expr }) => {
    try {
      const val = evaluate(expr);
      return String(val);
    } catch {
      return "NaN";
    }
  },
});

// 3) 智能体:ReAct 循环 + 步数限制 + 日志
async function main() {
  const llm = new ChatOpenAI({ model: "gpt-4o-mini", temperature: 0 });
  const tools = [webSearchTool, mathTool];

  // LangChain 版本差异较大,若 createReactAgent 不可用,可改为 initializeAgentExecutor。
  const agent = await createReactAgent({
    llm,
    tools,
    maxSteps: 5, // 护栏:最多5次工具调用
    prompt: `你是严谨的研究助理。用 web_search 获取事实,用 calculator 做计算。
- 除非必要,不要调用 web_search。
- 给出“思考-行动-观察”的可读过程,再给最终答案。
- 若信息不足请直说,不要编造。`,
  });

  const question = "2020~2024 年全球 LLM 领域投融资的增长趋势,给我一个估算同比(举例数据即可)并计算 2024/2021 比值。";
  const result = await agent.invoke({ input: question });
  console.log(result.output);
}

if (require.main === module) main();

要点

  • 工具白名单 + maxSteps 防止“失控”;

  • 先思考再行动 的 ReAct 回路,可观察性好

  • 搜索工具生产替换为自家 Search/Tavily/SerpAPI,并在服务端使用(避免泄密)。


5. 实战三:LangGraph 有状态智能体(计划→行动→反思)

目标:将对话/任务变为有向图流程:可调度、可中断、可恢复。适合“复杂连环任务”:规划 → 多轮执行 → 反思纠错。

// file: agent-graph.ts
// 依赖(示例):npm i langchain @langchain/openai langgraph
// 注:不同版本 API 可能差异,可基于 js.langchain.com 的 LangGraph 章节适当调整。
import { ChatOpenAI } from "@langchain/openai";
import { StateGraph, END, START } from "langgraph";
import { RunnableConfig } from "@langchain/core/runnables";

type GraphState = {
  goal: string;
  plan?: string[];
  stepIndex?: number;
  logs?: string[];
  answer?: string;
};

// 1) 节点:生成计划
async function planNode(state: GraphState) {
  const llm = new ChatOpenAI({ model: "gpt-4o-mini", temperature: 0 });
  const prompt = `
目标:${state.goal}
请拆解为3~6个可执行小步骤(短句),输出JSON数组,不要多余说明。
`.trim();
  const res = await llm.invoke(prompt);
  let plan: string[] = [];
  try {
    plan = JSON.parse(res.content as string);
  } catch {
    plan = ["检索资料", "分析要点", "撰写答案"];
  }
  return { ...state, plan, stepIndex: 0, logs: [`Plan: ${plan.join(" | ")}`] };
}

// 2) 节点:执行当前步骤(可挂接工具/检索)
async function actNode(state: GraphState) {
  const llm = new ChatOpenAI({ model: "gpt-4o-mini", temperature: 0 });
  const step = state.plan![state.stepIndex!];
  const input = `当前子任务:“${step}”;结合既有上下文(可省略),给一个执行结果摘要(100字内)。`;
  const res = await llm.invoke(input);
  const newLogs = (state.logs ?? []).concat(`Act[${state.stepIndex}]: ${(res.content as string).slice(0,80)}...`);
  return { ...state, logs: newLogs };
}

// 3) 节点:反思 / 继续 or 收尾
async function reflectNode(state: GraphState) {
  const next = (state.stepIndex ?? 0) + 1;
  if (next < (state.plan?.length ?? 0)) {
    return { ...state, stepIndex: next };
  }
  // 收尾:综合日志生成最终答案
  const llm = new ChatOpenAI({ model: "gpt-4o-mini", temperature: 0 });
  const summary = (state.logs ?? []).join("\n");
  const res = await llm.invoke(`基于以下执行日志,输出最终答案(条理清晰、400字内):\n${summary}`);
  return { ...state, answer: res.content as string };
}

// 4) 构图:START -> plan -> act -> reflect -> (loop) -> END
const graph = new StateGraph<GraphState>()
  .addNode("plan", planNode)
  .addNode("act", actNode)
  .addNode("reflect", reflectNode)
  .addEdge(START, "plan")
  .addConditionalEdges("plan", () => "act", ["act"])
  .addConditionalEdges("act", () => "reflect", ["reflect"])
  .addConditionalEdges("reflect", (s) => ((s.stepIndex ?? 0) < (s.plan?.length ?? 0) ? "act" : END), ["act", END])
  .compile();

// 5) 运行
async function main() {
  const init: GraphState = { goal: "给出 LangChain.js 工程化优势的要点列表,并形成简短建议" };
  const cfg: RunnableConfig = { configurable: { thread_id: "demo-graph-001" } };
  const final = await graph.invoke(init, cfg);
  console.log("== 最终答案 ==\n", final.answer);
}
if (require.main === module) main();

要点

  • 将“推理”显式化为 图结构可暂停/恢复

  • thread_id 便于对话级状态管理;

  • 每个节点都可插入工具(检索/调用 API),提升复杂任务成功率。


6. 实战四:SSE 流式接口服务化(Express/Edge)

目标:把链/智能体“服务化”,提供标准 HTTP 接口与实时 Token 流,前端零复杂度集成。

// file: server-sse.ts
// 依赖:npm i express cors langchain @langchain/openai
import express from "express";
import cors from "cors";
import { ChatOpenAI } from "@langchain/openai";
import { PromptTemplate } from "@langchain/core/prompts";

const app = express();
app.use(cors());
app.use(express.json());

app.post("/chat/stream", async (req, res) => {
  const { question } = req.body as { question: string };
  if (!question) return res.status(400).json({ error: "question required" });

  // SSE Header
  res.setHeader("Content-Type", "text/event-stream; charset=utf-8");
  res.setHeader("Cache-Control", "no-cache, no-transform");
  res.setHeader("Connection", "keep-alive");

  const llm = new ChatOpenAI({ model: "gpt-4o-mini", temperature: 0, streaming: true });
  const tmpl = PromptTemplate.fromTemplate(
    `你是专业技术助理,请对问题给出分点回答并给出参考行动清单。\n问题:{q}`
  );
  const prompt = await tmpl.format({ q: question });

  try {
    const stream = await llm.stream(prompt);
    for await (const chunk of stream) {
      res.write(`data: ${JSON.stringify({ token: chunk })}\n\n`);
    }
    res.write(`data: ${JSON.stringify({ done: true })}\n\n`);
    res.end();
  } catch (e) {
    res.write(`data: ${JSON.stringify({ error: (e as Error).message })}\n\n`);
    res.end();
  }
});

app.listen(8787, () => console.log("SSE server on http://127.0.0.1:8787"));

要点

  • 前端直接 EventSource 订阅 /chat/stream

  • 服务端可无痛切换到 Edge/Workers(把 express 改成原生 fetch Handler);

  • 统一 LangChain Runnable 接口,链/智能体即服务


7. 性能与工程要点(深度细节,建议收藏)

1)向量化与检索策略

  • 小数据量:MemoryVectorStore 快速验证;

  • 生产:Pinecone/Weaviate/pgvector(Postgres)——看预算/维护成本/数据合规;

  • 分片 + 过滤 + 多路检索(Hybrid) 结合,Recall/Precision 平衡更稳。

2)成本与稳定性

  • Prompt 带结构化输出(JSON/Markdown小节),避免反复追问

  • 缓存:对“检索结果/中间节点”做 KV 缓存,减少重复费用;

  • 温度设 0~0.3,稳定优先;关键链路要加重试/回退模型

3)可观测性与回放

  • Callbacks:记录 每个 Runnable 的输入/输出/Token 花费

  • 错误事件:专门的“墓碑表”记录,方便复盘;

  • 标注:收集“好答案/坏答案”的人为反馈,驱动后续偏好优化

4)评测闭环(必做)

  • 构造一批“黄金问题集”(包含负例与陷阱题);

  • 自动评估:答案一致性/覆盖率/引用率/长度/禁编造检查;

  • 线上灰度:新链/新 Prompt 先在小流量跑 A/B。

5)对比选择

  • LangChain.js vs 直接 SDK:复杂度上来后必然转工程化;

  • 固定链 vs 智能体:标准化流程优先固定链,智能体仅在实在需要动态规划时介入;

  • RAG vs 微调:事实类问题 → RAG;风格/格式/私域任务 → 轻量微调或偏好优化。


8. 总结与互动:从“写提示”到“造系统”

升华
写一个“能跑”的 Demo 从来不难,难的是把它“运营成系统”。LangChain.js 的价值在于把 LLM 的概率行为通过链/图/工具/记忆等工程手段制度化:让推理可观测、让流程可治理、让产品可进化。
当你掌握了:

  • 用 LCEL 把任务拆成可测试的 Runnable

  • 用 Retriever 统一检索、可拔插地切向量库;

  • 用 Agents 给模型“手”和“脚”,再配好护栏;

  • 用 Callbacks/Streaming 打通体验与观测;
    你就完成了从“会写提示”到“会造系统”的进阶。

欢迎在评论区分享你的 LangChain.js 实战坑点与解决思路;如果你想让我把本文 4 个示例整合成一个可部署的模板仓库(Node/Edge 双模 + Vite 前端示例 + 最小日志与评测脚本),在评论里扣个 RAG,人多我就出!


附:外部可靠资料(≥3)

友情提示:以上链接均为官方/权威站点,长期可用;如访问缓慢,建议先收藏或离线保存页面 PDF 备查。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值