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 应用的正确姿势。

相关推荐
ssshooter1 天前
看完就懂 useSyncExternalStore
前端·javascript·react.js
Live000001 天前
在鸿蒙中使用 Repeat 渲染嵌套列表,修改内层列表的一个元素,页面不会更新
前端·javascript·react native
柳杉1 天前
使用Ai从零开发智慧水利态势感知大屏(开源)
前端·javascript·数据可视化
球球pick小樱花1 天前
游戏官网前端工具库:海内外案例解析
前端·javascript·css
喝水的长颈鹿1 天前
【大白话前端 02】网页从解析到绘制的全流程
前端·javascript
用户14536981458781 天前
VersionCheck.js - 让前端版本更新变得简单优雅
前端·javascript
codingWhat1 天前
整理「祖传」代码,就是在开发脚手架?
前端·javascript·node.js
码路飞1 天前
写了个 AI 聊天页面,被 5 种流式格式折腾了一整天 😭
javascript·python
Lee川1 天前
优雅进化的JavaScript:从ES6+新特性看现代前端开发范式
javascript·面试
颜酱1 天前
单调队列:滑动窗口极值问题的最优解(通用模板版)
javascript·后端·算法