引言
一句话概括:LangChain 是一个让你像搭积木一样构建 AI 应用的工程化框架。
在 ChatGPT 横空出世、Transformer 架构席卷全球的 2022 年,你可能以为 LangChain 是为了"蹭热度"才诞生的。但其实------LangChain 比 ChatGPT 还早!它早在 LLM(大语言模型)爆发前就默默布局,如今已推出 1.0+ 稳定版本,成为构建 AI 应用的事实标准之一。
今天,我们就通过一个真实的小项目,手把手带你走进 LangChain 的世界。我们将从环境准备 开始,到逐行解析代码 ,再到深入理解每个 API 的作用,让你不仅会用,还能讲清楚"为什么这么用"。
一、什么是 LangChain?
1.1 背景与定位
LangChain = Lang (语言模型) + Chain(链)
它的核心思想非常简单又强大:
- 统一接口:无论你用的是 DeepSeek、OpenAI、Anthropic 还是本地 Llama,LangChain 都提供一致的调用方式。
- 模块化组合:把 Prompt、LLM、Memory、Tools 等组件像乐高一样拼接成工作流(Chain)。
- 工程化支持:不再是"prompt 调试即上线",而是支持可测试、可维护、可扩展的 AI 应用开发。
你可以把它想象成 AI 版的 n8n 或 Coze ------ 把一个个"任务节点"(Node)连接起来,形成自动化流程。
1.2 核心概念速览
- Model (LLM/ChatModel):大语言模型的抽象接口。
- PromptTemplate:结构化提示词模板,支持变量注入。
- Chain:多个组件(如 Prompt + Model)组成的可执行流水线。
- Runnable:所有可执行单元的基类(包括 Chain、Model、Function 等)。
- Agent:能自主决策、调用工具的高级智能体(本项目未涉及,但基于 Chain 构建)。
LangChain 的哲学是:将 LLM 从"黑盒"变为"可编程模块"。
二、项目准备:5 分钟搭好脚手架
我们先看看项目的骨架。以下是 package.json 的内容:
bash
{
"name": "demo",
"version": "1.0.0",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"description": "",
"type": "module",
"dependencies": {
"@langchain/core": "^1.1.8",
"@langchain/deepseek": "^1.0.3",
"dotenv": "^17.2.3",
"langchain": "^1.2.3"
}
}
2.1 关键字段解析
"name": "demo":项目名称。"version": "1.0.0":遵循语义化版本。"type": "module":关键配置 !启用 ES Module(ESM),所以我们使用import语法而非 CommonJS 的require。这是现代 Node.js 项目的推荐做法。"dependencies":@langchain/core:LangChain 的核心抽象层,包含PromptTemplate、RunnableSequence等基础类。@langchain/deepseek:DeepSeek 官方提供的模型适配器(Provider),封装了 API 调用细节。dotenv:用于加载.env文件中的环境变量,保护敏感信息(如 API Key)。langchain:主包,整合常用功能,简化导入路径。
💡 为什么分开
core和deepseek?LangChain 采用插件化架构 。
core提供通用接口,而@langchain/deepseek是针对特定模型的实现。这样你可以轻松切换模型,只需更换适配器包。
2.2 环境变量配置
接着看 .env 文件(敏感信息已脱敏,但结构保留):
DEEPSEEK_API_KEY=sk-986270f999f548f4b35dee32393eaf42
- 这个文件绝不应提交到 Git (通常加入
.gitignore)。 dotenv包会在程序启动时自动读取该文件,并将变量注入process.env。- LangChain 的模型适配器会自动检测 环境变量中的
DEEPSEEK_API_KEY,无需手动传入。
2.3 安装依赖
在项目根目录运行:
bash
pnpm install
# 或 npm install / yarn install
推荐使用
pnpm:速度快、磁盘占用少,且与 LangChain 官方示例一致。
至此,项目环境已准备就绪!
三、从最简单的开始:main.js ------ 直连 LLM
来看 main.js(原样引用,一字不改):
javascript
import 'dotenv/config' // 加载环境变量
// console.log(process.env.DEEPSEEK_API_KEY)
import { ChatDeepSeek } from '@langchain/deepseek' // 适配器
const model = new ChatDeepSeek({
model: 'deepseek-reasoner',
temperature: 0,
})
// invoke执行 是一个异步函数,需要用 await 等待结果
const res = await model.invoke('用一句话解释什么是RAG')
console.log(res.content)
// RAG是一种基于检索的生成模型,它通过从外部知识库中检索相关信息来生成文本。
3.1 逐行详解
第 1 行:import 'dotenv/config'
- 这是一个 side-effect import(副作用导入)。
- 执行时会自动读取项目根目录下的
.env文件,并将其中的键值对加载到process.env对象中。 - 因此,后续代码可以直接通过
process.env.DEEPSEEK_API_KEY访问密钥(虽然这里不需要显式使用)。
第 3 行:import { ChatDeepSeek } from '@langchain/deepseek'
- 从 DeepSeek 适配器包中导入
ChatDeepSeek类。 - 这是一个 Chat 模型专用类 ,继承自 LangChain 的
BaseChatModel抽象基类。 - 它封装了与 DeepSeek API 的通信细节(如 HTTP 请求、错误处理、流式响应等)。
第 4--7 行:实例化模型
javascript
const model = new ChatDeepSeek({
model: 'deepseek-reasoner',
temperature: 0,
})
model: 'deepseek-reasoner':指定使用的模型名称。DeepSeek 提供多种模型,reasoner版本擅长逻辑推理和复杂任务。temperature: 0:控制生成文本的随机性。0表示完全确定性(相同输入永远得到相同输出),适合事实性问答。- 值越高(最大通常为 1 或 2),输出越有创意但也越不稳定。
✅ 设计亮点:通过适配器模式,LangChain 屏蔽了不同 LLM 的 API 差异。换模型?只需改一行 import 和类名!
第 10-11 行:调用模型
javascript
const res = await model.invoke('用一句话解释什么是RAG')
console.log(res.content)
.invoke(input):- 这是所有 LangChain Runnable 对象的标准方法。
- 对于 Chat 模型,
input可以是:- 字符串(本例):自动包装为
HumanMessage。 BaseMessage[]数组:用于多轮对话(如[new HumanMessage("..."), new AIMessage("...")])。
- 字符串(本例):自动包装为
- 返回一个
Promise<AIMessage>。
res.content:AIMessage是 LangChain 定义的消息类型,包含:.content:模型生成的文本。.additional_kwargs:额外元数据(如 token 使用量,取决于模型支持)。
- 打印结果如注释所示,准确解释了 RAG(Retrieval-Augmented Generation)。
✅ 小结 :main.js 展示了 LangChain 最基础的能力------用统一接口调用任意 LLM,背后是"适配器模式"的威力。
四、加入提示词模板:1.js ------ 动态 Prompt
现在让 AI 更聪明一点。看 1.js:
javascript
// 适配器 provider 省去了适配工作
// 适配大模型也是工作量
import 'dotenv/config'
import { ChatDeepSeek } from '@langchain/deepseek' // 提示词模块 用于格式化提示词模板
import {PromptTemplate} from '@langchain/core/prompts'
// static 方法 用于创建提示词模板 方法 类的 不是实例的
const prompt = PromptTemplate.fromTemplate(`
你是一个{role} 请用不超过{limit}字回答以下问题: {question}
`)
const promptStr = await prompt.format({
role: '前端面试官',
limit: 50,
question: '什么是闭包',
})
// const prompt2 = await prompt.format({
// role: '后端面试官',
// limit: '50',
// question: '什么是MVC'
// })
// console.log(promptStr, prompt2)
const model = new ChatDeepSeek({
model: 'deepseek-reasoner',
temperature: 0.7
});
const res = await model.invoke(promptStr)
console.log(res.content);
// 闭包是函数及其所在词法环境的组合,它使函数能够记住并访问创建时的作用域,即使函数在其他地方执行。
4.1 逐行详解
第 5 行:import {PromptTemplate} from '@langchain/core/prompts'
- 从核心包导入
PromptTemplate。 - 这是 LangChain 实现 Prompt 工程化 的基石。
第 7--9 行:创建模板
javascript
const prompt = PromptTemplate.fromTemplate(`
你是一个{role} 请用不超过{limit}字回答以下问题: {question}
`)
PromptTemplate.fromTemplate(templateString):- 这是一个 静态工厂方法(static method),直接通过类调用,无需实例化。
- 模板字符串中的
{role}、{limit}、{question}是占位符。 - LangChain 会自动解析这些占位符,并在
.format()时替换。
💡 为什么用反引号(`````)?
方便书写多行字符串,提升可读性。实际内容会被 trim 和标准化。
第 10--14 行:格式化 Prompt
javascript
const promptStr = await prompt.format({
role: '前端面试官',
limit: 50,
question: '什么是闭包',
})
.format(variables):-
接收一个对象,键必须与模板中的占位符匹配。
-
返回一个格式化后的字符串(
Promise<string>,所以用await)。 -
本例生成:
你是一个前端面试官 请用不超过50字回答以下问题: 什么是闭包
-
📝 注释中的
prompt2示例展示了模板的复用性:同一模板,不同角色、问题。
第 23--26 行:调用模型
javascript
const model = new ChatDeepSeek({
model: 'deepseek-reasoner',
temperature: 0.7
});
const res = await model.invoke(promptStr)
temperature: 0.7:- 引入适度随机性,让回答更自然、不机械。
- 对比
main.js的0,这里更适合"面试官"这种需要人性化表达的场景。
第 27 行:输出结果
javascript
console.log(res.content);
// 闭包是函数及其所在词法环境的组合,它使函数能够记住并访问创建时的作用域,即使函数在其他地方执行。
- 模型成功扮演"前端面试官",并在 50 字内精准回答。
小结 :1.js 引入了 动态提示词,让同一个模板能应对不同角色、限制和问题,极大提升复用性与可控性。
五、构建工作流:2.js ------ 用 Chain 连接 Prompt 和 LLM
AI 应用往往不止一步。2.js 展示了如何用 Chain(链) 组合多个步骤:
javascript
// chain 链 多个节点 链接起来 形成一个流程
// AI业务是复杂的,需要多个节点来完成 分步骤处理
// 每个节点都有自己的功能,每个点都可执行
// 节点之间通过 输入输出 来通信
// 链 就是多个节点 链接起来 形成一个流程,即工作流
import 'dotenv/config'
import { ChatDeepSeek } from '@langchain/deepseek'
import { PromptTemplate } from '@langchain/core/prompts'
const model = new ChatDeepSeek({
model: 'deepseek-reasoner',
temperature: 0.7
})
const prompt = PromptTemplate.fromTemplate(`
你是一个前端专家,用一句话解释{topic}
`)
// prompt 模板节点 -> model 节点
// model 代表的是llm节点
// 结束节点 invoke
// pipe 管道 用于链接多个节点,形成工作流
// runnable 可运行序列 是一个链 Sequencial workflow Agent
// SequencialChain
const chain = prompt.pipe(model)
// console.log(chain)
// console.log(chain, instanceof RunnableSequence)
const response = await chain.invoke({ topic: '什么是闭包'})
console.log(response.content)
// 闭包是指函数能够记住并访问其词法作用域中的变量,即使该函数在其原始作用域之外执行。
5.1 逐行详解
第 12--15 行:定义模型与模板
- 与之前类似,但模板更简洁:
用一句话解释{topic}。
第 24 行:构建 Chain
javascript
const chain = prompt.pipe(model)
.pipe(other):- 这是
Runnable接口的核心方法。 - 它将当前对象(
prompt)的输出作为下一个对象(model)的输入,返回一个新的RunnableSequence。 - 数据流:
{topic} → prompt.format() → string → model.invoke() → AIMessage
- 这是
RunnableSequence:- 代表一个顺序执行的工作流。
- 它本身也是一个
Runnable,因此可以继续.pipe()或直接.invoke()。
💡 注释提到 "SequencialChain" ------ 这是 LangChain 旧版术语,新版统一为
RunnableSequence。
第 29 行:调用 Chain
javascript
const response = await chain.invoke({ topic: '什么是闭包'})
- 注意:现在传入的是
{ topic: '...' },而不是格式化后的字符串! chain内部会自动将这个对象传递给prompt.format()。
第 30 行:输出
javascript
console.log(response.content)
- 返回
AIMessage,取.content即可。
✅ 小结 :2.js 展示了 LangChain 的核心抽象------Chain 。它把"构造 prompt"和"调用 LLM"封装成一个原子操作,代码更简洁、逻辑更清晰,且具备组合性(可嵌套、可复用)。
六、复杂工作流:3.js ------ 多步 Chain 与总结提炼
最后,挑战高阶玩法:多阶段 Chain 。看 3.js:
javascript
import 'dotenv/config' // 加载环境变量
import { ChatDeepSeek } from '@langchain/deepseek' // 适配器
import { PromptTemplate } from '@langchain/core/prompts' // 提示词模板
// AI 应用的编程方式
// LLM 黑盒打开 以前key是prompt 现在是input
// langchain AI应用的工程化
import { RunnableSequence } from '@langchain/core/runnables' // 可运行序列 链
const model = new ChatDeepSeek({
model: 'deepseek-reasoner',
temperature: 0.7
})
const explainPrompt = PromptTemplate.fromTemplate(`
你是一个前端专家,请详细介绍一下概念:{topic}
要求:覆盖定义、原理、使用方式、别超过300字。
`)
const summaryPrompt = PromptTemplate.fromTemplate(`
请将以下前端概念解释总结为3个核心要点(每点不超过30个字):
{explanation}
`)
const explainChain = explainPrompt.pipe(model)
// console.log(explainChain)
const summaryChain = summaryPrompt.pipe(model)
// console.log(summaryChain)
const fullChain = RunnableSequence.from([
// input 作为一个对象,包含topic
(input) => explainChain.invoke({ topic: input.topic }).then(res => res.text),
(explanation) => summaryChain.invoke({ explanation }).then(res => `知识点:${explanation} 总结:${res.text}`)
])
// console.log(fullChain)
const response = await fullChain.invoke({ topic: '什么是RAG'})
console.log(response)
// RAG是一种基于检索的生成模型,它通过从外部知识库中检索相关信息来生成文本。
6.1 逐行详解
第 7 行:导入 RunnableSequence
- 显式导入
RunnableSequence,用于手动构建复杂链。
第 10--19 行:定义两个 Prompt 模板
explainPrompt:要求详细解释(定义、原理、使用方式),限 300 字。summaryPrompt:要求将解释总结为 3 个要点,每点 ≤30 字。
第 20--23 行:构建两个子 Chain
javascript
const explainChain = explainPrompt.pipe(model)
const summaryChain = summaryPrompt.pipe(model)
- 每个 Chain 负责一个独立任务,高内聚、低耦合。
第 26--31 行:构建主 Chain
javascript
const fullChain = RunnableSequence.from([
// input 作为一个对象,包含topic
(input) => explainChain.invoke({ topic: input.topic }).then(res => res.text),
(explanation) => summaryChain.invoke({ explanation }).then(res => `知识点:${explanation} 总结:${res.text}`)
])
RunnableSequence.from(steps):- 接收一个函数数组,每个函数代表一个处理步骤。
- 步骤 1:
- 输入:
{ topic: 'RAG' } - 调用
explainChain,获取详细解释。 .then(res => res.text):提取AIMessage的文本内容(res.text是res.content的别名)。
- 输入:
- 步骤 2:
- 输入:上一步的输出(即解释文本)。
- 调用
summaryChain,生成摘要。 - 拼接最终字符串:
知识点:... 总结:...
- 为什么用
.then()?- 因为
invoke()返回Promise,而RunnableSequence要求每个步骤同步返回Promise(不能用await,因为函数不是 async)。
- 因为
⚠️ 注意:这种写法略显冗长。LangChain 也支持更简洁的
LCEL(LangChain Expression Language)写法,但本例展示了底层机制。
第 34--35 行:执行与输出
javascript
const response = await fullChain.invoke({ topic: '什么是RAG'})
console.log(response)
- 最终输出是一个包含"详细解释 + 要点总结"的字符串。
✅ 小结 :3.js 展示了 LangChain 处理复杂 AI 逻辑的能力------分阶段思考、逐步精炼,模拟人类专家的工作流。这种模式非常适合教学、报告生成、知识提炼等场景。
七、总结:LangChain 的工程化价值
通过这 4 个文件,我们见证了 LangChain 如何一步步将"调用大模型"这件事:
| 文件 | 能力 | 核心 API | 工程价值 |
|---|---|---|---|
main.js |
基础调用 | ChatDeepSeek, .invoke() |
统一接口,屏蔽模型差异 |
1.js |
动态 Prompt | PromptTemplate.fromTemplate(), .format() |
Prompt 可配置、可复用 |
2.js |
单步 Chain | .pipe(), RunnableSequence |
流程封装,逻辑清晰 |
3.js |
多步 Chain | RunnableSequence.from([...]) |
复杂任务分解,模块化 |
为什么需要 LangChain?
- 避免重复造轮子:每个 LLM 的 API 都不同,LangChain 提供统一抽象。
- 提升开发效率:用声明式方式构建 AI 应用,而非过程式拼接字符串。
- 增强可维护性:Chain 可测试、可组合、可监控。
- 面向未来:轻松集成 Memory、Tools、Agents 等高级功能。
下一步建议
- 添加对话记忆 :使用
ConversationChain或RunnableWithMessageHistory。 - 接入 RAG :用
RetrievalQAChain连接向量数据库,实现知识库问答。 - 构建 Agent:让 AI 自主选择工具(如搜索、计算、API 调用)。
- 部署优化:使用缓存、流式响应、token 限制等生产级技巧。
Happy Coding!🚀