作为一名前端开发老程序员,这次给大家更新一个前端学AI的突破口:LangChain.js,希望能有所帮助。
背景
随着大语言模型(LLM)的快速发展,开发者需要高效的工具链来集成模型能力到实际应用中。
LangChain 是一个开源框架,旨在简化基于语言模型的应用程序开发,提供模块化的组件(如模型调用、记忆管理、工具集成等),可以简单类比Java界的Spring框架来理解,Nodejs界的express/chair等。
本文将通过一个示例项目,展示如何用 LangChain.js 实现一个对话机器人,结合 Ollama(本地运行开源模型)和 通义千问(国产大模型API),并提供完整的前后端代码。
LangChain.js 介绍
LainChain结构图:

其中:
- LainChain:提供底层的核心能力。
- LainGraph:提供流程编排能力。
- Integrations:提供扩展和集成能力。
- LangSmith:提供调试、监控、评测能力。
- LainGraph Platform:LangChain 的商业化大模型应用发布平台。
LangChain.js 是基于Langchain的 JavaScript/TypeScript 版本,支持在浏览器、Node.js 等环境中快速构建AI应用,除此之外还有Python版本。
LangChain.js 支持多种 LLM 提供商(如 OpenAI、Ollama 等),并提供了灵活的接口,使得开发者可以轻松集成不同的模型和服务,主要包括以下模块包:
- langchain-core: 提供基础抽象和核心运行时机制(聊天模型、向量存储、工具等)的抽象接口和组装方式。
- langchain: langchain的主包,包含了内置的通用的链(chains)、代理(agents)、检索策略(retrieval strategies),不包含第三方集成。
- langchain-community: 由LangChain社区维护的第三方集成包,包括 OpenAI、Anthropic 等 LLM,以及向量存储(如 Pinecone)、工具(如 Tavily 搜索)等。

LangChain.js 基础API详解
LangChain 的 API 设计以模块化和链式调用为核心,下面是一些基础 API 的简单介绍:
模型调用(Models)
LangChain 支持三类核心模型接口,满足不同场景需求:
LLM
基础文本生成模型(如 GPT-3.5 ):
- 核心方法:
vbnet
const model = new OpenAI({ temperature: 0.7 });
await model.call("你好,介绍react");
ChatModals
对话式模型(如 GPT-4 ):
- 核心方法:predictMessages()
csharp
const chat = new ChatOpenAI();
await chat.predictMessages([new HumanMessage("你好!")]);
Embeddings
文本向量化模型(用于语义检索、聚类)
- 核心方法:embedQuery() / embedDocuments()
ini
const embeddings = new OpenAIEmbeddings();
const vec = await embeddings.embedQuery("react技术");
关键参数说明:
- temperature(0-2):控制生成随机性,值越高结果越多样。
- maxTokens:限制生成文本的最大长度。
- streaming:启用流式传输(适合实时聊天场景)。
提示模板(Prompts)
通过模板动态生成提示词,支持结构化输入与输出控制:
基础模板
arduino
import { PromptTemplate } from"@langchain/core/prompts";
// 单变量模板
const template = "用{style}风格翻译以下文本:{text}";
const prompt = new PromptTemplate({
template,
inputVariables: ["style", "text"],
});
// 使用示例
const formattedPrompt = await prompt.format({
style: "文言文",
text: "Hello world",
});
// 输出:"用文言文风格翻译以下文本:Hello world"
Few-shot模板
嵌入示例提升模型表现:
php
import { FewShotPromptTemplate } from"langchain/prompts";
const examples = [
{ input: "高兴", output: "欣喜若狂" },
{ input: "悲伤", output: "心如刀绞" }
];
const examplePrompt = new PromptTemplate({
template: "输入:{input}\n输出:{output}",
inputVariables: ["input", "output"],
});
const fewShotPrompt = new FewShotPromptTemplate({
examples,
examplePrompt,
suffix: "输入:{adjective}\n输出:",
inputVariables: ["adjective"],
});
await fewShotPrompt.format({ adjective: "愤怒" });
/* 输出:
输入:高兴
输出:欣喜若狂
输入:悲伤
输出:心如刀绞
输入:愤怒
输出:
*/
文件模板加载
从外部文件读取模板:
arduino
import { PromptTemplate } from "@langchain/core/prompts";
import fs from "fs";
const template = fs.readFileSync("./prompts/email_template.txt", "utf-8");
const prompt = new PromptTemplate({
template,
inputVariables: ["userName", "product"],
});
链式调用(Chains)(langchain核心)
通过组合多个组件构建复杂工作流,是langchain核心模块:
LLMChain(基础链)
php
import { LLMChain } from "langchain/chains";
const chain = new LLMChain({
llm: new OpenAI(),
prompt: new PromptTemplate({
template: "生成关于{topic}的{num}条冷知识",
inputVariables: ["topic", "num"]
}),
});
const res = await chain.call({ topic: "react", num: 3 });
// 输出:模型生成的3条react冷知识
SequentialChain**(顺序链)
串联多个链实现分步处理:
php
import { SequentialChain } from "langchain/chains";
const chain1 = new LLMChain({ ... }); // 生成文章大纲
const chain2 = new LLMChain({ ... }); // 扩展章节内容
const chain3 = new LLMChain({ ... }); // 优化语言风格
const overallChain = new SequentialChain({
chains: [chain1, chain2, chain3],
inputVariables: ["title"],
outputVariables: ["outline", "content", "final"],
});
const result = await overallChain.call({ title: "前端已死,还有未来" });
TransformChain(转换链)
自定义数据处理逻辑:
javascript
import { TransformChain } from "langchain/chains";
const transform = new TransformChain({
transform: async (inputs) => {
// 自定义处理逻辑(如文本清洗)
return { cleaned: inputs.text.replace(/\d+/g, "") };
},
inputVariables: ["text"],
outputVariables: ["cleaned"],
});
文档加载器(Document Loaders)
支持从多种来源加载结构化文档,可以用来RAG的知识库录入:
- 本地文件:加载 文本/PDF/Word 文档。
ini
const loader = new TextLoader("example.txt");
const docs = await loader.load();
const loader2 = new PDFLoader("report.pdf");
const docs2 = await loader2.load();
console.log({ docs });
console.log({ docs2 });
- 网页内容:抓取指定 URL 的 HTML 内容。
ini
const loader = new CheerioWebBaseLoader("https://exampleurl.com");
const docs = await loader.load();
console.log({ docs });
- 数据库:从 MySQL/MongoDB 读取数据。
javascript
import { createClient } from"@supabase/supabase-js";
import { OpenAIEmbeddings } from"@langchain/openai";
import { SupabaseHybridSearch } from"@langchain/community/retrievers/supabase";
// 初始化 Supabase 客户端
const client = createClient(
process.env.SUPABASE_URL,
process.env.SUPABASE_PRIVATE_KEY
);
// 创建混合检索器
const retriever = new SupabaseHybridSearch(new OpenAIEmbeddings(), {
client,
similarityK: 5, // 语义搜索返回结果数
keywordK: 3, // 关键词搜索返回结果数
tableName: "documents", // 数据库表名
similarityQueryName: "match_documents", // 语义搜索函数名
keywordQueryName: "kw_match_documents", // 关键词搜索函数名
// 高级参数
reRanker: (results) => {
// 自定义结果合并策略(如加权分数)
return results.sort((a, b) => b.score - a.score);
}
});
- 云存储:从 AWS S3/GCS** 加载文件。
php
const loader = new S3Loader({
bucket: "my-document-bucket-123",
key: "AccountingOverview.pdf",
s3Config: {
region: "us-east-1",
credentials: {
accessKeyId: "<YourAccessKeyId>",
secretAccessKey: "<YourSecretAccessKey>",
},
},
unstructuredAPIURL: "<YourUnstructuredAPIURL>",
unstructuredAPIKey: "<YourUnstructuredAPIKey>",
});
const docs = await loader.load();
文档分块处理
可以用来做embeddings:
javascript
import { RecursiveCharacterTextSplitter } from "langchain/text_splitter";
const splitter = new RecursiveCharacterTextSplitter({
chunkSize: 500, // 单块最大字符数
chunkOverlap: 50, // 块间重叠字符
});
const docs = await loader.load();
const splitDocs = await splitter.splitDocuments(docs);
上下文管理(Memory)
管理对话或任务的上下文状态:
对话记忆
javascript
import { BufferMemory } from "langchain/memory";
const memory = new BufferMemory({
memoryKey: "chat_history", // 存储对话历史的字段名
});
const chain = new ConversationChain({
llm: new ChatOpenAI(),
memory
});
// 连续对话
await chain.call({ input: "你好!" });
await chain.call({ input: "刚才我们聊了什么?" }); // 模型能回忆历史
实体记忆
跟踪特定实体信息:
javascript
import { EntityMemory } from "langchain/memory";
const memory = new EntityMemory({
llm: new OpenAI(),
entities: ["userName", "preferences"], // 需要跟踪的实体
});
await memory.saveContext(
{ input: "我叫小明,喜欢研究react技术" },
{ output: "已记录您的偏好" }
);
const currentEntities = await memory.loadEntities();
// 输出:{ userName: "小明", preferences: "react技术" }
输出解析(Output Parsers)
将模型返回的文本转换为结构化数据,实用功能:
基础解析器
javascript
import { StringOutputParser } from "@langchain/core/output_parsers";
const chain = prompt.pipe(model).pipe(new StringOutputParser());
const result = await chain.invoke({ topic: "react技术" });
// result "React技术是一个用于构建用户界面的JavaScript库,其核心用途包括:1) 组件化开发,2) 虚拟DOM高效更新,3) 支持单页应用(SPA)开发。"
结构化解析,json
php
import { StructuredOutputParser } from"langchain/output_parsers";
// 定义输出Schema
const parser = StructuredOutputParser.fromZodSchema(
z.object({
title: z.string().describe("生成的文章标题"),
keywords: z.array(z.string()).describe("3-5个关键词"),
content: z.string().describe("不少于500字的内容")
})
);
const chain = new LLMChain({
llm: model,
prompt: new PromptTemplate({
template: "根据主题{topic}写一篇文章\n{format_instructions}",
inputVariables: ["topic"],
partialVariables: { format_instructions: parser.getFormatInstructions() }
}),
outputParser: parser
});
const res = await chain.call({ topic: "react技术" });
/*
{
title: "React技术:现代前端开发的核心理念",
keywords: ["组件化", "虚拟DOM", "Hooks", "SPA", "状态管理"],
content: "React是由Facebook开发的一个用于构建用户界面的JavaScript库...(至少500字)"
}
*/
工具与代理(Tools & Agents)
集成外部API扩展模型能力:
预定义工具
使用langchain内置的工具:
javascript
import { Calculator, SerpAPI } from "langchain/tools";
const tools = [
new Calculator(), // 数学计算
new SerpAPI(), // 实时网络搜索
new WolframAlphaTool(), // 科学计算
];
自定义工具
自定义开发工具:
javascript
import { DynamicTool } from "langchain/tools";
export const weatherTool = new DynamicTool({
name: "get_weather",
description: "查询指定城市的天气",
func: async (city) => {
const apiUrl = `https://api.weather.com/${city}`;
return await fetch(apiUrl).then(res => res.json());
}
});
代理执行:
vbnet
import { initializeAgentExecutorWithOptions } from "langchain/agents";
import { weatherTool } from 'weatherTool
const executor = await initializeAgentExecutorWithOptions(
tools:[weatherTool],
new ChatOpenAI({ temperature: 0 }),
{ agentType: "structured-chat-zero-shot-react-description" }
);
const res = await executor.invoke({
input: "上海当前温度是多少?比纽约高多少摄氏度?"
});
// 模型将自动调用天气查询工具和计算器
structured-chat-zero-shot-react-description是 LangChain 框架中一种 结构化对话代理(Structured Chat Agent) 的类型,专为让大模型(如 GPT-4)无需示例学习(Zero-Shot) 即可调用外部工具链而设计。
检索增强(Retrieval)RAG
结合向量数据库实现知识增强:
javascript
import { MemoryVectorStore } from"langchain/vectorstores/memory";
import { OpenAIEmbeddings } from"@langchain/openai";
// 1. 加载文档并向量化
const vectorStore = await MemoryVectorStore.fromDocuments(
splitDocs,
new OpenAIEmbeddings()
);
// 2. 语义检索
const results = await vectorStore.similaritySearch("神经网络的发展历史", 3);
// 3. 将检索结果注入提示词
const chain = createRetrievalChain({
retriever: vectorStore.asRetriever(),
combineDocsChain: new LLMChain(...)
});
示例:对话机器人
后端实现
安装依赖
安装langchainjs相关模块:
perl
"@langchain/community": "^0.3.40",
"@langchain/core": "^0.3.44",
"@langchain/ollama": "^0.2.0",
"langchain": "^0.3.21",
由于需要提供http服务托管前端页面,需要安装express:
perl
"@types/express": "^5.0.1",
"express": "^5.1.0",
调用本地模型
一个简单的通过ollama调用本地模型的例子:
javascript
import { Ollama,ChatOllama } from"@langchain/ollama"
asyncfunction main(): Promise<void> {
const ollamaLlm = new Ollama({
baseUrl: "http://127.0.0.1:11434",
model: "deepseek-r1:7b",
});
const stream = await ollamaLlm.stream(
`你谁,擅长什么?`
);
forawait (const chunk of stream) {
process.stdout.write(chunk);
}
}
main().catch(error => {
console.error("程序执行出错:");
console.error(error);
});
结合上下文,进行连续调用:
javascript
import { Ollama,ChatOllama } from"@langchain/ollama"
import { SystemMessage, HumanMessage } from"@langchain/core/messages";
asyncfunction mainChat(): Promise<void> {
const chatModel = new ChatOllama({
baseUrl: "http://127.0.0.1:11434",
model: "deepseek-r1:7b",
});
const stream = await chatModel.stream([
new SystemMessage("角色:一个前端技术专家 擅长:擅长回答前端技术相关的问题。"),
new HumanMessage("你谁,擅长什么?"),
]);
forawait (const chunk of stream) {
process.stdout.write(chunk.text);
}
}
main().catch(error => {
console.error("程序执行出错:");
console.error(error);
});
在Langchain中,Ollama,ChatOllama分别是对ollama工具的封装,Ollama用于基础文本生成,ChatOllama专为对话设计,支持多消息类型和上下文管理。需要将这些区别清晰地传达给用户。
调用外部模型-通义
- 前往 阿里云通义千问控制台
- 创建API Key(需实名认证)
- 获取模型服务地址(例如
qwen-turbo
或qwen-plus
)
根据文档,简单通过fetch实现对通义的调用:
typescript
/**
* 流式响应处理函数
* @param apiEndpoint - 通义千问API端点地址
* @param apiKey - API认证密钥
* @param modelName - 使用的大模型名称(例:qwen-max)
* @param prompt - 用户输入的提示词
* @param onCallback - 每收到一个token时的回调函数
*
* 实现流程:
* 1. 发送携带prompt的POST请求到API端点
* 2. 处理流式响应数据
* 3. 解析并提取每个数据块中的文本内容
* 4. 通过回调函数实时返回生成的文本
*/
exportconst streamResponseChunks = async ({
apiEndpoint,
apiKey,
modelName,
prompt,
onCallback
}: {
apiEndpoint: string,
apiKey: string,
modelName: string,
prompt: string,
onCallback: (text: string) =>void
}) => {
// 发送POST请求到通义千问API
const response = await fetch(apiEndpoint, {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${apiKey}`// 使用Bearer Token认证
},
body: JSON.stringify({
model: modelName,
messages: [{ // 构造消息体
role: "user",
content: prompt
}],
stream: true// 启用流式传输
})
});
// 处理HTTP错误响应
if (!response.ok || !response.body) {
const errorResponse = await response.json();
thrownewError(JSON.stringify(errorResponse));
}
// 创建流式读取器
const reader = response.body.getReader();
const decoder = new TextDecoder(); // 用于解码二进制流数据
// 持续读取流数据
while (true) {
const { done, value } = await reader.read();
if (done) break; // 流读取结束
const chunk = decoder.decode(value);
const lines = chunk.split("\n"); // 按行分割数据块
// 处理每行数据
for (const line of lines) {
if (line.trim() === "") continue; // 跳过空行
try {
// 处理流结束标记
if (line === 'data: [DONE]') {
console.log('流式响应结束');
continue;
}
// 验证数据格式
if (!line.startsWith('data: ')) {
console.log('跳过非数据行:', line);
continue;
}
// 解析JSON数据
const jsonStr = line.replace(/^data: /, '');
const data = JSON.parse(jsonStr);
// 提取生成的文本内容
if (data.choices?.[0]?.delta?.content) {
const text = data.choices[0].delta.content;
onCallback(text); // 触发回调函数
}
} catch (e) {
console.log('解析错误:', e);
console.log('错误数据:', line);
}
}
}
};
上面代码根据注释理解即可,主要是发起http调用,流式返回结果。
基于BaseLLM/BaseModal封装
上面代码中,只是简单使用fetch来调用通义模型提供的接口,但是如果要使用到langchain,则需要遵循BaseLLM/BaseModal来将上面的逻辑进行封装,以便符合langchain的规范:
typescript
import { BaseLLM, BaseLLMParams } from"@langchain/core/language_models/llms";
import { CallbackManagerForLLMRun } from"@langchain/core/callbacks/manager";
import { GenerationChunk } from"@langchain/core/outputs";
import { LLMResult, Generation } from"@langchain/core/outputs";
interface CustomLLMParams extends BaseLLMParams {
apiKey: string;
modelName: string;
apiEndpoint: string;
}
exportclass CustomLLM extends BaseLLM {
apiKey: string;
modelName: string;
apiEndpoint: string;
constructor(params: CustomLLMParams) {
super(params);
this.apiKey = params.apiKey;
this.modelName = params.modelName;
this.apiEndpoint = params.apiEndpoint;
}
_llmType(): string {
return"tongyi";
}
async *_streamResponseChunks(
prompt: string,
options: this["ParsedCallOptions"],
runManager?: CallbackManagerForLLMRun
): AsyncGenerator<GenerationChunk> {
const response = await fetch(this.apiEndpoint, {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${this.apiKey}`
},
body: JSON.stringify({
model: this.modelName,
messages: [
{
role: "user",
content: prompt
}
],
stream: true
})
});
if (!response.ok || !response.body) {
const errorResponse = await response.json();
thrownewError(JSON.stringify(errorResponse));
}
const reader = response.body.getReader();
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) break;
const chunk = decoder.decode(value);
const lines = chunk.split("\n");
for (const line of lines) {
if (line.trim() === "") continue;
try {
// 检查是否是结束标记
if (line === 'data: [DONE]') {
console.log('流式响应结束');
continue;
}
// 确保数据以 "data: " 开头
if (!line.startsWith('data: ')) {
console.log('跳过非数据行:', line);
continue;
}
const jsonStr = line.replace(/^data: /, '');
const data = JSON.parse(jsonStr);
if (data.choices?.[0]?.delta?.content) {
const text = data.choices[0].delta.content;
const generationChunk = new GenerationChunk({
text: text,
generationInfo: {}
});
yield generationChunk;
await runManager?.handleLLMNewToken(text);
}
} catch (e) {
console.log('解析错误:', e);
console.log('错误数据:', line);
}
}
}
}
async _generate(
prompts: string[],
options: this["ParsedCallOptions"],
runManager?: CallbackManagerForLLMRun
): Promise<LLMResult> {
const prompt = prompts[0];
const chunks: Generation[] = [];
forawait (const chunk of this._streamResponseChunks(prompt, options, runManager)) {
const text = chunk.text;
// 实时输出到控制台
process.stdout.write(text);
chunks.push({ text });
}
// 输出换行
process.stdout.write('\n');
return {
generations: [chunks]
};
}
async streamResponse(
prompt: string,
options: this["ParsedCallOptions"],
onToken: (token: string) =>void
): Promise<void> {
forawait (const chunk of this._streamResponseChunks(prompt, options)) {
onToken(chunk.text);
}
}
}
上面代码中,我们封装了一个CustomLLM类继承自BaseLLM,然后将参数作为构造方法的参数传入,同时将之前的fetch逻辑移到了 _streamResponseChunks 这个方法中,然后实现了一个 _generate 方法。
其中 _streamResponseChunks 和 _generate是langchain使用中的固定需要实现的方法,这样有助于在后续复杂的链式中简单调用,如果不理解记着这属于langchain的规范就行了,就像mvc框架需要有controller,service一样。
例如调用本地模型时,我们可以直接使用Ollama的ChatModal,那是由于langchain社区对Ollama统一进行了封装,如果社区没有,就可以自己继承BaseLLM/BaseModal来实现。
除了BaseLLM外,langchain还提供了BaseChatModal,其中:
- 接口设计差异 :
- BaseLLM :面向纯文本输入( string[] )
- BaseChatModel :面向结构化消息( BaseMessage[] )
- 使用场景 :
- BaseLLM :适用于单轮问答场景
- BaseChatModel :适用于多轮对话场景
实例代码使用的是BaseLLM,当然也可以换成BaseChatModel。
express服务:
下面是express实现一个本地http服务,流式返回,3000端口:
javascript
import { Router } from'express';
import express from'express';
import { streamResponseChunks } from'./tongyi-chat.js';
const router = Router();
router.get('/streamChat', (req, res) => {
// 设置 SSE 头
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Connection', 'keep-alive');
const { prompt } = req.query;
if (!prompt || typeof prompt !== 'string') {
res.write(`data: ${JSON.stringify({ error: '缺少有效的prompt参数' })}\n\n`);
res.end();
return;
}
// 处理客户端断开连接
req.on('close', () => {
res.end();
});
// 开始流式响应
// 调用 streamResponseChunks
});
const app = express();
const port = 3000;
// 中间件
app.use(express.json());
app.use(express.static('public'));
// 路由
app.use('/api', router);
// 启动服务器
app.listen(port, () => {
console.log(`服务器运行在 http://localhost:${port}`);
});
前端实现
前端页面比较简单,只有一个输入框和问题/回答列表,直接用原生实现,不用框架,下面是前端页面的JavaScript代码:
ini
<script>
const chatbox = document.getElementById('chatbox');
const userInput = document.getElementById('userInput');
function appendMessage(text, isUser, element) {
if (element) {
element.innerHTML += text; // 使用innerHTML
return element;
}
const div = document.createElement('div');
div.className = `message ${isUser ? 'user' : 'bot'}`;
div.innerHTML = text; // 用户消息保持纯文本
chatbox.appendChild(div);
chatbox.scrollTop = chatbox.scrollHeight;
return div;
}
function sendRequest() {
const input = userInput.value;
userInput.value = ''; // 清空输入框
appendMessage(input, true);
let botMessage = null; // 用于跟踪当前bot消息元素
const eventSource = new EventSource(`/api/streamChat?prompt=${encodeURIComponent(input)}`);
eventSource.onmessage = (event) => {
try {
const data = JSON.parse(event.data);
if (data.text) {
// 首次创建bot消息元素,后续持续更新
botMessage = appendMessage(data.text, false, botMessage);
} elseif (data.error) {
appendMessage(`错误: ${data.error}`, false);
eventSource.close();
}
} catch (error) {
}
};
eventSource.onerror = () => {
eventSource.close();
};
}
</script>
需要注意的是,创建EventSource事件来接受后端的流式返回,同时前端在append的时候要注意累加然后整体替换,详细看appendMessage这个方法。
实现效果
运行:
arduino
npm run server
效果截图
