LangChain 入门实战:从零搭建 AI 应用工作流

前言

2022 年底 ChatGPT 横空出世,让大家真正感受到大语言模型(LLM)的威力。但其实在 ChatGPT 发布之前,就已经有一个专门为 AI 应用开发而生的框架------LangChain,如今它已经发展到 1.0+ 版本,成为最受欢迎的 LLM 应用开发框架之一。

简单来说,LangChain 就是一个帮助开发者快速把大语言模型集成到实际业务中的"工具箱"。它解决了两个核心问题:

  1. 大模型切换成本高(不同厂商 API 不统一)
  2. 真实业务场景往往不是"一问一答"那么简单,需要多步处理、组合多种能力

LangChain 的名字就很直白:Lang (Language Model) + Chain(链)。把语言模型像积木一样串起来,形成可复用、可配置的工作流。

本文将结合实际代码,从最基础的调用开始,一步步带你掌握 LangChain 的核心概念。所有代码都基于 Node.js(ESM 模块)与 DeepSeek 模型实战运行通过,适合前端开发者快速上手。

一、环境准备与第一个 Hello World

1. 安装依赖

Bash 复制代码
pnpm init
pnpm i @langchain/deepseek @langchain/core dotenv

2. 配置 API Key

创建 .env 文件:

ini 复制代码
.env
`DEEPSEEK_API_KEY=sk-你的密钥`

3. 最简单的调用(main.js)

JavaScript 复制代码
import "dotenv/config";
import { ChatDeepSeek } from "@langchain/deepseek";

const model = new ChatDeepSeek({
  model: "deepseek-reasoner", // 推理能力更强的模型
  temperature: 0,             // 确定性输出
});

const res = await model.invoke("讲一个沸羊羊和美羊羊的短故事");
console.log(res.content);

这就是 LangChain 最核心的价值之一:统一的 LLM 接口

你只需要换一行导入和实例化,就能无缝切换到 OpenAI、Anthropic、通义千问、百度文心等几十种模型。省去了每次都要研究新 API 文档的麻烦。

为什么这段代码里不用手动指定 apiKey 和 baseURL?

答案很简单:LangChain 的 @langchain/deepseek 包在设计时,已经帮你把这两个参数设置成了"智能默认值"。

1). apiKey 为什么不用写?
  • ChatDeepSeek 类继承自 LangChain 的 OpenAI 兼容模型基类。

  • 在构造函数中,apiKey 参数是可选的,如果不传,它会自动从环境变量 process.env.DEEPSEEK_API_KEY 中读取。

  • 代码最前面有 import "dotenv/config",这行代码已经把 .env 文件里的 DEEPSEEK_API_KEY=sk-... 加载进了 process.env。

  • 所以 LangChain 一实例化模型,就自动拿到了API Key,完全不需要你手动传 { apiKey: process.env.DEEPSEEK_API_KEY }。

这是 LangChain 的最佳实践设计:鼓励使用环境变量管理密钥,既安全(不硬编码),又灵活(不同环境用不同配置)。

如果你想显式传,也可以:

JavaScript 复制代码
const model = new ChatDeepSeek({
  apiKey: process.env.DEEPSEEK_API_KEY,  // 显式传,等价于不传
});

但不传更简洁、更推荐。

2). baseURL 为什么不用写?
  • DeepSeek 的官方 API 是完全兼容 OpenAI API 格式 的,只不过端点不一样(api.deepseek.com)。

  • LangChain 的 ChatDeepSeek 在内部已经硬编码了正确的 baseURLapi.deepseek.com(或 /v1 路径)。

  • 所以你不需要像直接用 OpenAI SDK 那样手动设置 baseURL: 'api.deepseek.com'。

这也是 LangChain "适配器模式"的威力:它把不同厂商的细微差异(比如 baseURL、认证方式)封装好了,你只管用统一的接口。

如果你用的是其他代理或自部署的 DeepSeek 模型,才需要手动覆盖:

JavaScript 复制代码
const model = new ChatDeepSeek({
  baseURL: "http://localhost:11434/v1",  // 比如 Ollama 本地部署
});

但官方云端 API 完全不需要。

总结对比表
参数 是否必须传? 默认行为 代码中实际来源
apiKey 否(可选) 自动读取 process.env.DEEPSEEK_API_KEY 来自 .env + dotenv/config
baseURL 否(可选) 内置默认 api.deepseek.com(官方端点) LangChain 包内部硬编码
model 无默认,必须指定(如 "deepseek-reasoner") 手动传的
temperature 默认 1.0(随机性更高) 手动设为 0(确定性输出)
小贴士
  • 这种"默认读取环境变量 + 内置 baseURL"的设计,在 LangChain 的很多集成包里都很常见(比如 OpenAI 是 OPENAI_API_KEY,Anthropic 是 ANTHROPIC_API_KEY)。

  • 它让你本地开发时用 .env 方便,部署到 Vercel/Netlify/Cloudflare 等平台时,直接在平台后台设置环境变量就行,无需改代码。

二、Prompt 模板:让提示词可复用、可配置

单纯调用模型很快会遇到问题:每次都要手写一长串 prompt,角色、字数限制、问题都硬编码死了,复用性极差。

LangChain 提供了 PromptTemplate,让提示词变成可参数化的模板。

JavaScript 复制代码
import { PromptTemplate } from "@langchain/core/prompts";

const prompt = PromptTemplate.fromTemplate(`
  你是一个{role},
  请用不超过{limit}字回答以下问题:{question}
`);

const prompt1 = await prompt.format({
  role: "前端面试官",
  limit: "50",
  question: "什么是闭包?"
});

const prompt2 = await prompt.format({
  role: "后端面试官",
  limit: "50",
  question: "什么是MVC架构?"
});
// 调用
const model = new ChatDeepSeek({
  model: "deepseek-reasoner", 
  temperature: 0,
});

const res = await model.invoke(prompt2);
console.log(res.content);
//MVC是一种设计模式,将应用分为模型(数据)、视图(界面)和控制器(逻辑)三层,以实现职责分离和代码易维护。

这样同一套模板可以服务多个场景,大大提升了代码的可维护性。

面试常考: "如何设计一个稳定的 Prompt?如何在团队中复用 Prompt?" 答案:使用模板化管理,变量分离,角色、限制条件、输入内容分开配置。

三、Chain:把多个步骤串起来

真实业务很少是一问一答就结束的。更常见的需求是:

  1. 先详细解释一个概念
  2. 再把解释提炼成几个关键点
  3. 最后生成面试题或代码示例

这就是 Chain(链)的用武之地。

1.LangChain 中的 RunnableSequence 彻底讲解

在 LangChain(尤其是 JS/TS 版本 @langchain/core)中,RunnableSequence 是构建 Chain(链)的核心基石。它本质上就是一个"顺序执行的工作流":把多个可运行的组件(Runnable)按顺序连接起来,前一个组件的输出自动作为后一个组件的输入。

简单来说:RunnableSequence 就是 LangChain 里"Chain"的现代实现方式。它取代了早期版本中繁琐的 Chain 类,让一切变得更模块化、更灵活、更易组合。

为什么需要 RunnableSequence?

早期 LangChain 的 Chain 写法很死板:你要手动创建各种具体的 Chain 类(如 LLMChain、SequentialChain),代码冗长,扩展性差。

现在 LangChain 引入了 LCEL(LangChain Expression Language) ,核心就是 Runnable 接口。几乎所有组件(PromptTemplate、ChatModel、OutputParser、自定义函数等)都实现了 Runnable 接口,具备统一的 .invoke()、.stream()、.batch() 等方法。

RunnableSequence 的作用就是:把这些 Runnable 像管道一样串起来,形成复杂的工作流

2.简单 Sequential Chain

JavaScript 复制代码
import { PromptTemplate } from "@langchain/core/prompts";
import { RunnableSequence } from "@langchain/core/runnables";

const prompt = PromptTemplate.fromTemplate(
  `你是一个前端专家,用一句话解释:{topic}`
);

const chain = prompt.pipe(model); // 用 pipe 把 prompt 和 model 连接

const response = await chain.invoke({ topic: "闭包" });
console.log(response.content);

pipe 是 LangChain 的核心操作符,把可运行的节点(Runnable)连接成流水线。

prompt.pipe(model) 等价于:先格式化 prompt,再把结果喂给 model。

3.多步 Chain:解释 + 总结

JavaScript 复制代码
const explainPrompt = PromptTemplate.fromTemplate(`
  你是一个前端专家,请详细介绍一下概念:{topic}
  要求:覆盖定义、原理、使用方式,不超过300字
`);

const summaryPrompt = PromptTemplate.fromTemplate(`
  请将以下前端概念解释总结为三个核心要点(每点不超过20字):
  {explanation}
`);

const explainChain = explainPrompt.pipe(model);
const summaryChain = summaryPrompt.pipe(model);

// 组合成完整链
const fullChain = RunnableSequence.from([
  async ({ topic }) => {
    const result = await explainChain.invoke({ topic });
    return result.content; // 注意是 .content
  },
  async (explanation) => {
    const result = await summaryChain.invoke({ explanation });
    return `知识点:${explanation}\n\n总结:${result.content}`;
  },
]);

// 调用
const response = await fullChain.invoke({
  topic: "闭包",
});

console.log(response);

输出示例:

LangChain JS 的 RunnableSequence.from() 的工作机制是:
  • 每个函数的返回值 会直接作为下一个函数的参数传入。
  • 参数名就是你函数声明里的参数名(这里是 explanation)。
  • 类型完全匹配:第一个 async 函数返回 string(即 result.content),第二个函数的形参 explanation 就会收到这个字符串。

这段代码的作用是:构建一个两步顺序执行的 AI 工作流

  1. 先用 explainChain(Prompt + Model)生成一个概念的详细解释
  2. 再把这个解释喂给 summaryChain(另一个 Prompt + Model),生成三点总结
  3. 最后把"详细解释"和"三点总结"拼接成最终输出

整个过程只需要一行调用:

JavaScript 复制代码
await fullChain.invoke({ topic: "闭包" });

就能得到结构化的完整结果。

逐行拆解

JavaScript 复制代码
const fullChain = RunnableSequence.from([
  • const fullChain:声明一个常量变量,用于存放我们构建的完整处理链。

  • RunnableSequence.from([ ... ]):这是 LangChain JS 中创建序列链(Sequence Chain)的标准方式。

    • 它接受一个数组,数组里的每个元素是一个 Runnable(可运行的对象)。
    • 这里我们传入的是两个 async 函数,LangChain 会自动将它们包装成 Runnable。
    • 整个序列的执行顺序:从数组第0个元素开始,依次执行,每个步骤的输出自动作为下一个步骤的输入。
JavaScript 复制代码
async ({ topic }) => {
  • 定义序列的第一个步骤:一个异步函数。
  • 参数使用解构形式 { topic },表示这个步骤接收的输入是一个对象,必须包含 topic 属性(例如 { topic: "闭包" })。
  • 这是整个链的入口输入格式决定的------因为最后调用 fullChain.invoke({ topic: "闭包" })。
JavaScript 复制代码
const result = await explainChain.invoke({ topic });
  • 调用前面定义好的 explainChain(即 explainPrompt + model 的链)。
  • 传入 { topic },会自动替换提示模板中的 {topic} 占位符。
  • invoke() 返回一个 Promise,里面是模型的输出:一个 AIMessage 对象。
  • 使用 await 等待模型真正返回结果。
JavaScript 复制代码
return result.content; // 注意是 .content
  • 从 AIMessage 对象中提取实际的文本内容。

  • 在 LangChain JS 版本中,模型返回的消息内容存储在 .content 属性(字符串类型),不是 .text

  • return 这个纯字符串(详细解释文本)。

  • 关键点 :这个返回值会直接、完整、无包装地作为下一个步骤函数的参数传入。

  • 结束第一个步骤的函数定义,并以逗号分隔,进入数组下一个元素。

JavaScript 复制代码
async (explanation) => {
  • 定义序列的第二个步骤:另一个异步函数。
  • 参数直接写成 explanation(单个字符串),因为上一步返回的就是一个字符串。
  • LangChain 会自动把上一步的返回值作为这个参数传进来。
  • 参数名 explanation 是我们自己起的,便于代码阅读,表示"详细解释文本"。
JavaScript 复制代码
const result = await summaryChain.invoke({ explanation });
  • 调用 summaryChain(summaryPrompt + model 的链)。
  • 传入 { explanation },会把上一步得到的详细解释文本填充到总结提示的 {explanation} 占位符中。
  • 再次等待模型生成三点总结,返回另一个 AIMessage 对象。
JavaScript 复制代码
return `知识点:${explanation}\n\n总结:${result.content}`;
  • 构建最终输出字符串:

    • 先输出"知识点:" + 原始的详细解释(explanation)
    • 换两行
    • 再输出"总结:" + 模型生成的三个核心要点(result.content)
  • 这个返回值就是整个 fullChain.invoke() 的最终结果(一个字符串)。

  • 至此,fullChain 就是一个完整的、可复用的 Runnable 对象,可以多次调用。

面试常考: "如何避免 Prompt 过长导致成本高、效果差?" 答案:拆分任务为多步 Chain,先让模型专注做一件事,再基于结果做下一步,整体效果更好且更可控。

四、为什么选择 LangChain?

  1. 换模型跟换头像一样简单

    现实中,模型说换就换:今天 DeepSeek 便宜,明天 Claude 效果好,后天领导说用 Grok 4。 用原生 SDK 每次换都要改一堆代码,LangChain 直接换一行 import + 环境变量,完事。 这点在国内尤其香------国产模型层出不穷,接口还不统一,LangChain 基本都给你适配好了。

  2. 业务场景基本都不是"一问一答" 你真正要做的东西大概率是:

    • 用户问问题 → 先搜知识库 → 再生成答案(RAG)
    • 先解释概念 → 再出总结 → 再生成面试题
    • 先判断意图 → 调用工具 → 再回复(Agent) LangChain 的 Chain / RunnableSequence 天生就为这种"多步流程"而生,几行代码就能搭好整个流水线,其他框架要么没这概念,要么自己手撸状态机,累得要死。
  3. 工程化做得最到位

    • Prompt 可以模板化管理,不会满文件都是硬编码长字符串
    • 对话历史(Memory)现成用,不用自己拼 messages 数组
    • 流式输出、超时重试、日志追踪这些生产必备都内置
    • 还有 LangSmith 能可视化每一步的输入输出、token 消耗,调试神器
  4. 生态最狠 想要什么基本都有:100+ 模型、50+ 向量库、各种文档加载器、工具调用、Agent 框架...... 基本不需要你自己从零造轮子,踩坑概率低太多。

  5. 社区和资料最丰富 国内外用的人最多,GitHub 快 10 万星了,遇到问题 Google/Baidu 一搜一大堆解决方案,中文教程也多到爆炸。

缺点当然也有:概念有点多,入门时会觉得"哇怎么这么多类",包曾经比较大(现在拆分好多了)。 但一旦上手,你就会发现:它把你从"怎么调 API"的琐事里解放出来,让你真正去思考"这个 AI 功能该怎么设计才合理"。

一句话总结: 如果你只是玩玩 demo,用原生 SDK 就行;但一旦要做真实上线、能迭代、能维护的 AI 功能,LangChain 目前还是最省心、最全面的选择。

五、写在最后

LangChain 不是一个"黑盒魔法",而是把大语言模型从"玩具"变成"生产工具"的关键一层抽象。

它让你关注的重点从"怎么调用 API"变成"怎么设计合理的 AI 工作流",这才是构建可靠 AI 应用的正确姿势。

相关推荐
星月心城2 小时前
八股文-JavaScript(第二天)
开发语言·javascript·ecmascript
Aevget2 小时前
DevExpress JS & ASP.NET Core v25.1新版亮点 - 新增AI文本编辑功能
javascript·人工智能·asp.net·界面控件·devexpress·ui开发
用户377833043492 小时前
( 教学 )Agent 构建 Memory(提示词对话存储)1. ConversationBufferMemory(对话缓存存储, 版本>1.0和<1.0的区别
langchain·agent
xiaoxue..2 小时前
爬楼梯问题:从递归到动态规划再到闭包的进化之路
javascript·算法·面试·动态规划
后端小张2 小时前
【AI 学习】LangChain框架深度解析:从核心组件到企业级应用实战
java·人工智能·学习·langchain·tensorflow·gpt-3·ai编程
C_心欲无痕2 小时前
vue3 - 响应式数ref与reactive的深度解析
前端·javascript·vue.js
知其然亦知其所以然2 小时前
Redis 命中率 99%,数据库却 100% CPU,是谁在捣鬼
redis·后端·面试
于谦2 小时前
✨ feat(app1,pkg1): monorepo生成规范化提交信息的最优解 - committier
javascript·github·代码规范