TypeScript 开发者必看:LangChain.js 核心概念与实战指南 – wiki基地

TypeScript 开发者必看:LangChain.js 核心概念与实战指南

引言:从 Web 开发到 AI 工程化

在过去的一年里,大语言模型(LLM)彻底改变了软件开发的格局。对于 Python 开发者而言,LangChain 几乎是构建 AI 应用的默认选择。然而,随着全栈开发的演进,JavaScript 和 TypeScript 生态系统在 AI 领域的地位日益重要。LangChain.js 的出现,不仅让前端和全栈开发者能够利用熟悉的工具链构建复杂的 AI 应用,更凭借 TypeScript 强大的类型系统,为不确定的 LLM 输出提供了工程化的确定性保障。

本文将为 TypeScript 开发者提供一份详尽的 LangChain.js 指南。我们将跳过简单的“Hello World”,深入探讨其核心架构、LCEL(LangChain Expression Language)、RAG(检索增强生成)实战以及如何利用 TypeScript 的类型系统治理 AI 的“幻觉”。


第一部分:核心概念与架构设计

LangChain 的本质是一个“粘合剂”和“编排引擎”。它并不直接提供模型能力,而是将 LLM 与外部数据、计算工具以及其他 API 连接起来。对于 TypeScript 开发者来说,理解以下核心组件的接口定义至关重要。

1. Model I/O:不仅仅是 API 调用

在 LangChain 中,模型主要分为两类,它们的输入输出接口有着本质区别:

  • LLMs (纯文本模型): 输入是一个字符串,输出也是一个字符串。这对应于早期的 GPT-3 接口。
  • Chat Models (聊天模型): 输入是一系列消息(Messages),输出是一条消息。这是目前构建应用的主流(如 GPT-4, Claude 3)。

在 TypeScript 中,利用 BaseMessage 及其子类(HumanMessage, AIMessage, SystemMessage)可以清晰地构建上下文结构。

“`typescript
import { ChatOpenAI } from “@langchain/openai”;
import { HumanMessage, SystemMessage } from “@langchain/core/messages”;

const chat = new ChatOpenAI({
modelName: “gpt-4-turbo”,
temperature: 0, // 降低随机性,适合逻辑任务
});

const messages = [
new SystemMessage(“你是一位资深的 TypeScript 技术专家。”),
new HumanMessage(“请解释一下 LangChain 中的 Runnable 接口。”),
];

// 调用模型
const response = await chat.invoke(messages);
console.log(response.content);
“`

2. Output Parsers:赋予 LLM 类型安全

这是 TypeScript 开发者最能感受到优势的地方。LLM 本质上输出的是非结构化文本,但在工程实践中,我们需要 JSON、列表或特定的数据结构。

LangChain.js 提供了强大的解析器,结合 Zod schema,我们可以强制模型输出符合特定类型定义的数据。这对于后续的业务逻辑处理至关重要。

“`typescript
import { z } from “zod”;
import { StructuredOutputParser } from “langchain/output_parsers”;

// 1. 定义期望的 Schema
const parser = StructuredOutputParser.fromZodSchema(
z.object({
sentiment: z.enum([“positive”, “negative”, “neutral”]).describe(“用户评论的情感倾向”),
keywords: z.array(z.string()).describe(“评论中提到的关键技术点”),
action_item: z.boolean().describe(“是否需要后续跟进”),
})
);

// 2. 获取格式化指令
const formatInstructions = parser.getFormatInstructions();

// 3. 将指令注入 Prompt
const prompt = ChatPromptTemplate.fromTemplate(
“分析以下评论。\n{format_instructions}\n\n评论内容: {text}”
);

// … 后续在 Chain 中通过 pipe 连接 parser
“`

3. Prompt Templates:提示工程的代码化

不要将 Prompt 视为简单的字符串拼接。在生产环境中,Prompt 是可版本化、可复用的组件。ChatPromptTemplate 允许我们定义包含 system prompt 和 user prompt 的复杂模板,并自动处理变量插值。


第二部分:LCEL —— 声明式的编排艺术

LangChain 0.2 版本后,全面拥抱了 LCEL (LangChain Expression Language)。这是一种受函数式编程启发的声明式语法,主要通过 pipe 方法将组件串联起来。

对于熟悉 RxJS 或函数式编程的 TS 开发者,LCEL 会显得非常亲切。它的核心在于 Runnable 接口。几乎所有的 LangChain 组件(模型、Prompt、Parser、Retriever)都实现了 Runnable 协议,这意味着它们都拥有 invokestreambatch 等标准方法。

构建第一个 LCEL 链

让我们构建一个完整的处理链路:Prompt -> Model -> OutputParser。

“`typescript
import { StringOutputParser } from “@langchain/core/output_parsers”;
import { ChatPromptTemplate } from “@langchain/core/prompts”;
import { ChatOpenAI } from “@langchain/openai”;

// 1. 初始化组件
const model = new ChatOpenAI({ modelName: “gpt-3.5-turbo” });
const prompt = ChatPromptTemplate.fromMessages([
[“system”, “将以下文本翻译成 {language}。”],
[“human”, “{text}”],
]);
const outputParser = new StringOutputParser();

// 2. 使用 Pipe 构建链
// 类型推断会自动流动:Input -> PromptValue -> ChatMessage -> String
const chain = prompt.pipe(model).pipe(outputParser);

// 3. 执行
const result = await chain.invoke({
language: “TypeScript”, // 这里的示例稍微有点幽默,实际应为自然语言
text: “Hello, World!”,
});
“`

LCEL 的优势:

  1. 流式支持(Streaming): 只要链中的每个环节都支持流式传输,整个链就自动支持流式输出,无需额外修改代码。这对于提升 AI 应用的用户体验(Time To First Token)至关重要。
  2. 并行执行: 使用 RunnableMap 可以轻松并行执行多个独立的链。
  3. 可观测性: 配合 LangSmith,可以清晰地看到链式调用的每一步输入输出。

第三部分:实战 RAG —— 建立私有知识库

检索增强生成(RAG)是目前解决 LLM 知识截止和私有数据访问最成熟的方案。LangChain.js 提供了完整的 RAG 管道。

1. Document Loaders (文档加载)

LangChain 社区提供了大量的加载器,支持 PDF, Notion, GitHub, JSON 等。

“`typescript
import { PDFLoader } from “langchain/document_loaders/fs/pdf”;

const loader = new PDFLoader(“path/to/manual.pdf”);
const docs = await loader.load();
“`

2. Text Splitters (文本分割)

将长文档分割成小块(Chunks)是 RAG 的关键。切分过大可能超出 Context Window,切分过小可能丢失语义上下文。RecursiveCharacterTextSplitter 是最常用的分割器,它会尝试按段落、句子、单词的顺序递归分割,尽可能保持语义完整。

“`typescript
import { RecursiveCharacterTextSplitter } from “langchain/text_splitter”;

const splitter = new RecursiveCharacterTextSplitter({
chunkSize: 1000,
chunkOverlap: 200, // 重叠部分很重要,防止上下文在切分处断裂
});

const splitDocs = await splitter.splitDocuments(docs);
“`

3. Embeddings & Vector Stores (向量化与存储)

我们需要将文本块转换为向量(Embeddings),并存储在向量数据库中。对于 Node.js 环境,我们可以使用 Faiss (本地) 或 Pinecone/Supabase (云端)。

“`typescript
import { OpenAIEmbeddings } from “@langchain/openai”;
import { MemoryVectorStore } from “langchain/vectorstores/memory”;

// 初始化 Embeddings 模型
const embeddings = new OpenAIEmbeddings();

// 创建向量存储(生产环境建议使用 Pinecone 或 PgVector)
const vectorStore = await MemoryVectorStore.fromDocuments(
splitDocs,
embeddings
);

// 创建检索器
const retriever = vectorStore.asRetriever({
k: 3, // 每次检索前 3 个最相关的片段
searchType: “similarity”, // 或 “mmr” (最大边际相关性,用于增加多样性)
});
“`

4. 组装 RAG Chain

这里我们展示如何使用 LCEL 组装一个标准的 RAG 链。注意 RunnableSequence 的使用。

“`typescript
import { RunnableSequence, RunnablePassthrough } from “@langchain/core/runnables”;
import { StringOutputParser } from “@langchain/core/output_parsers”;

// 定义 RAG Prompt
const template = `基于以下上下文回答问题:
{context}

问题: {question}
`;
const prompt = ChatPromptTemplate.fromTemplate(template);

// 构建 Chain
const ragChain = RunnableSequence.from([
{
context: retriever.pipe((docs) => docs.map(d => d.pageContent).join(“\n\n”)),
question: new RunnablePassthrough(), // 透传用户输入的问题
},
prompt,
model,
new StringOutputParser(),
]);

// 执行
const answer = await ragChain.invoke(“文档中提到的核心架构是什么?”);
“`


第四部分:Agents —— 让 AI 具备行动能力

如果是 RAG 是给 AI 装上了“外挂硬盘”,那么 Agents(智能体)就是给 AI 装上了“双手”。Agents 允许 LLM 决定调用哪些工具(Tools)以及调用的顺序。

在 TypeScript 中,利用 OpenAI 的 Function Calling 功能构建 Agent 是最高效的。

定义强类型的工具

LangChain.js 引入了 DynamicStructuredTool,它完美结合了 Zod,确保 LLM 传给工具的参数符合 TS 类型定义。

“`typescript
import { DynamicStructuredTool } from “@langchain/core/tools”;
import { z } from “zod”;

const calculatorTool = new DynamicStructuredTool({
name: “calculator”,
description: “用于执行数学计算”,
schema: z.object({
operation: z.enum([“add”, “subtract”, “multiply”, “divide”]),
number1: z.number(),
number2: z.number(),
}),
func: async ({ operation, number1, number2 }) => {
// 实际的计算逻辑
switch (operation) {
case “add”: return (number1 + number2).toString();
// … handle others
default: return “Error”;
}
},
});
“`

创建 Agent Executor

Agent Executor 是 Agent 的运行时环境,它负责循环执行:思考 -> 调用工具 -> 获取结果 -> 再次思考,直到任务完成。

“`typescript
import { AgentExecutor, createOpenAIToolsAgent } from “langchain/agents”;
import { Pull } from “langchain/hub”; // 可以从 LangChain Hub 拉取标准 Prompt

const tools = [calculatorTool];

// 获取标准的 ReAct Prompt
// 也可以自己定义,但必须包含 {agent_scratchpad} 变量用于存储中间思考过程
const prompt = await pull(“hwchase17/openai-tools-agent”);

const agent = await createOpenAIToolsAgent({
llm: model,
tools,
prompt,
});

const agentExecutor = new AgentExecutor({
agent,
tools,
verbose: true, // 开发时开启,可以看到详细的推理步骤
});

await agentExecutor.invoke({
input: “如果我有 100 个苹果,吃掉了 20 个,剩下的数量乘以 5 是多少?”,
});
“`


第五部分:进阶技巧与最佳实践

1. 记忆管理 (Memory)

LLM 本质是无状态的。要构建聊天机器人,必须手动管理历史记录。LangChain 提供了多种 Memory 组件,但在 LCEL 中,我们通常显式地管理 History。

推荐使用 RunnableWithMessageHistory。它可以将任何 Chain 包装起来,并负责自动从数据库(如 Redis, Upstash)加载和保存历史消息。

“`typescript
import { RunnableWithMessageHistory } from “@langchain/core/runnables”;
import { ChatMessageHistory } from “langchain/stores/message/in_memory”;

const chainWithHistory = new RunnableWithMessageHistory({
runnable: ragChain,
getMessageHistory: (sessionId) => new ChatMessageHistory(), // 实际应连接数据库
inputMessagesKey: “question”,
historyMessagesKey: “history”,
});
“`

2. 流式响应 (Streaming)

在 Next.js 或 Express 中,流式响应对于 AI 应用至关重要。LangChain.js 对 Web Streams API 有着原生支持。

“`typescript
// 在 Next.js API Route 中
export async function POST(req: Request) {
const { messages } = await req.json();

// 使用 LCEL 的 stream 方法
const stream = await chain.stream({
input: messages[messages.length – 1].content
});

// 使用 AI SDK 或 LangChain 的 HttpResponseOutputParser 转换为 Response
return new Response(stream);
}
“`

3. 错误处理与重试

LLM 的调用偶尔会因为网络或限流而失败。LangChain 的 Runnable 允许配置重试策略:

typescript
const modelWithRetry = model.withRetry({
stopAfterAttempt: 3,
onFailedAttempt: (error) => {
console.log(`Attempt failed: ${error.message}`);
},
});

4. 调试工具:LangSmith

开发复杂的 Chain 或 Agent 时,单纯靠 console.log 很难调试。LangSmith 是 LangChain 团队推出的全链路追踪平台。只需配置环境变量,即可在云端看到每一次调用的延迟、Token 消耗、输入输出以及 Prompt 的渲染结果。

typescript
// .env
// LANGCHAIN_TRACING_V2=true
// LANGCHAIN_API_KEY=ls__...


第六部分:LangChain.js vs. 其他选择

作为 TypeScript 开发者,你可能会问:“为什么不直接使用 OpenAI SDK 或者 Vercel AI SDK?”

这是一个非常好的问题。

  • OpenAI SDK: 提供了最底层的 API 访问。如果你只需要简单的聊天补全,且不涉及复杂的上下文管理或工具调用,直接用它是最轻量的。
  • Vercel AI SDK: 专注于 UI 层 的流式集成(hooks 如 useChat, useCompletion)。它实际上可以与 LangChain 完美配合:LangChain 处理后端的逻辑编排(RAG、Agent),Vercel AI SDK 处理前端的数据流和 UI 更新。
  • LangChain.js: 它的核心价值在于 “复杂性管理”“模型无关性”
    • 模型切换成本低: 今天用 GPT-4,明天为了省钱想换成 Claude 3 或者本地的 Llama 3,LangChain 只需要改一行配置,而无需重写 Prompt 逻辑和数据处理代码。
    • 标准化流程: 它将 RAG、Memory、Output Parsing 等非标准化的操作标准化了。

结语

LangChain.js 不仅仅是一个库,它是 AI 应用开发的 基础设施框架。对于 TypeScript 开发者而言,掌握 LangChain.js 意味着你拥有了将传统 Web 开发技能迁移到 AI 领域的桥梁。

从类型安全的输出解析,到基于 LCEL 的函数式编排,再到复杂的 Agent 系统,LangChain.js 正在让 AI 开发变得更加确定、可控和工程化。随着 LangGraph.js(用于构建有状态、多角色的复杂 Agent 图)的推出,这个生态系统正在变得愈发强大。

现在的关键不在于是否使用 AI,而在于如何以工程化的手段,驯服 AI 的不确定性,构建出真正产生业务价值的应用。希望这篇指南能成为你 AI 征途上的有力参考。

发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注

滚动至顶部