别再只会调用API了!LangChainJS让大模型应用开发效率提升10倍的秘密
为什么每个前端工程师都应该了解LangChainJS?
当你看到这句话时,可能会有疑问:市面上已经有那么多教程教我们如何调用ChatGPT API,为什么还要专门学习一个JavaScript框架?这篇文章的标题是不是又在”标题党”?
不,这篇文章要告诉你的,是一套截然不同的开发范式。
想象一下这个场景:你的产品经理兴奋地说,”我们需要一个能理解PDF文档并回答用户问题的智能助手”。如果用传统方式,你需要:
- 手动解析PDF文本
- 实现语义搜索匹配
- 编写Prompt模板
- 处理上下文管理
- 拼接API调用逻辑
- 写一堆样板代码
用LangChainJS?这些都有现成的抽象。这不是在简化代码,而是在重新定义”快速开发”的标准。
一、项目概述与核心价值
1.1 LangChainJS是什么
LangChainJS是LangChain框架的JavaScript/TypeScript实现。LangChain最初诞生于Python社区,旨在简化大语言模型应用的开发流程。当开发者们意识到Node.js生态在前端工程化、API服务、实时应用等方面的优势后,LangChainJS应运而生。
这个项目的核心理念是:将LLM应用开发中的常见模式抽象成可组合的组件,让开发者能够像搭积木一样构建复杂的AI应用。
关键数据一览:
- GitHub Stars:超过25,000+
- 周下载量:100万+次
- 支持的LLM提供商:30+
- 活跃维护团队:LangChain官方
- TypeScript覆盖率:95%+
1.2 为什么选择LangChainJS
技术栈统一的优势
现代Web开发中,Node.js已经无处不在。如果你正在构建一个Next.js应用、一个Express后端服务,或者一个Electron桌面应用,使用LangChainJS意味着你可以:
前端/后端代码 → TypeScript/JavaScript → LangChainJS → LLM API
不需要切换语言环境,不需要维护两套代码库,不需要处理Python和JavaScript之间的序列化问题。
TypeScript带来的开发体验
TypeScript的静态类型检查让复杂的AI流水线调试变得更加可控。当你构建一个涉及多个LLM调用、向量检索、工具调用的复杂Agent时,类型提示就是你的地图。
生态系统的丰富性
LangChainJS继承了LangChain生态系统的丰富组件库:
- 文档加载器:PDF、Markdown、网页、Notion、Discord…
- 文本分割器:按字符数、token数、语义边界…
- 向量存储:Pinecone、Chroma、FAISS、MongoDB Atlas…
- LLM集成:OpenAI、Anthropic、Google、Azure、Cohere…
- 工具与Agent:搜索引擎、代码执行、数学计算…
企业级应用的支持
Vercel、AWS、Google Cloud等主流云平台都在积极拥抱LangChainJS。这意味着当你需要将AI应用部署到生产环境时,可以获得良好的基础设施支持。
二、环境搭建:从零开始的完整指南
2.1 前置要求
在开始之前,确保你的开发环境满足以下条件:
Node.js版本要求
Node.js ≥ 18.0.0
npm ≥ 9.0.0 或 yarn ≥ 1.22.0 或 pnpm ≥ 8.0.0
建议使用Node.js 20.x或更高版本,以获得更好的性能和最新的语言特性。
包管理器的选择
虽然npm是Node.js的默认包管理器,但在大型项目中,我推荐使用pnpm:
- 更快的安装速度
- 更节省磁盘空间
- 更好的依赖管理
2.2 项目初始化
让我们创建一个完整的LangChainJS项目:
步骤一:创建项目目录
mkdir my-langchain-app
cd my-langchain-app
步骤二:初始化npm项目
npm init -y
这会创建一个基础的package.json文件。
步骤三:安装LangChainJS核心包
npm install langchain
这是一个”All-in-One”包,包含了LangChainJS的大部分核心功能。对于生产环境,这是最推荐的方式。
步骤四:安装LLM提供商包
以OpenAI为例:
npm install @langchain/openai
LangChainJS将各个LLM提供商的SDK封装成独立的包,这样的设计让项目更轻量,同时便于按需安装。
步骤五:安装额外的依赖(可选但推荐)
# 向量数据库支持
npm install @langchain/community
# 环境变量管理
npm install dotenv
# 文档解析
npm install pdf-parse cheerio
步骤六:配置TypeScript(可选但强烈推荐)
npm install -D typescript @types/node ts-node
npx tsc --init
创建一个基础的tsconfig.json:
{
"compilerOptions": {
"target": "ES2020",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"lib": ["ES2020"],
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"include": ["src/**/*"],
"exclude": ["node_modules"]
}
步骤七:创建项目结构
my-langchain-app/
├── src/
│ ├── index.ts # 入口文件
│ ├── chains/ # Chain相关代码
│ ├── agents/ # Agent相关代码
│ ├── prompts/ # Prompt模板
│ └── utils/ # 工具函数
├── tests/ # 测试文件
├── .env # 环境变量
├── package.json
├── tsconfig.json
└── README.md
步骤八:配置环境变量
创建一个.env文件:
# OpenAI配置
OPENAI_API_KEY=sk-your-api-key-here
# 其他提供商(如需要)
ANTHROPIC_API_KEY=sk-ant-your-key-here
重要提醒: 永远不要将.env文件提交到版本控制系统!在.gitignore中添加:
.env
node_modules/
dist/
2.3 验证安装
创建一个简单的测试文件来验证安装是否成功:
// src/test-install.ts
import { OpenAI } from "@langchain/openai";
async function testInstallation() {
const model = new OpenAI({
modelName: "gpt-3.5-turbo",
openAIApiKey: process.env.OPENAI_API_KEY,
temperature: 0.7,
});
const response = await model.invoke("用一句话介绍LangChainJS");
console.log("测试结果:", response);
}
testInstallation().catch(console.error);
运行测试:
npx ts-node src/test-install.ts
如果一切正常,你应该能看到模型返回的文本。
三、核心概念详解
3.1 LLMs与Chat Models
LangChainJS区分两种类型的模型抽象:
LLMs(大型语言模型)
这是对纯文本补全模型的抽象,典型代表是GPT-3的text-davinci系列。输入是文本,输出也是文本。
import { OpenAI } from "@langchain/openai";
const llm = new OpenAI({
modelName: "gpt-3.5-turbo-instruct",
temperature: 0.9,
maxTokens: 100,
});
// 同步调用风格
const result = await llm.invoke("给我讲一个关于程序员的笑话");
Chat Models(聊天模型)
这是对对话模型的抽象,典型代表是GPT-3.5-turbo和GPT-4。输入是消息列表,输出是单条消息。
import { ChatOpenAI } from "@langchain/openai";
import { HumanMessage, SystemMessage, AIMessage } from "@langchain/core/messages";
const chatModel = new ChatOpenAI({
modelName: "gpt-3.5-turbo",
temperature: 0.8,
maxTokens: 500,
});
// 构建对话上下文
const messages = [
new SystemMessage("你是一个幽默的AI助手"),
new HumanMessage("你好,请介绍一下你自己"),
];
const response = await chatModel.invoke(messages);
console.log(response.content); // AI的回复
两者的核心区别
| 特性 | LLMs | Chat Models |
|---|---|---|
| 输入格式 | 字符串 | 消息数组 |
| 输出格式 | 字符串 | 消息对象 |
| 上下文处理 | 需手动管理 | 原生支持 |
| 适用场景 | 文本生成任务 | 对话系统 |
3.2 Prompts与Prompt Templates
Prompt工程是LLM应用的核心。LangChainJS提供了强大的Prompt模板功能。
基础Prompt模板
import { PromptTemplate } from "@langchain/core/prompts";
const template = "请将以下文本翻译成{targetLanguage}:{text}";
const promptTemplate = PromptTemplate.fromTemplate(template);
// 使用模板
const formattedPrompt = await promptTemplate.format({
targetLanguage: "日语",
text: "今天天气真好",
});
console.log(formattedPrompt);
// 输出: 请将以下文本翻译成日语:今天天气真好
带示例的Prompt模板
Few-shot learning能显著提升模型表现:
import { FewShotPromptTemplate } from "@langchain/core/prompts";
// 定义示例
const examples = [
{ input: "开心", output: "happy" },
{ input: "悲伤", output: "sad" },
{ input: "愤怒", output: "angry" },
];
const exampleTemplate = "中文: {input} → 英文: {output}";
const examplePrompt = PromptTemplate.fromTemplate(exampleTemplate);
// 构建few-shot提示
const fewShotPrompt = new FewShotPromptTemplate({
examples: examples,
examplePrompt: examplePrompt,
prefix: "将以下中文情感词翻译成英文:",
suffix: "中文: {word} → 英文:",
inputVariables: ["word"],
exampleSeparator: "\n",
templateFormat: "f-string",
});
const result = await fewShotPrompt.format({ word: "惊讶" });
console.log(result);
3.3 Chains(链)
Chain是LangChain的核心概念,它将多个组件串联起来形成完整的处理流程。
LLMChain:最简单的链
import { OpenAI } from "@langchain/openai";
import { PromptTemplate } from "@langchain/core/prompts";
import { LLMChain } from "langchain/chains";
// 定义Prompt模板
const template = "请为{product}写一句吸引人的广告词,最多15个字";
const prompt = PromptTemplate.fromTemplate(template);
// 创建LLM实例
const llm = new OpenAI({
modelName: "gpt-3.5-turbo",
temperature: 0.9,
});
// 创建链
const chain = new LLMChain({ llm, prompt });
// 调用链
const result = await chain.invoke({ product: "智能手表" });
console.log(result.text);
SequentialChain:顺序执行的链
当需要多个LLM依次处理数据时使用:
import { SequentialChain, LLMChain } from "langchain/chains";
import { OpenAI } from "@langchain/openai";
import { PromptTemplate } from "@langchain/core/prompts";
// Chain 1: 生成故事大纲
const outlineTemplate = "为{genre}类型的小说写一个三段式大纲";
const outlinePrompt = PromptTemplate.fromTemplate(outlineTemplate);
const outlineChain = new LLMChain({
llm: new OpenAI({ temperature: 0.8 }),
prompt: outlinePrompt,
outputKey: "outline", // 输出结果的键名
});
// Chain 2: 扩展故事
const storyTemplate = "基于以下大纲,写一个500字的故事:\n{outline}";
const storyPrompt = PromptTemplate.fromTemplate(storyTemplate);
const storyChain = new LLMChain({
llm: new OpenAI({ temperature: 0.7 }),
prompt: storyPrompt,
outputKey: "story",
});
// 组合成顺序链
const sequentialChain = new SequentialChain({
chains: [outlineChain, storyChain],
inputVariables: ["genre"],
outputVariables: ["outline", "story"],
verbose: true,
});
// 执行
const result = await sequentialChain.invoke({ genre: "科幻" });
console.log("故事大纲:", result.outline);
console.log("\n完整故事:\n", result.story);
RouterChain:智能路由
根据输入内容动态选择不同的处理路径:
import { RouterChain } from "langchain/chains";
import { OpenAI } from "@langchain/openai";
import { PromptTemplate } from "@langchain/core/prompts";
// 定义路由逻辑
const routerTemplate = `根据用户的问题,选择最合适的处理方式。
问题: {input}
选项:
1. math - 数学计算相关
2. code - 编程代码相关
3. general - 日常问答
只输出选项名称,不要其他内容。`;
const routerPrompt = PromptTemplate.fromTemplate(routerTemplate);
const routerChain = new LLMChain({
llm: new OpenAI({ temperature: 0 }),
prompt: routerPrompt,
});
// 根据路由选择对应的处理链
async function routeRequest(input: string): Promise<string> {
const route = await routerChain.invoke({ input });
if (route.text.includes("math")) {
return "将问题分解为数学步骤并计算";
} else if (route.text.includes("code")) {
return "用代码解释器处理这个问题";
} else {
return "用日常语言回答这个问题";
}
}
3.4 Memory(记忆)
Memory组件让Chain能够”记住”之前的对话内容,这对于构建聊天机器人至关重要。
对话缓冲记忆
最简单的记忆形式,只保留最近的几轮对话:
import { BufferMemory } from "langchain/memory";
import { ChatOpenAI } from "@langchain/openai";
import { ConversationChain } from "langchain/chains";
const memory = new BufferMemory({
memoryKey: "history", // 存储历史记录的键名
returnMessages: true, // 返回消息对象而非字符串
});
const chat = new ChatOpenAI({
modelName: "gpt-3.5-turbo",
temperature: 0.7,
});
const conversationChain = new ConversationChain({
llm: chat,
memory: memory,
verbose: true,
});
// 多轮对话
await conversationChain.invoke({ input: "我叫小明,18岁" });
await conversationChain.invoke({ input: "我叫什么名字?" }); // 应该记住"小明"
await conversationChain.invoke({ input: "我今年多大了?" }); // 应该记住"18岁"
缓冲窗口记忆
只保留最近N轮对话,节省token:
import { BufferWindowMemory } from "langchain/memory";
const windowMemory = new BufferWindowMemory({
k: 5, // 只保留最近5轮对话
memoryKey: "chat_history",
});
实体记忆
专注于记住对话中的实体信息:
import { EntityMemory } from "langchain/memory";
const entityMemory = new EntityMemory({
llm: chat,
sessionId: "user-123",
maxEntities: 10, // 最多记住10个实体
});
3.5 Agents(智能体)
Agent是LangChainJS中最强大的功能之一。它不仅仅是执行预设的流程,而是让LLM能够”思考”并决定下一步行动。
Agent的核心工作机制
用户输入 → LLM思考 → 选择工具 → 执行工具 → 获取结果 → LLM再思考 → ...
创建一个基础Agent
import { OpenAI } from "@langchain/openai";
import { initializeAgentExecutorWithOptions } from "langchain/agents";
import { SerpAPI } from "langchain/tools";
import { Calculator } from "langchain/tools/calculator";
// 初始化LLM
const model = new OpenAI({
modelName: "gpt-3.5-turbo",
temperature: 0,
verbose: true,
});
// 定义工具
const tools = [
new SerpAPI(process.env.SERPAPI_API_KEY, {
location: "Beijing,China",
hl: "zh-cn",
}),
new Calculator(),
];
// 创建Agent
const executor = await initializeAgentExecutorWithOptions(tools, model, {
agentType: "zero-shot-react-description",
verbose: true,
});
// 使用Agent
const result = await executor.invoke({
input: "查找2024年诺贝尔物理学奖得主,并计算他们的平均年龄",
});
console.log(result.output);
AgentType详解
LangChainJS提供了多种Agent类型:
| AgentType | 特点 | 适用场景 |
|---|---|---|
| zero-shot-react-description | 基于工具描述自动选择 | 通用问题 |
| react-docstore | 结合搜索和推理 | 需要查资料的复杂问题 |
| self-ask-with-search | 追问式思考 | 需要逐步推理的问题 |
| conversational-conversational | 对话式交互 | 聊天机器人 |
3.6 Document Loaders与Text Splitters
处理外部文档是LLM应用的常见需求。
文档加载器
import { PDFLoader } from "langchain/document_loaders/fs/pdf";
import { TextLoader } from "langchain/document_loaders/fs/text";
import { CSVLoader } from "langchain/document_loaders/fs/csv";
// 加载PDF
const pdfLoader = new PDFLoader("./document.pdf");
const pdfDocs = await pdfLoader.load();
// 加载文本文件
const textLoader = new TextLoader("./article.txt");
const textDocs = await textLoader.load();
// 加载CSV
const csvLoader = new CSVLoader("./data.csv");
const csvDocs = await csvLoader.load();
console.log(`加载了 ${pdfDocs.length} 页PDF`);
console.log(`第一页内容预览: ${pdfDocs[0].pageContent.substring(0, 200)}`);
文本分割器
import { RecursiveCharacterTextSplitter } from "langchain/text splitter";
const textSplitter = new RecursiveCharacterTextSplitter({
chunkSize: 1000, // 每块的最大字符数
chunkOverlap: 200, // 块之间的重叠字符数
separators: ["\n\n", "\n", " ", ""], // 分割优先级
});
// 分割文档
const chunks = await textSplitter.splitDocuments(pdfDocs);
console.log(`分割成 ${chunks.length} 个文本块`);
chunks.forEach((chunk, index) => {
console.log(`块 ${index + 1}: ${chunk.pageContent.length} 字符`);
});
3.7 Vector Stores与嵌入
向量存储是实现语义搜索的基础。
创建向量存储
import { OpenAIEmbeddings } from "@langchain/openai";
import { HNSWLib } from "langchain/vectorstores/hnswlib";
// 初始化嵌入模型
const embeddings = new OpenAIEmbeddings({
openAIApiKey: process.env.OPENAI_API_KEY,
});
// 从文档创建向量存储
const vectorStore = await HNSWLib.fromDocuments(
chunks, // 之前分割的文本块
embeddings // 嵌入模型
);
// 保存到本地(可选)
await vectorStore.save("./vector_store");
语义搜索
// 相似性搜索
const results = await vectorStore.similaritySearch(
"关于人工智能的最新发展", // 查询文本
3 // 返回结果数量
);
results.forEach((result, index) => {
console.log(`\n结果 ${index + 1}:`);
console.log(`内容: ${result.pageContent.substring(0, 150)}...`);
console.log(`元数据: ${JSON.stringify(result.metadata)}`);
});
// 带相似度分数的搜索
const resultsWithScore = await vectorStore.similaritySearchWithScore(
"什么是机器学习",
5
);
resultsWithScore.forEach(([doc, score]) => {
console.log(`相似度分数: ${score.toFixed(4)}`);
console.log(`内容: ${doc.pageContent.substring(0, 100)}...`);
});
从本地加载向量存储
const loadedVectorStore = await HNSWLib.load(
"./vector_store",
embeddings
);
四、实战教程:构建一个PDF问答助手
现在让我们综合运用前面学到的知识,构建一个完整的PDF文档问答助手。
4.1 项目架构
pdf-qa-assistant/
├── src/
│ ├── index.ts # 主入口
│ ├── loaders/
│ │ └── pdfLoader.ts # PDF加载逻辑
│ ├── vectorstore/
│ │ └── store.ts # 向量存储管理
│ ├── chains/
│ │ └── qaChain.ts # 问答链
│ ├── prompts/
│ │ └── qaPrompt.ts # Prompt模板
│ └── types/
│ └── index.ts # 类型定义
├── data/
│ └── sample.pdf # 示例PDF
├── .env
├── package.json
└── tsconfig.json
4.2 类型定义
// src/types/index.ts
export interface DocumentMetadata {
source: string;
page?: number;
}
export interface QAResponse {
question: string;
answer: string;
sources: Array<{
content: string;
metadata: DocumentMetadata;
score: number;
}>;
}
export interface VectorStoreConfig {
persistDirectory: string;
embeddingsModel: string;
}
4.3 PDF加载模块
// src/loaders/pdfLoader.ts
import { PDFLoader } from "langchain/document_loaders/fs/pdf";
import { RecursiveCharacterTextSplitter } from "langchain/text splitter";
import { Document } from "langchain/document";
export interface LoadPdfResult {
docs: Document[];
metadata: {
source: string;
totalPages: number;
totalChunks: number;
};
}
export async function loadPdf(
filePath: string,
chunkSize: number = 1000,
chunkOverlap: number = 200
): Promise<LoadPdfResult> {
console.log(`\n# ===== 开始加载PDF =====`);
console.log(`文件路径: ${filePath}`);
// 加载PDF
const loader = new PDFLoader(filePath);
const rawDocs = await loader.load();
console.log(`原始文档数: ${rawDocs.length}`);
// 分割文本
const textSplitter = new RecursiveCharacterTextSplitter({
chunkSize,
chunkOverlap,
separators: ["\n\n", "\n", "。", "!", "?", " ", ""],
});
const docs = await textSplitter.splitDocuments(rawDocs);
console.log(`分割后文本块数: ${docs.length}`);
console.log(`# ===== PDF加载完成 =====\n`);
return {
docs,
metadata: {
source: filePath,
totalPages: rawDocs.length,
totalChunks: docs.length,
},
};
}
4.4 向量存储管理
// src/vectorstore/store.ts
import { OpenAIEmbeddings } from "@langchain/openai";
import { HNSWLib } from "langchain/vectorstores/hnswlib";
import { Document } from "langchain/document";
import * as fs from "fs";
import * as path from "path";
export class VectorStoreManager {
private embeddings: OpenAIEmbeddings;
private vectorStore: HNSWLib | null = null;
private persistDirectory: string;
constructor(persistDirectory: string = "./data/vectorstore") {
this.embeddings = new OpenAIEmbeddings({
openAIApiKey: process.env.OPENAI_API_KEY,
});
this.persistDirectory = persistDirectory;
}
async createFromDocuments(docs: Document[]): Promise<void> {
console.log(`\n# ===== 创建向量存储 =====`);
console.log(`文档数量: ${docs.length}`);
this.vectorStore = await HNSWLib.fromDocuments(docs, this.embeddings);
// 确保目录存在
if (!fs.existsSync(this.persistDirectory)) {
fs.mkdirSync(this.persistDirectory, { recursive: true });
}
await this.vectorStore.save(this.persistDirectory);
console.log(`已保存到: ${this.persistDirectory}`);
console.log(`# ===== 向量存储创建完成 =====\n`);
}
async load(): Promise<void> {
console.log(`\n# ===== 加载向量存储 =====`);
console.log(`从: ${this.persistDirectory}`);
if (!fs.existsSync(this.persistDirectory)) {
throw new Error(`向量存储不存在: ${this.persistDirectory}`);
}
this.vectorStore = await HNSWLib.load(this.persistDirectory, this.embeddings);
console.log(`# ===== 向量存储加载完成 =====\n`);
}
async similaritySearch(
query: string,
k: number = 4
): Promise<Array<{ doc: Document; score: number }>> {
if (!this.vectorStore) {
throw new Error("向量存储未初始化");
}
const results = await this.vectorStore.similaritySearchWithScore(query, k);
return results.map(([doc, score]) => ({
doc,
score,
}));
}
isLoaded(): boolean {
return this.vectorStore !== null;
}
}
4.5 Prompt模板
// src/prompts/qaPrompt.ts
import { PromptTemplate } from "@langchain/core/prompts";
export const QA_TEMPLATE = `你是一个专业的文档问答助手。你的任务是根据提供的文档片段回答用户的问题。
## 重要规则:
1. 只使用提供的文档内容回答问题,不要编造信息
2. 如果文档中没有相关信息,请明确告知用户
3. 回答要准确、简洁、有条理
4. 如果有多处相关内容,综合整理后回答
## 文档内容:
{context}
## 用户问题:
{question}
## 回答要求:
1. 先给出直接回答
2. 如果适用,引用相关文档片段
3. 指出信息来源
请开始回答:`;
export const CONDENSE_QUESTION_TEMPLATE = `给定以下对话历史和用户后续问题,将后续问题改写为一个独立、完整的问句,使其能够直接用于向量搜索。
## 对话历史:
{chat_history}
## 用户后续问题:
{question}
## 改写后的独立问题:`;
export function createQAPrompt(): PromptTemplate {
return PromptTemplate.fromTemplate(QA_TEMPLATE);
}
export function createCondenseQuestionPrompt(): PromptTemplate {
return PromptTemplate.fromTemplate(CONDENSE_QUESTION_TEMPLATE);
}
4.6 问答链
// src/chains/qaChain.ts
import { OpenAI } from "@langchain/openai";
import { VectorStoreManager } from "../vectorstore/store";
import { LLMChain } from "langchain/chains";
import { createQAPrompt, createCondenseQuestionPrompt } from "../prompts/qaPrompt";
import { QAResponse } from "../types";
export class PDFQAChain {
private vectorStore: VectorStoreManager;
private qaChain: LLMChain;
private history: Array<{ role: string; content: string }> = [];
constructor(vectorStore: VectorStoreManager) {
this.vectorStore = vectorStore;
// 初始化LLM
const llm = new OpenAI({
modelName: "gpt-3.5-turbo",
temperature: 0.3,
maxTokens: 1000,
});
// 创建问答链
this.qaChain = new LLMChain({
llm,
prompt: createQAPrompt(),
});
}
async ask(question: string): Promise<QAResponse> {
console.log(`\n# ===== 处理问题 =====`);
console.log(`问题: ${question}`);
// 1. 从向量存储中检索相关文档
const relevantDocs = await this.vectorStore.similaritySearch(question, 4);
console.log(`\n# 检索到 ${relevantDocs.length} 个相关文档`);
// 2. 构建上下文
const context = relevantDocs
.map((item, index) => `[文档 ${index + 1}]\n${item.doc.pageContent}`)
.join("\n\n");
// 3. 调用LLM生成答案
const response = await this.qaChain.invoke({
context,
question,
});
console.log(`# ===== 回答生成完成 =====\n`);
// 4. 更新历史记录
this.history.push({ role: "user", content: question });
this.history.push({ role: "assistant", content: response.text });
// 5. 返回结果
return {
question,
answer: response.text,
sources: relevantDocs.map((item) => ({
content: item.doc.pageContent,
metadata: item.doc.metadata as any,
score: item.score,
})),
};
}
getHistory(): Array<{ role: string; content: string }> {
return [...this.history];
}
clearHistory(): void {
this.history = [];
}
}
4.7 主入口文件
// src/index.ts
import "dotenv/config";
import * as readline from "readline";
import { loadPdf } from "./loaders/pdfLoader";
import { VectorStoreManager } from "./vectorstore/store";
import { PDFQAChain } from "./chains/qaChain";
async function main() {
console.log(`
╔═══════════════════════════════════════════════════════════╗
║ ║
║ PDF 文档智能问答助手 ║
║ ║
║ 使用 LangChainJS 构建的本地文档问答系统 ║
║ ║
╚═══════════════════════════════════════════════════════════╝
`);
// 检查API密钥
if (!process.env.OPENAI_API_KEY) {
console.error("错误: 请设置 OPENAI_API_KEY 环境变量");
console.log("创建 .env 文件并添加: OPENAI_API_KEY=your-api-key");
process.exit(1);
}
// ========== 步骤1: 加载PDF文档 ==========
console.log("# 正在初始化系统...\n");
const pdfPath = "./data/sample.pdf";
const { docs, metadata } = await loadPdf(pdfPath, 1000, 200);
// ========== 步骤2: 创建向量存储 ==========
const vectorStore = new VectorStoreManager("./data/vectorstore");
// 检查是否已有向量存储
try {
await vectorStore.load();
console.log("已加载已有的向量存储");
} catch {
console.log("创建新的向量存储...");
await vectorStore.createFromDocuments(docs);
}
// ========== 步骤3: 初始化问答链 ==========
const qaChain = new PDFQAChain(vectorStore);
// ========== 步骤4: 交互式问答 ==========
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
const askQuestion = (): void => {
rl.question("\n请输入您的问题(输入 'quit' 退出): ", async (question) => {
if (question.toLowerCase() === "quit") {
console.log("\n感谢使用!再见!\n");
rl.close();
return;
}
if (!question.trim()) {
askQuestion();
return;
}
try {
const result = await qaChain.ask(question);
console.log("\n" + "=".repeat(50));
console.log("回答:");
console.log("=".repeat(50));
console.log(result.answer);
console.log("\n" + "-".repeat(50));
console.log("参考来源 (共3条):");
console.log("-".repeat(50));
result.sources.slice(0, 3).forEach((source, index) => {
console.log(`\n[来源 ${index + 1}] 相似度: ${(1 - source.score).toFixed(2)}`);
console.log(source.content.substring(0, 200) + "...");
});
} catch (error) {
console.error("\n处理问题时出错:", error);
}
askQuestion();
});
};
console.log("\n# 系统准备就绪,请开始提问!\n");
askQuestion();
}
// 运行
main().catch((error) => {
console.error("Fatal error:", error);
process.exit(1);
});
4.8 运行项目
首先,准备一个PDF文件放在./data/sample.pdf目录下,然后运行:
npx ts-node src/index.ts
交互式界面将启动:
╔═══════════════════════════════════════════════════════════╗
║ ║
║ PDF 文档智能问答助手 ║
║ ║
╚═══════════════════════════════════════════════════════════╝
# 正在初始化系统...
# ===== 开始加载PDF =====
文件路径: ./data/sample.pdf
原始文档数: 5
分割后文本块数: 23
# ===== PDF加载完成 =====
# ===== 创建向量存储 =====
文档数量: 23
已保存到: ./data/vectorstore
# ===== 向量存储创建完成 =====
# 系统准备就绪,请开始提问!
请输入您的问题(输入 'quit' 退出):
五、更多实用案例
5.1 案例一:智能客服Agent
构建一个能够访问商品信息和订单状态的客服Agent:
import { OpenAI } from "@langchain/openai";
import { initializeAgentExecutorWithOptions } from "langchain/agents";
import { Toolkit } from "langchain/agents/toolkits";
import { ChainTool } from "langchain/tools";
// 模拟数据库查询
const mockDatabase = {
products: [
{ id: "P001", name: "无线耳机", price: 299, stock: 50 },
{ id: "P002", name: "机械键盘", price: 599, stock: 0 },
{ id: "P003", name: "人体工学鼠标", price: 199, stock: 120 },
],
orders: [
{ orderId: "O2024001", userId: "U001", status: "已发货", items: ["P001"] },
{ orderId: "O2024002", userId: "U001", status: "处理中", items: ["P002", "P003"] },
],
};
// 创建工具函数
const productSearchTool = new ChainTool({
name: "searchProduct",
description: "搜索商品信息。输入商品名称或ID。返回商品详情。",
chain: new LLMChain({
llm: new OpenAI({ temperature: 0 }),
prompt: PromptTemplate.fromTemplate(`
在商品列表中搜索匹配的商品:{query}
商品列表:
${JSON.stringify(mockDatabase.products, null, 2)}
返回JSON格式结果。
`),
}),
});
const orderQueryTool = new ChainTool({
name: "queryOrder",
description: "查询订单状态。输入订单号。返回订单详情。",
chain: new LLMChain({
llm: new OpenAI({ temperature: 0 }),
prompt: PromptTemplate.fromTemplate(`
查询订单状态。订单号:{orderId}
订单列表:
${JSON.stringify(mockDatabase.orders, null, 2)}
返回JSON格式结果。
`),
}),
});
// 初始化Agent
const model = new OpenAI({ temperature: 0, modelName: "gpt-3.5-turbo" });
const agentExecutor = await initializeAgentExecutorWithOptions(
[productSearchTool, orderQueryTool],
model,
{
agentType: "openai-functions",
verbose: true,
}
);
// 对话循环
async function customerService() {
const responses = [
"我想买一个无线耳机,有货吗?",
"那机械键盘呢?",
"帮我查一下订单O2024001的状态",
];
for (const message of responses) {
console.log(`\n用户: ${message}`);
const result = await agentExecutor.invoke({ input: message });
console.log(`客服: ${result.output}`);
}
}
customerService();
5.2 案例二:多语言翻译流水线
构建一个支持多种语言、能够保持上下文一致性的翻译系统:
import { ChatOpenAI } from "@langchain/openai";
import { LLMChain } from "langchain/chains";
import { PromptTemplate } from "@langchain/core/prompts";
import { StringOutputParser } from "@langchain/core/output_parsers";
interface TranslationResult {
original: string;
translated: string;
language: string;
tone?: string;
}
class MultiLanguageTranslator {
private chatModel: ChatOpenAI;
private toneOptions = ["正式", "口语化", "文学", "技术"];
private languageMap: Record<string, string> = {
en: "English",
zh: "中文",
ja: "日本語",
ko: "한국어",
fr: "Français",
de: "Deutsch",
es: "Español",
};
constructor() {
this.chatModel = new ChatOpenAI({
modelName: "gpt-3.5-turbo",
temperature: 0.3,
});
}
async translate(
text: string,
targetLanguage: string,
tone: string = "通用",
sourceLanguage?: string
): Promise<TranslationResult> {
const template = `你是一个专业的翻译专家。
## 翻译任务
- 源语言:${sourceLanguage ? this.languageMap[sourceLanguage] : "自动检测"}
- 目标语言:${this.languageMap[targetLanguage]}
- 语气风格:${tone}
## 翻译要求
1. 准确传达原文含义
2. 符合目标语言的语言习惯
3. 保持原文的语气和风格
4. 专业术语保持一致
## 待翻译文本
${text}
## 输出格式
只输出翻译结果,不要其他内容。`;
const chain = new LLMChain({
llm: this.chatModel,
prompt: PromptTemplate.fromTemplate(template),
outputParser: new StringOutputParser(),
});
const result = await chain.invoke({});
return {
original: text,
translated: result.text,
language: targetLanguage,
tone,
};
}
async batchTranslate(
texts: string[],
targetLanguage: string,
tone: string = "通用"
): Promise<TranslationResult[]> {
const results: TranslationResult[] = [];
for (const text of texts) {
const result = await this.translate(text, targetLanguage, tone);
results.push(result);
// 添加小延迟避免API限流
await new Promise((resolve) => setTimeout(resolve, 500));
}
return results;
}
async translateWithGlossary(
text: string,
targetLanguage: string,
glossary: Record<string, string>
): Promise<string> {
const template = `你是一个专业的翻译专家,使用以下术语表确保翻译一致性:
## 术语表
${Object.entries(glossary)
.map(([zh, en]) => `- ${zh} → ${en}`)
.join("\n")}
## 翻译任务
将以下中文文本翻译成${this.languageMap[targetLanguage]}:
${text}
## 要求
1. 严格按照术语表翻译
2. 保持专业性和准确性
3. 只输出翻译结果`;
const chain = new LLMChain({
llm: this.chatModel,
prompt: PromptTemplate.fromTemplate(template),
});
const result = await chain.invoke({});
return result.text;
}
}
// 使用示例
async function main() {
const translator = new MultiLanguageTranslator();
// 单条翻译
const singleResult = await translator.translate(
"人工智能正在改变我们的生活方式",
"en",
"正式"
);
console.log("单条翻译:", singleResult);
// 带术语表的翻译
const glossary = {
人工智能: "Artificial Intelligence",
机器学习: "Machine Learning",
深度学习: "Deep Learning",
神经网络: "Neural Network",
};
const techResult = await translator.translateWithGlossary(
"深度学习是机器学习的一个分支,神经网络是其核心组件。",
"en",
glossary
);
console.log("技术文档翻译:", techResult);
}
main();
5.3 案例三:代码审查助手
import { ChatOpenAI } from "@langchain/openai";
import { LLMChain } from "langchain/chains";
import { PromptTemplate } from "@langchain/core/prompts";
import { StringOutputParser } from "@langchain/core/output_parsers";
interface CodeReviewResult {
issues: Array<{
severity: "critical" | "warning" | "info";
line?: number;
message: string;
suggestion: string;
}>;
summary: string;
score: number;
}
class CodeReviewAssistant {
private chatModel: ChatOpenAI;
constructor() {
this.chatModel = new ChatOpenAI({
modelName: "gpt-3.5-turbo",
temperature: 0.2,
});
}
async review(code: string, language: string = "typescript"): Promise<CodeReviewResult> {
const template = `你是一个经验丰富的代码审查专家。审查以下${language}代码,找出潜在问题。
## 审查重点
1. 安全性漏洞(SQL注入、XSS、敏感信息泄露等)
2. 性能问题(内存泄漏、算法复杂度、数据库查询等)
3. 代码质量(可读性、可维护性、最佳实践等)
4. 错误处理(异常捕获、日志记录等)
5. 最佳实践(设计模式、命名规范等)
## 代码
\`\`\`${language}
${code}
\`\`\`
## 输出要求
以JSON格式输出,结构如下:
{
"issues": [
{
"severity": "critical|warning|info",
"line": 行号(如适用),
"message": "问题描述",
"suggestion": "修改建议"
}
],
"summary": "总体评价(100字以内)",
"score": 1-10的质量评分
}
只输出JSON,不要其他内容。`;
const chain = new LLMChain({
llm: this.chatModel,
prompt: PromptTemplate.fromTemplate(template),
outputParser: new StringOutputParser(),
});
const result = await chain.invoke({});
try {
return JSON.parse(result.text);
} catch {
return {
issues: [],
summary: "无法解析审查结果",
score: 0,
};
}
}
async reviewPullRequest(
diff: string,
context?: string
): Promise<CodeReviewResult> {
const template = `你是一个专业的代码审查专家。审查以下Pull Request变更。
## PR背景
${context || "无额外上下文"}
## 变更内容(diff格式)
\`\`\`diff
${diff}
\`\`\`
## 审查要点
1. 变更是否符合代码库的编码规范
2. 是否引入了安全隐患
3. 是否有性能影响
4. 是否进行了充分的测试
5. 变更的必要性
## 输出格式(JSON)
{
"issues": [...],
"summary": "...",
"score": 数字
}`;
const chain = new LLMChain({
llm: this.chatModel,
prompt: PromptTemplate.fromTemplate(template),
outputParser: new StringOutputParser(),
});
const result = await chain.invoke({});
try {
return JSON.parse(result.text);
} catch {
return {
issues: [],
summary: "无法解析审查结果",
score: 0,
};
}
}
}
// 使用示例
async function main() {
const reviewer = new CodeReviewAssistant();
const codeToReview = `
async function getUserData(userId: string) {
const query = "SELECT * FROM users WHERE id = " + userId;
const result = await db.execute(query);
return result;
}
function processPassword(password: string) {
const hashed = password + "salt";
return hashed;
}
`;
const result = await reviewer.review(codeToReview, "typescript");
console.log("代码审查结果");
console.log("=".repeat(50));
console.log(`质量评分: ${result.score}/10`);
console.log(`\n总体评价: ${result.summary}`);
console.log(`\n发现问题: ${result.issues.length}个`);
result.issues.forEach((issue, index) => {
console.log(`\n[${index + 1}] [${issue.severity.toUpperCase()}] ${issue.message}`);
if (issue.line) console.log(` 行号: ${issue.line}`);
console.log(` 建议: ${issue.suggestion}`);
});
}
main();
六、最佳实践与性能优化
6.1 Prompt工程最佳实践
使用结构化的输出格式
让模型输出JSON比自由文本更可靠:
const structuredPrompt = PromptTemplate.fromTemplate(`
回答以下问题,并以JSON格式输出结果。
问题:{question}
输出格式:
{
"answer": "直接回答",
"confidence": 0-1之间的置信度,
"reasoning": "推理过程"
}
`);
// 模型更容易返回有效的JSON
分离指令和上下文
// 不推荐:混杂在一起
const badPrompt = `用户问:${question},请根据以下文档回答:${document}`;
// 推荐:清晰的分区
const goodPrompt = PromptTemplate.fromTemplate(`
## 指令
回答用户的问题,只使用提供的文档内容。
## 文档
{document}
## 问题
{question}
## 输出格式
按以下格式回答:...
`);
使用示例引导输出
Few-shot prompting能显著提升效果:
const fewShotPrompt = new FewShotPromptTemplate({
examples: [
{
input: "北京的人口是多少?",
output: '{"city": "北京", "metric": "人口", "value": "约2100万"}',
},
{
input: "东京的GDP是多少?",
output: '{"city": "东京", "metric": "GDP", "value": "约2万亿美元"}',
},
],
// ... 其他配置
});
6.2 Token使用优化
选择合适的模型
| 场景 | 推荐模型 | 理由 |
|---|---|---|
| 简单问答 | gpt-3.5-turbo | 成本低、速度快 |
| 复杂推理 | gpt-4 | 更强的推理能力 |
| 批量处理 | gpt-3.5-turbo-instruct | 专为补全任务优化 |
| 嵌入生成 | text-embedding-ada-002 | 性价比最高 |
使用流式响应
对于长文本,使用流式处理改善用户体验:
import { ChatOpenAI } from "@langchain/openai";
const model = new ChatOpenAI({
modelName: "gpt-3.5-turbo",
streaming: true,
});
const stream = await model.stream("写一篇关于AI的文章");
for await (const chunk of stream) {
process.stdout.write(chunk.content);
}
缓存频繁使用的嵌入
import { CacheBackedEmbeddings } from "langchain/embeddings";
import { Redis } from "ioredis";
// 创建带缓存的嵌入模型
const underlyingEmbeddings = new OpenAIEmbeddings();
const redisClient = new Redis(process.env.REDIS_URL);
const cachedEmbeddings = new CacheBackedEmbeddings({
underlyingEmbeddings,
documentEmbeddingCache: new RedisByteStore(redisClient),
});
6.3 错误处理与重试机制
import { RetryStrategy, ExponentialBackoffRetryStrategy } from "langchain/core/util";
async function withRetry<T>(
fn: () => Promise<T>,
maxRetries: number = 3,
baseDelay: number = 1000
): Promise<T> {
let lastError: Error;
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
return await fn();
} catch (error) {
lastError = error as Error;
console.log(`尝试 ${attempt + 1}/${maxRetries} 失败,${(baseDelay * Math.pow(2, attempt)) / 1000}秒后重试...`);
await new Promise(resolve =>
setTimeout(resolve, baseDelay * Math.pow(2, attempt))
);
}
}
throw lastError!;
}
// 使用示例
const result = await withRetry(async () => {
return await chain.invoke({ input: "test" });
});
6.4 生产环境配置
import { ChatOpenAI } from "@langchain/openai";
// 生产环境配置
const productionModel = new ChatOpenAI({
modelName: "gpt-3.5-turbo",
temperature: 0.3,
maxTokens: 2000,
timeout: 60000, // 60秒超时
maxRetries: 3, // 自动重试
streaming: false, // 生产环境可关闭流式
callbacks: [
{
handleLLMEnd: (output) => {
console.log("Token使用情况:", output.llmOutput?.tokenUsage);
},
handleLLMError: (error) => {
console.error("LLM调用错误:", error);
// 可以在这里发送告警
},
},
],
});
6.5 监控与日志
import { CallbackManager } from "langchain/callbacks";
import { LangChainTracer } from "langchain/callbacks/tracers";
// 集成LangSmith监控
const callbackManager = new CallbackManager([
new LangChainTracer({
projectName: "production-assistant",
// 可选:配置LangSmith API
// ... 其他配置
}),
]);
const model = new ChatOpenAI({
modelName: "gpt-3.5-turbo",
callbackManager,
});
七、常见问题与解决方案
7.1 API相关问题
问题:Rate LimitExceededError
// 解决方案1:添加请求间隔
import { AsyncQueue } from "./utils/asyncQueue";
const queue = new AsyncQueue({ concurrency: 1, interval: 1000 });
async function rateLimitedCall(fn: () => Promise<any>) {
return queue.add(fn);
}
// 解决方案2:使用批量API
const batchModel = new ChatOpenAI({
modelName: "gpt-3.5-turbo",
batchSize: 10, // LangChain会自动批量处理
});
问题:Invalid API Key
// 确保环境变量正确加载
import "dotenv/config";
// 验证API Key
if (!process.env.OPENAI_API_KEY?.startsWith("sk-")) {
throw new Error("Invalid OPENAI_API_KEY format");
}
7.2 向量存储问题
问题:向量搜索返回空结果
// 可能原因1:嵌入模型未正确初始化
const embeddings = new OpenAIEmbeddings({
openAIApiKey: process.env.OPENAI_API_KEY,
// 确保模型名称正确
modelName: "text-embedding-ada-002",
});
// 可能原因2:向量维度不匹配
// 确保向量存储和检索使用相同的嵌入模型
// 可能原因3:文本分割方式不适合
// 尝试调整chunkSize和overlap
const textSplitter = new RecursiveCharacterTextSplitter({
chunkSize: 500, // 减小
chunkOverlap: 100,
separators: ["\n", "。", "!", "?"], // 使用中文分隔符
});
7.3 内存问题
问题:对话历史过长导致Token溢出
import { BufferMemory } from "langchain/memory";
const memory = new BufferMemory({
memoryKey: "chat_history",
maxTokenLimit: 2000, // 限制历史长度
returnMessages: true,
});
7.4 Agent执行问题
问题:Agent陷入死循环
const executor = await initializeAgentExecutorWithOptions(tools, model, {
agentType: "zero-shot-react-description",
maxIterations: 10, // 限制最大迭代次数
earlyStoppingMethod: "force",
});
八、项目扩展与生态
8.1 LangChain生态全景图
LangChain生态系统
├── 核心框架
│ ├── langchain (Python)
│ ├── langchainjs (JavaScript/TypeScript)
│ └── langchain-go (Go)
│
├── LangServe
│ └── 将Chain部署为REST API
│
├── LangSmith
│ ├── 调试与测试
│ ├── 性能监控
│ └── 数据集管理
│
└── 社区生态
├── 第三方集成
├── 预构建模板
└── 工具市场
8.2 相关的优秀开源项目
LangChain Python版
- 仓库: github.com/langchain-ai/langchain
- 用途: Python生态的LLM应用开发
LangServe
- 仓库: github.com/langchain-ai/langserve
- 用途: 快速将LangChain应用部署为REST API
LangSmith
- 官网: smith.langchain.com
- 用途: LLMOps平台,用于调试、测试和监控
LlamaIndex
- 仓库: github.com/jerryjliu/llama_index
- 用途: 专注于知识检索增强(RAG)
AutoGPT
- 仓库: significantgravitas/AutoGPT
- 用途: 自主Agent实验项目
8.3 学习资源推荐
官方文档
- LangChainJS文档: js.langchain.com
- LangChain文档: python.langchain.com
社区资源
- LangChain Discord服务器
- GitHub Discussions
- 官方博客
视频教程
- LangChain官方YouTube频道
- 各技术平台的AI应用开发系列
九、总结与展望
9.1 核心要点回顾
在这篇文章中,我们深入探索了LangChainJS的各个方面:
基础概念
- LLMs和Chat Models的区别与使用场景
- Prompt Templates的创建与使用
- Chains的组合与执行流程
- Memory组件实现上下文保持
核心功能
- Agent的构建与工具集成
- 文档加载与处理
- 向量存储与语义搜索
实战技能
- PDF问答助手的完整实现
- 智能客服Agent的构建
- 多语言翻译系统
- 代码审查助手
最佳实践
- Prompt工程技巧
- Token使用优化
- 错误处理机制
- 生产环境配置
9.2 LangChainJS的未来
LangChainJS作为一个活跃发展的项目,正在快速演进:
近期发展方向
- 更强大的Agent能力
- 更丰富的工具集成
- 更好的性能优化
- 更完善的TypeScript支持
生态系统成熟度
- 更多的生产级应用采用
- 企业级支持增强
- 与主流云平台的深度集成
9.3 行动建议
立即开始
- 按照文章中的环境搭建步骤,创建你的第一个LangChainJS项目
- 从简单的LLMChain开始,体验基础功能
- 逐步尝试更复杂的Agent和向量检索功能
深入学习
- 阅读LangChainJS官方文档和示例代码
- 参与LangChain社区讨论
- 尝试将LangChainJS集成到你现有的项目中
生产准备
- 了解LangSmith监控工具
- 使用LangServe部署你的应用
- 建立完善的错误处理和监控机制
9.4 最后的思考
LangChainJS代表的不仅是另一个框架,更是一种新的应用开发范式。它将复杂的LLM调用封装成可组合的组件,让开发者能够专注于业务逻辑而非底层细节。
正如React改变了前端开发、Next.js改变了React应用开发一样,LangChainJS正在改变AI应用开发的方式。无论你是前端工程师、后端开发者,还是AI爱好者,掌握LangChainJS都将为你的技术栈增添一块重要的拼图。
现在,是时候开始了。用LangChainJS构建你的第一个AI应用吧!
相关链接
- GitHub仓库: github.com/langchain-ai/langchainjs
- 官方文档: js.langchain.com
- NPM包: npmjs.com/package/langchain
- Discord社区: discord.gg/langchain
- 官方博客: blog.langchain.dev
评论区