前端学AI之LangChain.js入门教程:实现智能对话机器人

作为一名前端开发老程序员,这次给大家更新一个前端学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-turboqwen-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,其中:

  1. 接口设计差异 :
  • BaseLLM :面向纯文本输入( string[] )
  • BaseChatModel :面向结构化消息( BaseMessage[] )
  1. 使用场景 :
  • 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

效果截图

相关推荐
struggle202541 分钟前
DeepSpeed 是一个深度学习优化库,使分布式训练和推理变得简单、高效和有效
人工智能·深度学习
猎嘤一号1 小时前
使用 PyTorch 和 TensorBoard 实时可视化模型训练
人工智能·pytorch·python
从零开始学习人工智能2 小时前
多模型协同:基于 SAM 分割 + YOLO 检测 + ResNet 分类的工业开关状态实时监控方案
人工智能·yolo·分类
s153352 小时前
12-OPENCV ROCKX项目 人脸拍照
人工智能·opencv·计算机视觉
alasnot3 小时前
BERT情感分类
人工智能·深度学习·bert
只有左边一个小酒窝3 小时前
(九)现代循环神经网络(RNN):从注意力增强到神经架构搜索的深度学习演进
人工智能·rnn·深度学习
UQI-LIUWJ4 小时前
论文略读:REEF: Representation Encoding Fingerprints for Large Language Models
人工智能·语言模型·自然语言处理
强盛小灵通专卖员4 小时前
基于YOLOv12的电力高空作业安全检测:为电力作业“保驾护航”,告别安全隐患!
人工智能·深度学习·安全·yolo·核心期刊·计算机期刊
万米商云4 小时前
AI推荐系统演进史:从协同过滤到图神经网络与强化学习的融合
人工智能·深度学习·神经网络