AI 应用工程化实战:使用 LangChain.js 编排 DeepSeek 复杂工作流
在 2024 年至 2025 年的技术浪潮中,大语言模型(LLM)的应用开发已经从“尝鲜”阶段迈向了“工程化”阶段。对于开发者而言,仅仅调用 fetch 接口获取模型回复是远远不够的。在构建复杂的生产级应用时,我们面临着提示词管理混乱、模型切换成本高、上下文处理复杂以及任务编排困难等诸多痛点。
LangChain 的出现,正是为了解决这些工程化难题。它不是一个模型,而是一个框架,旨在将 LLM 的能力封装成可维护、可复用的组件。
本文将通过四个循序渐进的代码示例,演示如何利用 LangChain.js 结合当下热门的 DeepSeek(深度求索)模型,完成从基础调用到复杂工作流编排的进阶之路。
第一阶段:标准化的开始——适配器模式的应用
在没有任何框架之前,调用 LLM 通常意味着处理各种非标准化的 HTTP 请求。OpenAI、DeepSeek、Claude 的 API 格式各不相同。LangChain 的第一个核心价值在于标准化。
以下是基于 main.js 的基础调用示例:
JavaScript
// main.js
import 'dotenv/config'; // 加载环境变量
import { ChatDeepSeek } from '@langchain/deepseek';
// 1. 实例化模型
const model = new ChatDeepSeek({
model: 'deepseek-reasoner', // 使用 DeepSeek 的推理模型
temperature: 0, // 设定温度,0 代表最确定性的输出
// apiKey 自动从 process.env.DEEPSEEK_API_KEY 读取
});
// 2. 执行调用
const res = await model.invoke('用一句话解释什么是RAG?');
console.log(res.content);
深度解析:适配器模式 (Adapter Pattern)
这段代码看似简单,却蕴含了 AI 工程化的第一块基石:适配器模式。
在软件工程中,适配器模式用于屏蔽底层接口的差异。ChatDeepSeek 类就是一个适配器(Provider)。
- 统一接口:无论底层使用的是 DeepSeek、OpenAI 还是 Google Gemini,在 LangChain 中我们都统一调用 .invoke() 方法,invoke(英文:调用)。
- 配置解耦:开发者无需关心 baseURL 配置、鉴权头部的拼接或请求体格式。
- 参数控制:temperature: 0 是一个关键参数。在开发代码生成或逻辑推理(如使用 deepseek-reasoner)应用时,我们将温度设为 0 以减少随机性;而在创意写作场景,通常设为 0.7 或更高,这是决定你的大模型输出的内容严谨还是天马行空的关键因素之一。
通过这种方式,我们实现了业务逻辑与模型实现的解耦。如果未来需要更换模型,只需修改实例化部分,业务代码无需变动。
第二阶段:提示词工程化——数据与逻辑分离
直接在 .invoke() 中传入字符串(Hardcoding)在 Demo 阶段可行,但在实际项目中是反模式。因为提示词(Prompt)往往包含静态的指令和动态的用户输入。
下面这段代码展示了如何使用 PromptTemplate(对prompt设计一个模板,只需要提供关键的参数) 进行管理:
JavaScript
// 1.js
import { PromptTemplate } from '@langchain/core/prompts';
import { ChatDeepSeek } from '@langchain/deepseek';
// 1. 定义模板:静态结构与动态变量分离
const prompt = PromptTemplate.fromTemplate(`
你是一个{role}。
请用不超过{limit}字回答以下问题:
{question}
`);
// 2. 格式化:注入数据
const promptStr = await prompt.format({
role: '前端面试官',
limit: '50',
question: '什么是闭包'
});
// 3. 调用模型
const model = new ChatDeepSeek({
model: 'deepseek-reasoner',
temperature: 0.7
});
const res = await model.invoke(promptStr);
console.log(res.content);
深度解析:提示词模板的意义
这里体现了关注点分离(Separation of Concerns)的设计原则。
- 复用性:同一个 prompt 对象可以生成“前端面试官”、“后端面试官”甚至“测试工程师”的问答场景,只需改变 format 的入参。
- 维护性:当需要优化 Prompt(例如增加“请使用中文回答”的系统指令)时,只需修改模板定义,而不用在代码库的各个角落查找字符串拼接逻辑。
- 类型安全:虽然 JavaScript 是弱类型,但在 LangChain 的 TypeScript 定义中,模板的输入变量(Variables)是可以被静态分析和校验的。
然而,上述代码仍显得有些“命令式”:我们需要手动格式化,拿到字符串,再手动传给模型。这依然是两步操作。
第三阶段:链式流转——LCEL 与声明式编程
LangChain 的核心精髓在于 Chain(链) 。通过 LangChain 表达式语言(LCEL),我们可以通过管道(Pipe)将组件连接起来,形成自动化的工作流。
下面的这段代码展示了这一范式转变:
JavaScript
// 2.js
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}
`);
// 核心变化:构建 Chain
// prompt (模板节点) -> model (LLM 节点)
const chain = prompt.pipe(model);
// 执行 Chain
const response = await chain.invoke({
topic: '闭包'
});
console.log(response.content);
深度解析:LCEL 与声明式编程
这段代码引入了 .pipe() 方法,它深受 Unix 管道思想的影响。
-
声明式编程 (Declarative) :
我们不再编写“如何做”(先格式化,再调用),而是定义“是什么”(链条是 Prompt 流向 Model)。LangChain 运行时会自动处理数据的传递。 -
Runnable 接口:
在 LangChain 中,Prompt、Model、OutputParser 甚至整个 Chain 都实现了 Runnable 接口。这意味着它们具有统一的调用方式(invoke, stream, batch)。 -
自动化数据流:
当我们调用 chain.invoke({ topic: '闭包' }) 时,对象 { topic: '闭包' } 首先进入 Prompt,Prompt 将其转化为完整的提示词字符串,然后该字符串自动流入 Model,最终输出结果。
这是构建 Agent(智能体)的基础单元。
第四阶段:编排复杂工作流——任务拆解与序列化
在真实业务中,单一的 Prompt 往往难以完美解决复杂问题。例如,我们希望 AI 既能“详细解释原理”,又能“精简总结要点”。如果试图在一个 Prompt 中完成,模型往往会顾此失彼。
更好的工程化思路是任务拆解。下面的这段代码展示了如何使用 RunnableSequence 串联多个任务:
JavaScript
// 3.js
import { ChatDeepSeek } from '@langchain/deepseek';
import { PromptTemplate } from '@langchain/core/prompts';
import { RunnableSequence } from '@langchain/core/runnables';
const model = new ChatDeepSeek({
model: 'deepseek-reasoner',
temperature: 0.7
});
// 任务 A:详细解释
const explainPrompt = PromptTemplate.fromTemplate(`
你是一个前端专家,请详细介绍以下概念: {topic}
要求:覆盖定义、原理、使用方式,不超过300字。
`);
// 任务 B:总结核心点
const summaryPrompt = PromptTemplate.fromTemplate(`
请将以下前端概念总结为3个核心要点 (每点不超过20字):
{explanation}
`);
// 创建两个独立的子链
const explainChain = explainPrompt.pipe(model);
const summaryChain = summaryPrompt.pipe(model);
// 核心逻辑:编排序列
const fullChain = RunnableSequence.from([
// 第一步:输入 topic -> 获取详细解释 text
(input) => explainChain.invoke({ topic: input.topic }).then(res => res.content),
// 第二步:接收 explanation -> 生成总结 -> 组合最终结果
(explanation) => summaryChain.invoke({ explanation }).then(res =>
`知识点详情:\n${explanation}\n\n精简总结:\n${res.content}`
)
]);
const response = await fullChain.invoke({
topic: '闭包'
});
console.log(response);
深度解析:序列化工作流
这是一个典型的 Sequential Chain(顺序链) 模式。
-
输入/输出对齐:
第一步的输出(详细解释)通过函数传递,直接成为了第二步的输入变量 { explanation }。这种数据流的自动衔接是复杂 AI 应用的关键。 -
DeepSeek Reasoner 的优势:
在这个场景中,我们使用了 deepseek-reasoner。对于解释原理和归纳总结这类需要逻辑分析(Reasoning)的任务,DeepSeek 的 R1 系列模型表现优异。通过拆解任务,我们让模型在每个步骤都专注于单一目标,从而大幅提升了输出质量。 -
可观测性与调试:
将长任务拆分为短链,使得我们在调试时可以单独检查 explainChain 的输出是否准确,而不必在一个巨大的黑盒 Prompt 中盲目尝试。
总结
到此为止我们见证了 AI 代码从“脚本”到“工程”的进化:
- 适配器模式:解决了模型接口碎片化问题。
- 提示词模板:实现了数据与逻辑的分离。
- LCEL 管道:将原子能力组装成自动化流程。
- 序列化编排:通过任务拆解解决复杂业务逻辑。
- **要想拿到大模型输出的结果,别忘了配置APIKEY和环境变量
LangChain.js 结合 DeepSeek,不仅仅是调用了一个 API,更是为您提供了一套构建可扩展、可维护 AI 系统的脚手架。作为前端开发者,掌握这种“搭积木”的思维方式,是在 AI 时代保持竞争力的关键。