在构建 LLM(大语言模型)应用时,LangChain 无疑是目前最强大的编排框架之一。虽然 Python 版本广为人知,但在全栈开发和 Serverless 环境中,LangChain.js (TypeScript) 的生态同样蓬勃发展。

本文将带你拆解 LangChain.js 的三大基石:

  1. Message:与模型沟通的通用语言。
  2. Model:驱动应用的大脑,涵盖同步与流式输出。
  3. LCEL:将组件串联成复杂应用的声明式“胶水”语法。

一、 Message:沟通的基石

在大模型交互中,单纯的字符串(String)已经无法满足复杂的上下文需求。LangChain 抽象出了一套标准的 Message 对象,用于在不同的模型提供商(OpenAI, Anthropic, Gemini 等)之间统一数据格式。

所有消息类型都继承自 BaseMessage,包含 content(内容)和 role(角色)等核心属性。

1. 核心消息类型详解

  • SystemMessage (系统消息):

  • 作用: 设定 AI 的行为基准、角色设定或背景知识。通常作为对话历史的第一条。

  • 示例: new SystemMessage("你是一个精通 TypeScript 的资深架构师。")

  • HumanMessage (人类消息):

  • 作用: 代表来自用户的输入。

  • 示例: new HumanMessage("如何优化 React 的渲染性能?")

  • AIMessage (AI 消息):

  • 作用: 模型生成的回复。通常用于存储对话历史,让模型“记住”它之前说过的话。

  • 示例: new AIMessage("可以使用 React.memo 或 useMemo 进行优化...")

  • ToolMessage (工具消息):

  • 作用: 当模型调用外部工具(Function Calling)后,工具返回的结果需要封装在此消息中传回给模型。包含 tool_call_id 以匹配请求。

  • ChatMessage (通用消息):

  • 作用: 当你需要自定义非标准角色(如 role: "reviewer")时使用。

  • AIMessageChunk (AI 块消息):

  • 作用: 专用于流式传输 (Streaming)。它不仅包含内容片段,还包含 tool_call_chunks 等增量数据。在流式响应拼接时非常关键。

代码示例:构建对话历史

import { SystemMessage, HumanMessage, AIMessage } from "@langchain/core/messages";

const messages = [
  new SystemMessage("你是一个乐于助人的翻译助手。"),
  new HumanMessage("I love programming."),
  new AIMessage("我喜欢编程。"),
  new HumanMessage("What is LangChain?")
];

// 这些消息可以直接传给 ChatModel


二、 Model:不仅是输入输出

在 LangChain 中,ChatModel 是对底层 LLM API 的一层封装。它不仅统一了调用接口(不再需要分别去查 OpenAI 或 Claude 的 API 文档),还提供了丰富的功能。

1. 同步输出 (invoke)

这是最基础的调用方式,等待模型生成完整回复后一次性返回。

import { ChatOpenAI } from "@langchain/openai";
import { HumanMessage } from "@langchain/core/messages";

// 实例化模型
const model = new ChatOpenAI({
  modelName: "gpt-4o",
  temperature: 0.7,
});

// 执行调用
async function runSync() {
  const response = await model.invoke([
    new HumanMessage("给我讲一个关于程序员的冷笑话")
  ]);
  
  // response 是一个 AIMessage 对象
  console.log(response.content); 
}

2. 流式输出 (stream) —— 提升用户体验的关键

在构建 Chatbot 时,等待几秒钟的空白期会让用户感到焦虑。stream 方法允许我们接收一个个 Token(字符片段),实现类似打字机的效果。

async function runStream() {
  const stream = await model.stream([
    new HumanMessage("请写一篇 500 字的科幻小说摘要")
  ]);

  console.log("开始生成...");
  
  // 这里的 chunk 是 AIMessageChunk 类型
  for await (const chunk of stream) {
    // process.stdout.write 可以不换行打印
    process.stdout.write(chunk.content as string);
  }
}

3. 进阶能力:Batch (批量处理)

除了同步和流式,LangChain 的 Model 还支持 batch,可以并发处理多个请求,这在处理大量数据分类或翻译时非常有用。

async function runBatch() {
  const responses = await model.batch([
    [new HumanMessage("你好")],
    [new HumanMessage("再见")]
  ]);
  
  console.log(responses.length); // 2
  console.log(responses[0].content); // "你好!有什么我可以帮你的吗?"
}


三、 LCEL:声明式的魔法

LCEL (LangChain Expression Language) 是 LangChain 框架底层的核心语法。它的核心理念是Composable(可组合性)

在 JavaScript/TypeScript 中,LCEL 主要通过 .pipe() 方法(类似于 Unix 的管道 |)来实现。它让我们可以将 Prompt(提示词)、Model(模型)、OutputParser(输出解析器)等组件像搭积木一样串联起来。

为什么使用 LCEL?

  1. 清晰的逻辑: 输入 -> 模板 -> 模型 -> 解析 -> 输出。
  2. 统一的接口: 每个组件都实现了 Runnable 接口(拥有 invoke, stream, batch 方法)。
  3. 自动并行: LCEL 可以在可能的情况下自动并行执行步骤。

核心组件组合示例

让我们构建一个经典的链:PromptTemplate -> ChatModel -> StringOutputParser

  1. PromptTemplate: 将用户输入转换为完整的 Prompt。
  2. StringOutputParser: 将模型输出的 AIMessage 直接转为纯字符串。
import { ChatOpenAI } from "@langchain/openai";
import { ChatPromptTemplate } from "@langchain/core/prompts";
import { StringOutputParser } from "@langchain/core/output_parsers";

// 1. 定义模型
const model = new ChatOpenAI({ modelName: "gpt-3.5-turbo" });

// 2. 定义提示词模板
const prompt = ChatPromptTemplate.fromMessages([
  ["system", "你是一个专业的{topic}专家。"],
  ["human", "{question}"]
]);

// 3. 定义输出解析器 (从 Message 中提取 content 字符串)
const outputParser = new StringOutputParser();

// 4. 使用 pipe 组合链 (LCEL)
// 数据流向:Input -> prompt -> model -> outputParser -> Output
const chain = prompt.pipe(model).pipe(outputParser);

// 5. 调用链
async function runChain() {
  // invoke 的参数会自动注入到 prompt 的变量中
  const result = await chain.invoke({
    topic: "前端架构",
    question: "什么是微前端?"
  });

  // 此时 result 已经是纯字符串,而非 AIMessage 对象
  console.log(result);
}

LCEL 的强大之处:RunnableMap

LCEL 不仅仅是单链条,还可以通过 RunnableMap (在 JS 中通常通过对象结构或 RunnableSequence 实现) 并行处理数据。

例如,我们需要先检索上下文(Context),再回答问题:

import { RunnableSequence, RunnablePassthrough } from "@langchain/core/runnables";

// 假设我们有一个检索器函数 (模拟 RAG)
const retriever = async (input: string) => "一些相关的背景知识...";

const ragChain = RunnableSequence.from([
  {
    context: async (input: string) => await retriever(input), // 步骤1:获取上下文
    question: new RunnablePassthrough() // 步骤1并行:透传原始问题
  },
  prompt, // 步骤2:结合 context 和 question 填充模板
  model,  // 步骤3:模型推理
  outputParser // 步骤4:解析输出
]);

LangChain 的 LCEL 范式持续演进

如今,通过 model.withStructuredOutput(zodSchema),我们可以更简洁地定义输出结构,而无需手动编写解析逻辑。这并非取代 LCEL,而是 LCEL 在结构化数据场景下的自然延伸——让声明式链路更强大、更类型安全。

// 完整 LCEL 链 + 结构化输出
const movieChain = ChatPromptTemplate.fromTemplate(
  "Extract movie info from the following text:\n{text}"
).pipe(
  model.withStructuredOutput(Movie)
);

const output = await movieChain.invoke({
  text: "Inception came out in 2010 and was directed by Christopher Nolan."
});
// output: { title: "Inception", year: 2010, director: "Christopher Nolan", rating: ... }

总结

  • Message 解决了“怎么跟 AI 说话”的数据结构问题,区分了 System、Human 和 AI 的职责。
  • Model 提供了统一的 API 网关,支持 Invoke(同步)、Stream(流式)和 Batch(批量)三种模式。
  • LCEL 是将上述积木搭建成城堡的粘合剂,通过 .pipe() 实现了代码的高可读性、模块化和易维护性。
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐