微信扫码
添加专属顾问
我要投稿
前端开发者进军AI的捷径:用LangChain.js轻松打造智能对话机器人,结合本地与云端大模型能力。 核心内容: 1. LangChain.js框架的核心架构与模块化设计解析 2. 实战演示:基于Ollama和通义千问搭建对话机器人 3. 完整前后端代码实现与API调用最佳实践
随着大语言模型(LLM)的快速发展,开发者需要高效的工具链来集成模型能力到实际应用中。
LangChain 是一个开源框架,旨在简化基于语言模型的应用程序开发,提供模块化的组件(如模型调用、记忆管理、工具集成等),可以简单类比Java界的Spring框架来理解,Nodejs界的express/chair等。
本文将通过一个示例项目,展示如何用 LangChain.js 实现一个对话机器人,结合 Ollama(本地运行开源模型)和 通义千问(国产大模型API),并提供完整的前后端代码。
LainChain结构图:
其中:
LangChain.js 是基于Langchain的 JavaScript/TypeScript 版本,支持在浏览器、Node.js 等环境中快速构建AI应用,除此之外还有Python版本。
LangChain.js 支持多种 LLM 提供商(如 OpenAI、Ollama 等),并提供了灵活的接口,使得开发者可以轻松集成不同的模型和服务,主要包括以下模块包:
LangChain 的 API 设计以模块化和链式调用为核心,下面是一些基础 API 的简单介绍:
LangChain 支持三类核心模型接口,满足不同场景需求:
基础文本生成模型(如 GPT-3.5 ):
const model = new OpenAI({ temperature: 0.7 });
await model.call("你好,介绍react");
对话式模型(如 GPT-4 ):
const chat = new ChatOpenAI();
await chat.predictMessages([new HumanMessage("你好!")]);
文本向量化模型(用于语义检索、聚类)
const embeddings = new OpenAIEmbeddings();
const vec = await embeddings.embedQuery("react技术");
关键参数说明:
通过模板动态生成提示词,支持结构化输入与输出控制:
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"
嵌入示例提升模型表现:
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: "愤怒" });
/* 输出:
输入:高兴
输出:欣喜若狂
输入:悲伤
输出:心如刀绞
输入:愤怒
输出:
*/
从外部文件读取模板:
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"],
});
通过组合多个组件构建复杂工作流,是langchain核心模块:
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冷知识
串联多个链实现分步处理:
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: "前端已死,还有未来" });
自定义数据处理逻辑:
import { TransformChain } from "langchain/chains";
const transform = new TransformChain({
transform: async (inputs) => {
// 自定义处理逻辑(如文本清洗)
return { cleaned: inputs.text.replace(/\d+/g, "") };
},
inputVariables: ["text"],
outputVariables: ["cleaned"],
});
支持从多种来源加载结构化文档,可以用来RAG的知识库录入:
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 });
const loader = new CheerioWebBaseLoader("https://exampleurl.com");
const docs = await loader.load();
console.log({ docs });
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);
}
});
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:
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);
管理对话或任务的上下文状态:
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: "刚才我们聊了什么?" }); // 模型能回忆历史
跟踪特定实体信息:
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技术" }
将模型返回的文本转换为结构化数据,实用功能:
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)开发。"
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字)"
}
*/
集成外部API扩展模型能力:
使用langchain内置的工具:
import { Calculator, SerpAPI } from "langchain/tools";
const tools = [
new Calculator(), // 数学计算
new SerpAPI(), // 实时网络搜索
new WolframAlphaTool(), // 科学计算
];
自定义开发工具:
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());
}
});
代理执行:
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) 即可调用外部工具链而设计。
结合向量数据库实现知识增强:
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相关模块:
"@langchain/community": "^0.3.40",
"@langchain/core": "^0.3.44",
"@langchain/ollama": "^0.2.0",
"langchain": "^0.3.21",
由于需要提供http服务托管前端页面,需要安装express:
"@types/express": "^5.0.1",
"express": "^5.1.0",
一个简单的通过ollama调用本地模型的例子:
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);
});
结合上下文,进行连续调用:
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专为对话设计,支持多消息类型和上下文管理。需要将这些区别清晰地传达给用户。
qwen-turbo
或 qwen-plus
)根据文档,简单通过fetch实现对通义的调用:
/**
* 流式响应处理函数
* @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调用,流式返回结果。
上面代码中,只是简单使用fetch来调用通义模型提供的接口,但是如果要使用到langchain,则需要遵循BaseLLM/BaseModal来将上面的逻辑进行封装,以便符合langchain的规范:
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,当然也可以换成BaseChatModel。
下面是express实现一个本地http服务,流式返回,3000端口:
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代码:
<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这个方法。
运行:
npm run server
53AI,企业落地大模型首选服务商
产品:场景落地咨询+大模型应用平台+行业解决方案
承诺:免费场景POC验证,效果验证后签署服务协议。零风险落地应用大模型,已交付160+中大型企业
2025-06-08
Langfuse:重新定义LLM应用开发与运维的可观测性
2025-06-08
Langgraph实战--自定义embeding
2025-06-07
为 AI Agent 铺路:深度解析下一代应用的核心基建 LangGraph
2025-06-05
智能体框架怎么选?LangChain、Dify、CrewAI、AutoGen五大框架横向对比
2025-06-04
吴恩达对谈LangChain创始人:企业构建Agen系统的核心认知!
2025-06-02
一文看懂RAG、LangChain、Agent三者的关系
2025-05-31
吴恩达:别再纠结Agent定义,AI Agent开发者应关注这些要点
2025-05-30
使用 LangChain 与 MCP集成
2025-03-16
2025-03-20
2025-03-17
2025-05-08
2025-04-18
2025-03-22
2025-05-06
2025-03-23
2025-04-13
2025-05-28
2025-05-21
2025-05-19
2025-05-08
2025-05-06
2025-04-22
2025-04-18
2025-03-22
2025-03-22