LangChainJS Chain链详解第四部分
Chain链本质是管道,将模型、提示词等组件串联成工作流,统一管理数据传递与逻辑编排。通过链式调用可复用子链,避免手动管理“胶水代码”。路由链根据问题类别分发到不同专家prompt,转换链利用Runnable对输入灵活转换。
聊完了消息大模型、prompt 提示词和 Runnable 这些基础概念,我们终于可以进入一个更核心的环节——Chain 链。如果你对前面的内容还不太熟悉,建议先翻一翻之前的文章,因为接下来要讲的东西,正是建立在这些基础之上的。
## 为什么需要Chain链?
说白了,**Chain链的本质,就是管道**。
用过 Shell 命令行的人应该不陌生,`cat file.txt | tr 'a-z' 'A-Z'` 这个命令,就是把左侧命令的标准输出,直接作为右侧命令的标准输入。管道符 `|` 在这里扮演了数据搬运工的角色。LangChain 借鉴了这个经典的设计,并将其命名为 `Chain`(链)。
之所以引入管道特性,就是为了践行 Unix 哲学:**每个组件只做一件事,并且把它做好,然后通过管道将它们组合起来,解决复杂问题**。在 LangChain.js 中,Chain 链正是这一理念的落地——它将模型、提示词模板、工具等异构组件串联成可执行的工作流,并统一管理组件间的数据传递与逻辑编排。开发者可以用声明式、可组合的方式来构建复杂的 AI 应用。
举个例子,这样一个链式调用:
```ja vascript
ChatPrompt.pipe(model).pipe(new StringOutputParser())
```
如果没有 Chain 链,当你使用模型、提示词、工具等组件时,只能手动管理它们之间的数据传递。每一次调用,都需要显式地获取前一步的输出,再作为下一步的输入。这种“胶水代码”在单次调用时尚且可控,可一旦涉及多轮对话或复杂工作流,数据结构就会变得混乱不堪,逻辑纠缠,维护成本急剧攀升。
**Chain 链正是为此而生**。它不仅允许开发者反复复用一个已构建好的工作流,更重要的是,它提供了一种标准化的编排模式——将模型、提示词、工具等异构组件统一纳入同一套接口(`Runnable`)之下,由链本身统一管理组件间的数据流转与执行顺序。开发者只需关心“做什么”,而无需关心“怎么传”。
下面我们不妨直观对比一下,手动调用和 Chain 链调用的区别。
## 手动调用
假设我们要生成一篇文章,如果不使用 Chain 链,代码的执行流程会是什么样?直接看代码:
```ja vascript
// 初始化模型
const model = new ChatOpenAI({ model: "DeepSeek-V4-Pro" });
// 定义提示词模板(依然可以复用)
const outlinePrompt = ChatPromptTemplate.fromTemplate("请为主题 '{topic}' 生成一篇文章的大纲。");
const articlePrompt = ChatPromptTemplate.fromTemplate("根据以下大纲写一篇完整的文章:nn{outline}");
// 1️⃣ 生成大纲
const outlineMessages = await outlinePrompt.formatMessages({ topic: "人工智能的未来" });
const outlineResponse = await model.invoke(outlineMessages);
// 从响应中提取文本内容(相当于 StringOutputParser 的功能)
const outline = outlineResponse.content;
// 2️⃣ 基于大纲生成文章
const articleMessages = await articlePrompt.formatMessages({ outline: outline });
const articleResponse = await model.invoke(articleMessages);
const article = articleResponse.content;
console.log(article);
```
可以看到,如果不使用 Chain 链,开发者需要手动依次调用每个组件,并显式地传递数据,手动管理每一步的输入/输出、顺序和异常。这就像在写流水账,每一步都要亲力亲为。
## Chain链调用
那如果用 Chain 链来写,又会是什么样子?请看下面的代码:
```ja vascript
// 初始化模型
const model = new ChatOpenAI({ model: "DeepSeek-V4-Pro" });
// 大纲提示词
const outlinePrompt = ChatPromptTemplate.fromTemplate("请为主题 '{topic}' 生成一篇文章的大纲。");
const outlineChain = outlinePrompt.pipe(model).pipe(new StringOutputParser()); // 输出字符串
// 文章提示词
const articlePrompt = ChatPromptTemplate.fromTemplate("根据以下大纲写一篇完整的文章:nn{outline}");
const articleChain = articlePrompt.pipe(model).pipe(new StringOutputParser()); // 输出字符串
// chain链拼接组合
const fullChain = outlineChain
// 把上一步的字符串 outline 包成对象,再喂给 articleChain
.pipe(RunnableLambda.from((outline: string) => ({ outline })))
.pipe(articleChain);
const result = await fullChain.invoke({ topic: "人工智能的未来" });
```
这里 `outlineChain` 和 `articleChain` 使用 Chain 链组装,它们是两个等待调用或复用的组件,此时它们还没被调用。而 `fullChain` 是这两个 Chain 组件的组合结果。`outlineChain` 生成大纲并且输出字符串,再将生成的大纲字符串通过 `RunnableLambda` 转换成 `{ outline: string }` 对象传递给 `articleChain`。直到调用 `invoke` 方法,整个流程才开始运行:
```
topic 进入 outlineChain
↓
大纲字符串
↓
RunnableLambda 包成 { outline }
↓
{ outline } 进入 articleChain
↓
最终文章字符串
```
使用 Chain 链构建工作流,其中的每个子链(如 `outlineChain`)都可独立复用,也可灵活拼接,数据流转由链自动管理。这才是 Chain 链设计的精髓所在。
## 路由链
路由这个概念,类比网络路由就很好理解:首先收到一个数据包,看包的目的地址,再根据路由表把包转发到正确的下一跳。“路由”的核心是:**根据输入的某个属性,把请求分发到不同的处理路径**。
在 Chain 链中,路由指的是根据问题类别分发到不同专家 prompt。下面我们来看一个路由链的例子:
```ja vascript
// 使用 withStructuredOutput 进行分类
const classificationSchema = z.object({
category: z.enum(["tech", "cooking", "general"]).describe("问题类别"),
});
const classifier = model.withStructuredOutput(classificationSchema);
// 三个提示词模版
const techPrompt = ChatPromptTemplate.fromTemplate("你是一个技术专家。请回答以下技术问题:n{question}");
const cookingPrompt = ChatPromptTemplate.fromTemplate("你是一个厨师。请回答以下烹饪问题:n{question}");
const generalPrompt = ChatPromptTemplate.fromTemplate("请回答以下问题:n{question}");
const question = "怎么做红烧肉?";
// 分类
const classification = await classifier.invoke(`将以下问题分类为 tech(技术)、cooking(烹饪)或 general(一般):n${question}`);
let selectedPrompt;
// 根据问题分类,加载对应提示词模版
switch (classification.category) {
case "tech":
selectedPrompt = techPrompt;
break;
case "cooking":
selectedPrompt = cookingPrompt;
break;
default:
selectedPrompt = generalPrompt;
}
const answer = await selectedPrompt.pipe(model).pipe(new StringOutputParser()).invoke({ question });
```
如果你只看 switch 语句,那确实就是普通的条件分支,没什么“路由”味道。但“路由链”这个名字来自 LangChain 的设计模式命名——**它强调的是整个流程的模式(分类 → 分发 → 处理),而不是单个 switch 语句**。
换句话说:switch 本身不叫路由,但“先分类再分发到不同链”这种整体结构,就叫路由链。
## 转换链
转换链的本质,是借助 `Runnable` 统一标准,对单个或多个输入数据进行灵活处理,将其转换为下游组件所需的结构。
```ja vascript
const transformInput = RunnableLambda.from((input: { text: string }) => ({
text: input.text,
wordCount: input.text.split(/\s+/).length,
characterCount: input.text.length,
}));
const promptTemplate = ChatPromptTemplate.fromTemplate(`分析以下文本的摘要:
文本: {text}
字数统计: {wordCount} 词,{characterCount} 字
请提供摘要。`);
const chain = transformInput.pipe(promptTemplate).pipe(model).pipe(new StringOutputParser());
const text = `LangChain 是一个用于开发由语言模型驱动的应用程序的框架。它提供了一套丰富的工具和组件,使得构建复杂的 LLM 应用变得更加容易。无论是简单的聊天机器人还是复杂的智能袋里,LangChain 都能帮你实现。`.trim();
const result = await chain.invoke({ text });
```
在这个例子中,`RunnableLambda.from` 将原始的 `text` 字段转换为包含多个字段的对象。得益于 `Runnable` 的统一接口规范,经转换后的数据对象,能够无缝传递给后续的提示词模板(`promptTemplate`)使用,整个流程自然而流畅。
在实际业务场景中,原始数据格式往往是固定的,但前端展示或下游处理逻辑,对数据形态有特定要求。转换链恰好精准地解决了这个问题——它作为数据预处理/后处理的标准化“加工站”,让开发者能够以声明式、可组合的方式,将复杂的数据清洗、格式转换、字段映射等逻辑解耦出来,确保数据在进入核心大模型(LLM)环节之前,已完全就绪。
## 总结
如今,AI 编程工具已经成为日常开发的得力助手。但理解这些底层原理依然有意义:当你看到 AI 生成的代码时,你不仅能看懂它“在做什么”,更能理解它“为什么这样写”。而这,往往才是决定一个开发者水平高低的关键所在。
来源:https://juejin.cn/post/7661502161270112265
游乐网为非赢利性网站,所展示的游戏/软件/文章内容均来自于互联网或第三方用户上传分享,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系youleyoucom@outlook.com。
ESP32自定义唤醒词:TFLite到离线语音唤醒一下午搞定
基于TFLiteMicro和ESP-IDF,在ESP32-S3上实现自定义唤醒词离线语音识别。通过5层检测管线控制误触发,模型可从voicute com在线生成并直接部署。系统推理耗时约360ms,模型体积小于80KB,支持用户自定义唤醒词且无需联网。
AI Agent中Tracker的真正作用解析
Tracker是AIAgent的运行时状态中心,记录当前任务进度、槽位、流程和最近动作,解决上下文丢失与任务恢复问题。它不同于长期记忆和知识库,遵循统一状态源,确保系统各模块状态一致,避免混乱。