从零上手 LangChain:用 JavaScript 打造强大 AI 应用的全流程指南
2022 年 10 月,一个名为 LangChain 的开源框架悄然诞生,比 ChatGPT 正式发布早了一个月。它不是一个聊天机器人,而是一个AI 应用开发框架 ,专门帮助开发者把大语言模型(LLM)从"黑盒"变成可工程化、可组合的生产力工具。到 2025 年底,LangChain 已演进到 1.x 稳定版,成为构建 Agent、RAG、复杂工作流的事实标准,GitHub Star 数遥遥领先。
LangChain 的核心理念可以用一个词概括:Chain(链)。就像乐高积木一样,把 Prompt、Model、内存、工具、解析器等模块"链"起来,形成一个自动执行的工作流。这让 AI 应用从简单的"一问一答"跃升到多步骤推理、工具调用、长期记忆的智能代理。
本文基于实际代码示例(DeepSeek 模型 + JavaScript),带你从最基础的 Prompt 模板开始,一步步构建复杂链条。

一、环境配置
5 分钟快速上手环境配置
LangChain 的 JavaScript 版(也叫 LangChain.js)完全基于现代 ESM(ES Modules),所以我们要用 Node.js 18+。
-
创建项目文件夹并初始化
mkdir langchain-demo && cd langchain-demo npm init -y
-
修改 package.json 支持 ESM(关键!)
把这一行改成: "type": "module"
-
安装核心依赖
npm i @langchain/core @langchain/deepseek dotenv
- 创建 .env 文件存放 API Key(强烈推荐)
去 platform.deepseek.com/api-keys
申请 DEEPSEEK_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
易错提醒:
- 忘记加 "type": "module" 会报错 Cannot use import statement outside a module。
- 不要直接把 API Key 写死在代码里,用 dotenv 读取更安全。
(dotenv 的作用就是把 .env 文件里的环境变量读取并加载到 Node.js 的全局对象 process.env 上)
- DeepSeek 的包是 @langchain/deepseek,不是 langchain-deepseek。
二、为什么需要 LangChain?LLM 的"黑盒"如何被打开
大语言模型(如 DeepSeek、GPT、Claude)强大但原始:你给一个字符串,它吐出一个字符串。想做点复杂的事?比如:
- 动态插入变量(Prompt 工程)
- 多次调用模型完成多步任务
- 记住对话历史
- 调用外部工具(搜索、计算器、数据库)
直接用 API 就能实现,但代码会迅速变成"意大利面":嵌套回调、重复的错误处理、模型切换麻烦......
LangChain 就是那个"工程化工具箱":
- 统一接口:不管用 DeepSeek、OpenAI 还是 Anthropic,一个抽象层搞定切换。
- 模块化组件:PromptTemplate、ChatModel、OutputParser、Memory 等。
- 可组合性 :用
pipe()或RunnableSequence把组件串成链,数据自动流动。 - 生产级特性:流式输出、异步、调试(LangSmith)、监控。
用个比喻:原生 LLM API 像一辆法拉利引擎------动力强劲但裸露;LangChain 像整车工厂,把引擎装进车身,加方向盘、刹车、导航,让你安心上路。
三、LangChain 的适配器(Provider)
在 LangChain 中,安装了 dotenv 后,你可以不用显式传 apiKey 和 baseURL ,这正是 LangChain 的适配器(Provider)设计哲学的核心魅力之一:统一接口 + 智能默认配置。
我们可以直接这样创建大模型实例:
JavaScript
javascript
import { ChatDeepSeek } from '@langchain/deepseek';
const model = new ChatDeepSeek({
model: 'deepseek-reasoner',
temperature: 0.7
// 不用写 apiKey 和 baseURL!
});
而之前的老方式(不靠 dotenv)必须这样写:
JavaScript
arduino
const model = new ChatDeepSeek({
apiKey: 'sk-xxx',
baseURL: 'https://api.deepseek.com', // 某些老版本需要手动指定
model: 'deepseek-reasoner'
});
背后的核心机制是什么?
这是 LangChain(尤其是 JS 版)在 ChatModel 适配器内部 实现的智能配置优先级链,大致逻辑如下(优先级从高到低):
-
构造函数显式传入的参数(最高优先级) 你手动传了 apiKey 或 baseURL,就无条件用你的。
-
环境变量(process.env)中的标准名称 (这就是 dotenv 发挥作用的地方) LangChain 的每个 Provider(DeepSeek、OpenAI、Anthropic 等)都定义了约定的环境变量名:
- DeepSeek:DEEPSEEK_API_KEY
- OpenAI:OPENAI_API_KEY(+ OPENAI_BASE_URL 可选)
- Anthropic:ANTHROPIC_API_KEY
- Groq:GROQ_API_KEY
- ...
当你没有显式传 apiKey 时,ChatDeepSeek 构造函数内部会自动去 process.env.DEEPSEEK_API_KEY 里取!
-
baseURL 的智能默认 每个 Provider 包内部都硬编码了官方 API 地址:
- @langchain/deepseek 默认 api.deepseek.com
- @langchain/openai 默认 api.openai.com/v1 所以你根本不用手动指定,除非你要用代理或自定义 endpoint。
-
如果以上都没找到 → 抛错 防止你默默跑出"密钥为空"的诡异行为。
统一接口的魅力
LangChain 的统一接口,就是不管你用哪个大模型提供商(DeepSeek、OpenAI、Anthropic、Groq、Claude、Gemini 等),创建和调用模型的方式、链的构建方式、输入输出格式都完全一致,你只需要换个 import 包和环境变量,就能无缝切换模型。
具体体现在哪些统一接口?
-
模型创建与调用接口统一(最常用)
所有聊天模型都继承自同一个基类 ChatModel,提供相同的构造函数和方法:
JavaScript
javascript// DeepSeek import { ChatDeepSeek } from '@langchain/deepseek'; const model = new ChatDeepSeek({ model: 'deepseek-reasoner' }); // OpenAI(切换只需改这一行 + .env) import { ChatOpenAI } from '@langchain/openai'; const model = new ChatOpenAI({ model: 'gpt-4o' }); // Anthropic import { ChatAnthropic } from '@langchain/anthropic'; const model = new ChatAnthropic({ model: 'claude-3-5-sonnet' }); // 调用方式完全一样! const res = await model.invoke(messages); // 所有模型都支持 .invoke() console.log(res.content);输出也统一为 AIMessage 对象,内容在 .content。
-
Chain 构建接口统一(pipe 和 Runnable)
不管底层模型是谁,链的写法一模一样:
JavaScript
arduinoconst chain = prompt.pipe(model).pipe(parser); // 所有模型都支持 pipe await chain.invoke({ topic: '闭包' }); -
输入输出格式统一
- 输入:通常是 { key: value } 对象或消息数组
- 输出:标准化消息对象(HumanMessage、AIMessage、SystemMessage)
- 解析器:StringOutputParser、JsonOutputParser 等通用
-
环境变量命名统一
每个提供商都有约定的环境变量名:
- DEEPSEEK_API_KEY
- OPENAI_API_KEY
- ANTHROPIC_API_KEY
- GROQ_API_KEY
配合 dotenv,一换就灵。
为什么这个统一接口这么重要?
| 场景 | 没统一接口(原始方式) | 有统一接口(LangChain) |
|---|---|---|
| 切换模型 | 大改代码、改 URL、改参数格式 | 改一行 import + .env 的 key |
| 团队协作 | 每个人用不同模型,代码不兼容 | 统一标准,代码可复用 |
| 成本优化 | 想用更便宜的模型?重写适配代码 | 直接换 Gpt 或 DeepSeek,零成本切换 |
| 生产稳定性 | 某个提供商挂了,应用瘫痪 | 快速切换备用模型,故障切换秒级完成 |
实际案例:一键从 DeepSeek 切换到 GPT-4o
Bash
ini
# .env 原版
DEEPSEEK_API_KEY=sk-xxx
# 改成
OPENAI_API_KEY=sk-xxx
JavaScript
javascript
// 代码只改一行
// import { ChatDeepSeek } from '@langchain/deepseek';
import { ChatOpenAI } from '@langchain/openai';
const model = new ChatOpenAI({ // 构造函数参数基本一致
model: 'gpt-4o',
temperature: 0.7
});
// 下面所有链、invoke 代码完全不动!
const chain = prompt.pipe(model);
运行结果:行为几乎一致,但可能更聪明或更贵😂
总结
LangChain 的"统一接口"本质就是:
一套标准化的抽象层(基类 + 协议 + 约定),让所有大模型提供商看起来"长得一样",用起来"感觉一样"。
这才是 LangChain 能爆火的真正原因------它不只是工具,更是AI 应用的工程化基础设施。
四、入门:PromptTemplate + Model 的第一次"链"
我们从最简单的例子开始。
javascript
import { ChatDeepSeek } from '@langchain/deepseek';
import { PromptTemplate } from '@langchain/core/prompts';
const prompt = PromptTemplate.fromTemplate(`
你是一个{role},
请用不超过{limit}字回答以下问题:
{question}
`);
const model = new ChatDeepSeek({
model: 'deepseek-reasoner', // 2025 年 DeepSeek 的旗舰推理模型,擅长长链思考
temperature: 0
});
const promptStr = await prompt.format({
role: '前端面试官',
limit: '50',
question: '什么是闭包'
});
const res = await model.invoke(promptStr);
console.log(res.content);
底层逻辑 :
PromptTemplate 就是一个带占位符的字符串工厂。format() 方法把变量注入,生成完整提示词。然后直接扔给模型调用。
扩展知识点:
- 为什么用模板? Prompt 工程是 AI 应用的核心,模板让你复用、动态调整,避免硬编码。
- DeepSeek-reasoner 模型亮点:2025 年版本内置 Chain of Thought(CoT),在回答前会自动生成推理过程(reasoning_content),准确率大幅提升,尤其适合复杂问题。
易错提醒:
- 别忘了
await prompt.format()------ 它是异步的(虽然简单模板看起来同步)。 - temperature=0 时输出最确定,适合知识问答;0.7~1.0 更有创意。
五、真正意义上的 Chain:pipe() 与 RunnableSequence
单纯的 Prompt + Model 还不是链。真正的链是用 pipe() 连接。
javascript
const prompt = PromptTemplate.fromTemplate(`
你是一个前端专家,用一句话解释:{topic}
`);
const chain = prompt.pipe(model); // 这里诞生了链!
const res = await chain.invoke({ topic: '闭包' });
console.log(res.content);
底层逻辑揭秘 :
pipe() 返回一个 RunnableSequence 对象(可运行序列)。内部结构是:
- first:PromptTemplate
- middle:[](空)
- last:ChatModel
输入 {topic: '闭包'} → Prompt 格式化 → 完整提示词 → Model 调用 → AIMessage 输出。
扩展:RunnableSequence 的 first/middle/last
- first:链的起点
- middle:中间组件列表(可多个)
- last:终点
- 数据像水流一样,从 first 流到 last,自动传递。
当你用 prompt.pipe(model) 或 RunnableSequence.from([...]) 创建链时,LangChain 内部会自动把组件组织成以下结构:
- first :永远是链的第一个组件(通常是 PromptTemplate 或 RunnablePassthrough 等)
- middle :从第二个到倒数第二个的所有组件,形成一个数组(可以是空 [],也可以有多个)
- last :永远是链的最后一个组件(通常是 ChatModel、OutputParser 或自定义 RunnableLambda)
数据流向:输入 → first → middle[0] → middle[1] → ... → middle[n] → last → 输出 每一步的输出自动成为下一步的输入。
举例说明(假设用 pipe 连了 5 个组件):
JavaScript
scss
const chain = prompt // 1. PromptTemplate
.pipe(model) // 2. ChatModel
.pipe(parser) // 3. StringOutputParser
.pipe(runnableLambda) // 4. 自定义函数
.pipe(finalFormatter); // 5. 最终格式化
// 内部结构相当于:
{
first: prompt,
middle: [model, parser, runnableLambda],
last: finalFormatter
}
易错提醒:
-
chain.invoke() 的输入必须匹配第一个组件的输入变量(这里是 {topic})。
核心规则: chain.invoke(input) 的 input 只看链的第一个组件(即 first)需要什么变量。
-
输出是 AIMessage 对象,内容在
.content或.text(不同模型略有差异)。
六、进阶:多步链 + 自动数据传递
手动链:
JavaScript
javascript
const fullChain = RunnableSequence.from([
(input) => explainChain.invoke({topic: input.topic}).then(res => res.text),
(explanation) => summaryChain.invoke({explanation}).then(res => `知识点:${explanation} 总结:${res.text}`)
]);
它能跑,但只是"伪链"------所有 invoke 和拼接都是你手动写的,LangChain 只负责笨笨地传返回值。
纯线性自动链(真链,但丢失中间值):
JavaScript
javascript
import { StringOutputParser } from "@langchain/core/output_parsers";
const explainChain = explainPrompt.pipe(model); // Prompt + Model
const summaryChain = summaryPrompt.pipe(model); // Prompt + Model
// 真正的自动链:全都是 Runnable 对象
const fullChain = RunnableSequence.from([
explainChain.pipe(new StringOutputParser()), // 第1步:输出详细解释字符串
summaryChain.pipe(new StringOutputParser()), // 第2步:自动收到上一步字符串,输出总结字符串
]);
const result = await fullChain.invoke({ topic: "闭包" });
console.log(result);
// 输出:只有"三点总结"的字符串,详细解释被"吃掉"了
为什么是真自动?
- 没有一行 invoke()
- 没有 .then()
- 数据完全由 LangChain 自动传递
- 支持流式、批处理、LangSmith 追踪
缺点:最终只能拿到总结,详细解释丢失了(线性链的特性)。
终极优雅版:对象语法 + RunnablePassthrough(LCEL 精髓):
JavaScript
javascript
import { RunnablePassthrough, StringOutputParser } from '@langchain/core/runnables';
const explainChain = explainPrompt.pipe(model).pipe(new StringOutputParser());
const summaryChain = summaryPrompt.pipe(model).pipe(new StringOutputParser());
const fullChain = RunnableSequence.from([
{ explanation: explainChain }, // 生成详细解释
{
explanation: new RunnablePassthrough(), // 保留不吃掉
summary: summaryChain,
},
({ explanation, summary }) => `
【前端知识点详解】
${explanation}
【三点核心总结】
${summary}
`.trim()
]);
await fullChain.invoke({ topic: '闭包' });
底层逻辑: new RunnablePassthrough() 的作用是把上一步的整个输出(返回值)原封不动地传递到下一步,但它通常和对象语法结合使用,才发挥最大威力。
执行流程详解:
-
第1步输出一个对象:{ explanation: "详细文本..." }
-
第2步收到这个对象:
- explanation: new RunnablePassthrough() → 直接返回收到的 explanation 值(不修改)
- summary: summaryChain → 用收到的 explanation 作为输入,生成总结
-
第2步最终输出:{ explanation: "详细文本...", summary: "三点总结..." }
-
第3步拿到完整对象,自由组合
所以:RunnablePassthrough 就是那个"搬运工"------它确保上一步的 explanation 不会被 summaryChain 的输出覆盖,而是继续向下传递。
常见用法总结
| 用法场景 | 代码示例 | 作用 |
|---|---|---|
| 保留原始输入 | { topic: new RunnablePassthrough() } | 把 invoke 的 {topic} 一直传下去 |
| 保留中间结果 | { explanation: new RunnablePassthrough() } | 防止被下一步吃掉 |
| 合并多个并行结果 | 和其他字段一起用 | 构建复杂对象 |
| 跳过处理直接传递 | 单独用(少见) | 输入 = 输出 |
核心点:对象语法,占位保留
先说"对象语法"是什么
对象语法就是:在 RunnableSequence.from() 里,每一步不是放单个 Runnable,而是放一个对象 { key: runnable }。
JavaScript
csharp
RunnableSequence.from([
{ explanation: explainChain }, // 第一步:对象
{
explanation: ..., // 第二步:还是对象
summary: ...
}
])
每一步的输入/输出不再是单个字符串,而是一个对象(带有多个字段)。
这让数据流从"一维流水线"变成了"多维结构化对象流",你可以随意添加、保留、合并字段。
再来说"占位保留"是什么意思
在对象语法里,如果你不做任何处理,后一步的输出会完全覆盖前一步的输出(只保留新生成的字段)。
比如:
JavaScript
yaml
RunnableSequence.from([
{ explanation: explainChain }, // 输出 { explanation: "详细文本" }
{ summary: summaryChain } // 输入 { explanation: ... },但只输出 { summary: "三点总结" }
])
// 最终结果:只有 { summary: ... },explanation 没了!
explanation 被"吃掉"了------这就是线性思维的遗毒。
"占位保留" 的意思就是:我要在这一步故意"占个位置",把上一步的某个字段原样保留下来,不让它被吃掉。
工具就是 new RunnablePassthrough()。
JavaScript
arduino
{
explanation: new RunnablePassthrough(), // ← 这里!占位保留
summary: summaryChain
}
它相当于在对象里说:"explanation 这个字段,你别动,上一步是什么我就输出什么(占位传下去)"。
优势:完全自动、保留所有中间值、支持并行/分支、LangSmith 追踪完美。
八、LangChain 的未来与最佳实践
到 2025 年,LangChain 已与 LangGraph(状态图工作流)深度融合,支持更复杂的 Agent。记住:
- 用 LCEL(LangChain Expression Language)即
|或pipe()构建链,最简洁。 - 生产环境加 LangSmith 调试、追踪。
- 模型切换只需改一行:从 DeepSeek 换 OpenAI 零成本。
LangChain 不是终点,而是起点。它让你把 LLM 从玩具变成生产力武器。