前端学AI:基于Node.js的Langchain开发-简单实战应用

1,149 阅读10分钟

前端学AI:基于Node.js的Langchain开发-简单实战应用

本文主要介绍 LangChain 的实战开发,包括:开发准备阶段、Agent 开发实战、对话机器人实现、最佳实践与调试技巧、文本处理组件、插件开发(天气API工具)、性能优化策略和框架集成方案。

供自己以后查漏补缺,也欢迎同道朋友交流学习。

引言

上一篇讲了些基础知识,介绍了简单概念和基础的 API,本章就着手实战开发。

本文主要介绍 LangChain 的实战开发,包括:开发准备阶段、Agent 开发实战、对话机器人实现、最佳实践与调试技巧、文本处理组件、插件开发(天气API工具)、性能优化策略和框架集成方案。

LangChain的开发准备

环境要求

  • Node.js版本:官方推荐 18.x 及以上(Node.js 16需额外polyfill)。
  • 包管理工具:npm/yarn/pnpm 任选,推荐 npm
  • 兼容性:支持 ESMCommonJS 模块。

依赖安装

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】的创建如下:

set-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();

上面案例主要使用了通义千问大模型作为基础,具有以下特点:

  1. 使用 ChatOpenAI 接入通义千问模型
  2. 通过 Calculator 工具扩展了数学计算能力
  3. 使用 MemorySaver 实现对话历史记忆功能
  4. 采用 ReAct Agent 架构来协调模型、工具和记忆组件
  5. 提供了三个测试用例:基础对话数学计算历史记忆测试

运行结果如下

langchain-1

对话机器人实现

下面是一个对话机器人实现,需要记忆管理流式响应能力。

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 }
);

运行结果如下

langchain-2

主要功能是创建一个具有记忆功能客服助手。它使用 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" } });
    }
    

最佳实践与调试技巧

  1. 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秒超时
    );
    
  2. 记忆容量管理

    // 限制对话历史长度
    const memory = new BufferMemory({
      maxTokenLimit: 1000,  // 按Token计数
      memoryKey: "history",
      chatHistory: new ChatMessageHistory()
    });
    
    // 定期清理旧记录
    setInterval(() => {
      memory.chatHistory.clear();
    }, 60 * 60 * 1000); // 每小时重置
    
  3. 性能监控

    // 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();

这是一个基于黑名单机制的敏感词过滤器,支持单个文档批量文档处理。

它可以通过正则表达式匹配敏感词,将匹配到的内容替换为***

同时提供了异步处理能力,适用于文本内容审核场景

运行结果如下

langchain-3

插件开发(天气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调用,支持中文输出,并包含完善的错误处理机制。

运行结果如下

langchain-4

性能优化策略

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

关键设计原则

  1. 分层隔离业务逻辑AI 模型解耦,便于替换算法
  2. 弹性扩展无状态服务层 + 水平扩展模型推理集群
  3. 安全防护请求限流输入消毒敏感词过滤

常见问题

  • 兼容性报错:Node.js 18+无需 polyfill,低版本需安装 node-fetchcore-js
  • 依赖冲突:确保所有包使用同一版本的 @langchain/core
  • 流式响应失败:检查模型参数 stream: true 及网络连接。

推荐资料

官方资源

学习平台

社区与案例

  • GitHub示例库langchain-examples
  • Stack Overflow:搜索langchain.js标签解决常见问题。

扩展阅读

我的Demo代码库

请先获取一个OPENAI_API_KEY,然后执行以下命令 node 相关命令,

专栏系列