前端学AI:基于Node.js的Langchain开发-简单实战应用
本文主要介绍
LangChain
的实战开发,包括:开发准备阶段、Agent
开发实战、对话机器人实现、最佳实践与调试技巧、文本处理组件、插件开发(天气API工具)、性能优化策略和框架集成方案。供自己以后查漏补缺,也欢迎同道朋友交流学习。
引言
上一篇讲了些基础知识,介绍了简单概念和基础的 API,本章就着手实战开发。
本文主要介绍 LangChain
的实战开发,包括:开发准备阶段、Agent
开发实战、对话机器人实现、最佳实践与调试技巧、文本处理组件、插件开发(天气API工具)、性能优化策略和框架集成方案。
LangChain的开发准备
环境要求
- Node.js版本:官方推荐
18.x
及以上(Node.js 16需额外polyfill
)。 - 包管理工具:
npm/yarn/pnpm
任选,推荐npm
。 - 兼容性:支持
ESM
和CommonJS
模块。
依赖安装
npm install langchain @langchain/core @langchain/community
# 按需安装模型包(如OpenAI)
npm install @langchain/openai
环境变量配置
- 设置
API
密钥(如OpenAI
):通过.env
文件或process.env
注入。 TS
配置:修改tsconfig.json
,设置"target": "ES2020"
和"module": "nodenext"
。
获取密钥 API-KEY
推荐无脑白嫖阿里云的体验【API-KEY】,注册并登录阿里百炼,自动免费体验。
【API-KEY】的创建如下:
基础框架生成
# 新建目录
mkdir langchain-node-demo
cd langchain-node-demo
# 初始化项目
npm init -y
# 依赖安装
npm install langchain @langchain/core @langchain/community
# 按需安装模型包(如OpenAI)
npm install @langchain/openai
# 安装环境依赖
npm install dotenv
# 创建.env文件,写入API-KEY
DASHSCOPE_API_KEY=your_api_key_here
LangChain的最小单元开发
Agent开发实战
Agent
的核心能力是根据用户输入自主选择工具(如计算器、搜索引擎)完成任务。
我们来做一个数学计算的 Agent 示例,它需要调用计算器工具。
import { ChatOpenAI } from "@langchain/openai";
import { MemorySaver } from "@langchain/langgraph";
import { HumanMessage } from "@langchain/core/messages";
import { createReactAgent } from "@langchain/langgraph/prebuilt";
import { Calculator } from "@langchain/community/tools/calculator";
import dotenv from "dotenv";
// 加载环境变量
dotenv.config();
// 配置通义千问API
const model = new ChatOpenAI({
modelName: "qwen-max",
temperature: 0.7,
openAIApiKey: process.env.DASHSCOPE_API_KEY,
configuration: {
baseURL: "https://dashscope.aliyuncs.com/compatible-mode/v1",
},
});
// 初始化工具集
// Calculator 数学计算
const tools = [
new Calculator(),
];
// 初始化记忆组件
const memorySaver = new MemorySaver();
// 创建增强型ReAct智能体
// llm: 使用配置好的通义千问模型
// checkpointSaver: 使用记忆组件保存对话检查点
// tools: 集成计算器和搜索工具,增强AI的实际问题解决能力
const agent = createReactAgent({
llm: model,
checkpointSaver: memorySaver,
tools: tools,
verbose: true // 启用详细日志输出
});
// 对话处理函数
async function chat(input, threadId = "default") {
try {
const response = await agent.invoke(
{ messages: [new HumanMessage(input)] },
{
configurable: {
thread_id: threadId,
maxIterations: 3 // 限制最大工具调用次数,防止无限循环
}
}
);
return response.messages[response.messages.length - 1].content;
} catch (error) {
console.error("对话处理出错:", error);
return "抱歉,处理您的请求时出现错误。";
}
}
// 主函数:演示增强型对话系统的使用方法
async function main() {
try {
// 测试基础对话能力
const response1 = await chat("你好,请介绍一下你自己");
console.log("AI response1: ", response1);
// 测试工具使用能力
const response2 = await chat("x = 2; y = 3; 请问 x + y * 2 = ?");
console.log("AI response2: ", response2);
// 测试记忆能力
const response3 = await chat("你能总结一下我们刚才讨论了什么吗?", "default");
console.log("AI response3: ", response3);
} catch (error) {
console.error("执行出错:", error);
}
}
// 运行测试程序
main();
上面案例主要使用了通义千问大模型作为基础,具有以下特点:
- 使用
ChatOpenAI
接入通义千问模型 - 通过
Calculator
工具扩展了数学计算能力 - 使用
MemorySaver
实现对话历史记忆功能 - 采用
ReAct Agent
架构来协调模型、工具和记忆组件 - 提供了三个测试用例:
基础对话
、数学计算
和历史记忆测试
运行结果如下:
对话机器人实现
下面是一个对话机器人实现,需要记忆管理和流式响应能力。
import { ChatOpenAI } from "@langchain/openai";
import { ConversationChain } from "langchain/chains";
import { BufferMemory } from "langchain/memory";
import { HumanMessagePromptTemplate, ChatPromptTemplate } from "@langchain/core/prompts";
import dotenv from "dotenv";
// 加载环境变量
dotenv.config();
// 配置通义千问API
const chatModel = new ChatOpenAI({
modelName: "qwen-max",
temperature: 0.7,
openAIApiKey: process.env.DASHSCOPE_API_KEY,
configuration: {
baseURL: "https://dashscope.aliyuncs.com/compatible-mode/v1",
},
});
// 步骤1:配置带记忆的链
const memory = new BufferMemory({
returnMessages: true, // 保留完整消息对象(适合Chat模型)
memoryKey: "history", // 记忆存储字段名
inputKey: "input" // 输入字段名(需与调用参数一致)
});
// 步骤2:构建对话链
const chain = new ConversationChain({
llm: chatModel,
memory,
prompt: ChatPromptTemplate.fromMessages([
HumanMessagePromptTemplate.fromTemplate(`你是一个客服助手,需友好回答用户问题。
当前对话历史:{history}
用户最新输入:{input}`)
])
});
// 步骤3:实现流式交互
const userInput = "帮我推荐一款轻薄笔记本";
console.log("用户提问:", userInput);
// 调用并监听流事件
const stream = await chain.stream({ input: userInput });
let fullResponse = "";
for await (const chunk of stream) {
if (chunk?.response) {
process.stdout.write(chunk.response); // 逐词输出
fullResponse += chunk.response;
}
}
// 记录完整响应到记忆
await memory.saveContext(
{ input: userInput },
{ output: fullResponse }
);
运行结果如下:
主要功能是创建一个具有记忆功能
的客服助手
。它使用 BufferMemory
来存储对话历史,
通过 ConversationChain
构建对话链,并实现了流式输出响应的功能。
当用户输入问题时,助手会基于历史对话记录
友好地回答用户的问题。
核心机制解析:
-
记忆管理:
BufferMemory
:保存原始对话记录,适用于短对话。EntityMemory
:提取实体信息(如用户偏好),适合个性化场景。
-
流式响应原理:
通过stream: true
启动 Server-Sent Events (SSE
),模型生成结果逐词返回。前端可通过EventSource
监听:// 前端示例(Next.js API路由) export async function POST(req) { const encoder = new TextEncoder(); const stream = new ReadableStream({ async start(controller) { const model = new ChatOpenAI({ streaming: true }); const response = await model.stream(await req.json()); for await (const chunk of response) { controller.enqueue(encoder.encode(chunk.content)); } controller.close(); } }); return new Response(stream, { headers: { "Content-Type": "text/event-stream" } }); }
最佳实践与调试技巧
-
Agent稳定性优化
// 错误重试策略 import { Fallbacks } from "langchain/util/fallbacks"; const robustAgent = executor.withFallbacks({ fallbacks: [backupModel], // 备用模型 exceptionsToHandle: ["TimeoutError", "APIError"] }); // 超时控制 const result = await robustAgent.invoke( { input: "查询北京到上海的航班" }, { timeout: 5000 } // 5秒超时 );
-
记忆容量管理
// 限制对话历史长度 const memory = new BufferMemory({ maxTokenLimit: 1000, // 按Token计数 memoryKey: "history", chatHistory: new ChatMessageHistory() }); // 定期清理旧记录 setInterval(() => { memory.chatHistory.clear(); }, 60 * 60 * 1000); // 每小时重置
-
性能监控
// LangSmith跟踪 import { trace } from "@langchain/core/tracers"; chain.withConfig({ callbacks: [new trace.LangChainTracer({ projectName: "customer-service" })] }); // 日志记录中间件 chain.withConfig({ middleware: [{ async invoke(input, next) { console.log("收到输入:", input); const output = await next(input); console.log("生成输出:", output); return output; } }] });
LangChain的进阶开发
文本处理组件
实现一个敏感词过滤处理器,自动清洗输入/输出文本:
import { BaseDocumentTransformer } from "langchain-core/documents";
import { Document } from "@langchain/core/documents";
/**
* 敏感词过滤器类
* 继承自BaseDocumentTransformer,用于文本内容的敏感词过滤
*/
export class SensitiveFilter extends BaseDocumentTransformer {
/**
* 构造函数
* @param {string[]} blacklist - 需要过滤的敏感词列表
* @param {Object} options - 配置选项
* @param {boolean} options.useRegex - 是否启用正则表达式匹配
*/
constructor(blacklist, options = {}) {
super();
this.blacklist = blacklist;
this.useRegex = options.useRegex || false;
}
/**
* 批量处理文档
* @param {Document[]} docs - 需要处理的文档数组
* @returns {Promise<Document[]>} 处理后的文档数组
*/
async processDocuments(docs) {
return Promise.all(docs.map(doc => this.processDocument(doc)));
}
/**
* 处理单个文档
* @param {Document} doc - 需要处理的文档
* @returns {Promise<Document>} 处理后的文档
*/
async processDocument(doc) {
const filteredContent = this.filterText(doc.pageContent);
return {
...doc,
pageContent: filteredContent
};
}
/**
* 文本过滤处理
* @param {string} content - 需要过滤的文本内容
* @returns {string} 过滤后的文本
*/
filterText(content) {
// 使用黑名单进行过滤
return this.blacklist.reduce((text, word) => {
if (this.useRegex) {
try {
const regex = new RegExp(word, "gi"); // 全局、忽略大小写匹配
return text.replace(regex, "***");
} catch (e) {
console.warn(`无效的正则表达式模式: ${word}`);
return text;
}
}
return text.replace(new RegExp(word, "gi"), "***");
}, content);
}
/**
* 批量处理文本内容
* @param {string[]} contents - 需要处理的文本数组
* @returns {Promise<string[]>} 处理后的文本数组
*/
async batchProcess(contents) {
return Promise.all(contents.map(content => {
const doc = new Document({ pageContent: content });
return this.processDocument(doc).then(result => result.pageContent);
}));
}
}
// 创建过滤器实例
const filter = new SensitiveFilter(
["暴力", "色情"], // 定义敏感词黑名单
{
whitelist: ["暴力美学"], // 定义白名单例外
useRegex: true, // 启用正则表达式匹配
cacheSize: 1000 // 设置缓存大小
}
);
/**
* 测试函数
* 演示敏感词过滤器的使用方法
*/
async function testFilter() {
// 测试文档处理
const docs = [
new Document({ pageContent: "这是一段暴力美学的描写" }), // 测试白名单
new Document({ pageContent: "这是一段含有暴力内容的文本" }) // 测试敏感词过滤
];
const cleanDocs = await filter.processDocuments(docs);
console.log(cleanDocs);
// 测试批量文本处理
const contents = ["文本1包含暴力", "文本2正常"];
const cleanContents = await filter.batchProcess(contents);
console.log(cleanContents);
}
// 运行测试
testFilter();
这是一个基于黑名单
机制的敏感词过滤器
,支持单个文档
和批量文档
处理。
它可以通过正则表达式匹配敏感词
,将匹配到的内容替换为***
,
同时提供了异步处理
能力,适用于文本内容审核场景
。
运行结果如下:
插件开发(天气API工具)
创建可被 Agent
调用的天气查询插件
import { ChatOpenAI } from "@langchain/openai";
import { HumanMessage } from "@langchain/core/messages";
import { createReactAgent } from "@langchain/langgraph/prebuilt";
import { Calculator } from "@langchain/community/tools/calculator";
import { Tool } from "langchain/tools";
import axios from "axios";
import dotenv from "dotenv";
// 加载环境变量
dotenv.config();
/**
* WeatherAPI工具类
* 用于获取指定城市的实时天气数据
*/
export class WeatherAPI extends Tool {
constructor() {
super();
this.name = "get_weather";
this.description = "获取指定城市的实时天气数据";
}
/**
* 调用天气API获取数据
* @param {string} city - 城市名称
* @returns {Promise<string>} 格式化的天气信息
*/
async _call(city) {
if (!process.env.WEATHER_API_KEY) {
throw new Error("未配置WEATHER_API_KEY环境变量");
}
try {
const response = await axios.get("https://api.weatherapi.com/v1/current.json", {
params: {
key: process.env.WEATHER_API_KEY,
q: city,
lang: "zh"
}
});
const { temp_c, condition } = response.data.current;
return `${city}的天气情况:\n温度:${temp_c}℃\n天气状况:${condition.text}`;
} catch (error) {
if (error.response) {
// API响应错误
const status = error.response.status;
if (status === 401) {
return "API密钥无效或已过期";
} else if (status === 400) {
return "请求参数无效,请检查城市名称";
}
}
// 网络错误或其他未知错误
return "无法获取天气信息,请稍后重试";
}
}
}
// 配置通义千问API
const chatModel = new ChatOpenAI({
modelName: "qwen-max",
temperature: 0.7,
openAIApiKey: process.env.DASHSCOPE_API_KEY,
configuration: {
baseURL: "https://dashscope.aliyuncs.com/compatible-mode/v1",
},
});
// 注册到Agent
const tools = [new WeatherAPI(), new Calculator()];
// 创建增强型ReAct智能体
// llm: 使用配置好的通义千问模型
// checkpointSaver: 使用记忆组件保存对话检查点
// tools: 集成计算器和搜索工具,增强AI的实际问题解决能力
const agent = createReactAgent({
llm: chatModel,
tools: tools,
verbose: true // 启用详细日志输出
});
// 对话处理函数
async function chat(input, threadId = "default") {
try {
const response = await agent.invoke(
{ messages: [new HumanMessage(input)] },
{
configurable: {
thread_id: threadId,
maxIterations: 3 // 限制最大工具调用次数,防止无限循环
}
}
);
return response.messages[response.messages.length - 1].content;
} catch (error) {
console.error("对话处理出错:", error);
return "抱歉,处理您的请求时出现错误。";
}
}
// 主函数
async function main() {
try {
// 因为WEATHER_API_KEY是国外的免费key,所以要写城市的英文缩写
const response = await chat("今天BEIJING的天气怎么样?气温是多少度?比SHANGHAI的气温高嘛?");
console.log("AI response: ", response);
} catch (error) {
console.error("执行出错:", error);
}
}
// 运行测试程序
main();
这是一个基于 WeatherAPI
的天气查询
助手,支持实时获取城市天气数据并进行温度对比。
技术实现上使用了ReAct
智能体架构,集成了天气查询
和计算器工具
,
通过Tool
类扩展实现天气API调用,支持中文输出,并包含完善的错误处理机制。
运行结果如下:
性能优化策略
LangSmith全链路监控
配置请求追踪与性能分析:
import { trace } from "@langchain/core/tracers";
// 初始化监控
const tracer = new trace.LangChainTracer({
projectName: "prod-customer-service",
apiUrl: "https://api.smith.langchain.com",
apiKey: process.env.LANGCHAIN_API_KEY,
});
// 集成到链
const chain = customerServiceChain.withConfig({
callbacks: [tracer],
});
// 分析数据指标
/*
1. 平均响应时间
2. 工具调用次数统计
3. 错误类型分布
*/
Redis缓存优化
减少重复模型调用开销:
import { RedisCache } from "langchain/cache/redis";
import { createClient } from "redis";
// 连接Redis
const client = createClient({ url: "redis://localhost:6379" });
await client.connect();
// 配置缓存
const cache = new RedisCache(client);
const llm = new ChatOpenAI({
cache,
cacheKeyBuilder: (input: string) =>
crypto.createHash("md5").update(input).digest("hex") // 输入内容哈希为Key
});
// 自动缓存结果
await llm.invoke("解释量子计算原理"); // 首次调用访问API
await llm.invoke("解释量子计算原理"); // 第二次直接从缓存读取
框架集成方案
Next.js全栈应用开发
构建AI内容生成平台:
// app/api/generate/route.ts
import { NextResponse } from "next/server";
import { StreamingTextResponse } from "ai";
import { ChatOpenAI } from "@langchain/openai";
export async function POST(req: Request) {
const { messages } = await req.json();
const model = new ChatOpenAI({
streaming: true,
callbacks: [{
async handleLLMNewToken(token) {
// 实时推送Token到前端
controller.enqueue(encoder.encode(token));
}
}]
});
const stream = new ReadableStream({
async start(controller) {
const encoder = new TextEncoder();
await model.stream(messages);
controller.close();
}
});
return new StreamingTextResponse(stream);
}
前端交互(React)
// components/AIChat.tsx
import { useChat } from "ai/react";
export function AIChat() {
const { messages, input, handleInputChange, handleSubmit } = useChat();
return (
<div className="chat-container">
{messages.map(m => (
<div key={m.id}>{m.content}</div>
))}
<form onSubmit={handleSubmit}>
<input
value={input}
onChange={handleInputChange}
placeholder="输入您的问题..."
/>
<button type="submit">发送</button>
</form>
</div>
);
}
企业级架构设计
推荐架构拓扑图:
前端(React/Vue)
↓ HTTP/WebSocket
Edge Function (Vercel/Cloudflare) ← 流式响应优化
↓ REST API
Node.js服务层
↓ LangChain Core
工具层 → 数据库(Supabase) / 本地模型(Ollama) / 外部API
↓ 缓存层(Redis)
监控层 → LangSmith / Prometheus/Grafana
关键设计原则:
- 分层隔离:
业务逻辑
与AI 模型解耦
,便于替换算法
- 弹性扩展:
无状态服务层
+水平扩展
的模型推理集群
- 安全防护:
请求限流
、输入消毒
、敏感词过滤
常见问题
- 兼容性报错:Node.js 18+无需 polyfill,低版本需安装
node-fetch
和core-js
。 - 依赖冲突:确保所有包使用同一版本的
@langchain/core
。 - 流式响应失败:检查模型参数
stream: true
及网络连接。
推荐资料
官方资源
- LangChain.js官方文档。
- GitHub仓库:langchain-js(含代码示例)。
学习平台
- Deeplearning.ai短期课程:LCEL与RAG技术实战
- Scrimba交互教程:Learn LangChain.js。
社区与案例
- GitHub示例库:langchain-examples。
- Stack Overflow:搜索
langchain.js
标签解决常见问题。
扩展阅读
我的Demo代码库
请先获取一个OPENAI_API_KEY
,然后执行以下命令 node 相关命令,