我们不再迷信“魔法提示”。高质量的 AI 应用 = 明确任务拆解 + 可迭代的链式流程 + 可控的工具调用 + 持续记忆 + 适度微调/偏好优化。本文以 背景–原理–实践–总结 主线,系统性讲透 LangChain.js 在前端/Node/边缘环境中的工程化玩法。
目录
-
背景:为什么是 LangChain.js(JS/TS 生态的系统化 AI 工程)
-
原理:LCEL、Runnable、Retriever、Memory、Tools/Agents、Callbacks/Streaming
-
2.1 概念解释
-
2.2 示例与对比
-
-
实战一:最小可用 RAG(Node & 边缘兼容)
-
实战二:工具增强型智能体(ReAct + 搜索/计算)
-
实战三:LangGraph 有状态智能体(可视化流程与可恢复对话)
-
实战四:SSE 流式接口服务化(Express/Edge 响应,真正在生产里跑)
-
性能与工程要点:向量化选型、成本观、可观测性与评测闭环
-
总结与互动:从“写提示”到“造系统”的进阶路线
1. 背景:为什么是 LangChain.js?
解释:LangChain.js 是 JavaScript/TypeScript 生态下的“AI 应用搭建框架”。它强调“链式思维 + 组件化拼装”,让我们把大模型的推理变成可编排的流程,并在浏览器、Node、Cloudflare Workers、Vercel Edge 等环境平滑运行。
示例:同样是“问答机器人”,纯提示(prompt)方案容易不稳定、不可控;而用 LangChain.js,我们可以把问题分解为“检索 → 归并 → 生成 → 校对”,每步是独立的 Runnable,易于调试与复用。
对比:
-
直接 SDK(如直接调 OpenAI 接口):上手快,但流程不可见、可观察性弱。
-
LangChain.js:工程化能力强(可插拔 Retriever/Tools、内置回调、链式可视化),更适合团队协作与持续演进。
外部参考:
-
LangChain.js 官方文档:Introduction | 🦜️🔗 Langchain
-
OpenAI 平台文档:https://platform.openai.com/docs
-
Pinecone 向量库文档:Pinecone Database - Pinecone Docs
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改成原生fetchHandler); -
统一 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)
-
LangChain.js 官方文档:Introduction | 🦜️🔗 Langchain
-
OpenAI 平台文档:https://platform.openai.com/docs
-
Pinecone 向量数据库文档:Pinecone Database - Pinecone Docs
-
Weaviate 开发者中心:Weaviate Database | Weaviate Documentation
-
LangGraph(概念与实现,按使用环境选择对应章节):LangGraph
友情提示:以上链接均为官方/权威站点,长期可用;如访问缓慢,建议先收藏或离线保存页面 PDF 备查。

548

被折叠的 条评论
为什么被折叠?



