从 0 到 1 搭建 RAG 应用:用 LangChain + Chroma + qwen-plus 实现《红楼梦》问答

本文会基于《红楼梦》小说文本构建一个 RAG 系统。使用到了 Langchain + Chroma + qwen-plus 。

Langchain 是一个模块化、可扩展的开源框架,专为简化大型语言模型(LLM)应用开发而设计,通过提供标准化组件和抽象层,帮助开发者高效构建智能对话系统、知识增强应用和 AI 智能体。

Chroma 是一款开源的 AI 原生向量数据库,专为高效存储和检索高维向量数据设计,特别适合语义相似性搜索场景,是构建 RAG(检索增强生成)系统最流行的工具之一。

qwen-plus 是阿里巴巴推出的大语言模型。

构建 RAG 系统的主要步骤:

  • 初始化向量数据库

  • 文本切分并写入向量数据库

  • 检索向量数据库,结合 LLM 生成答案

初始化向量数据库

创建 Embedding 对象,供后续 Chroma 入库和检索使用:

ts 复制代码
const embeddings = new OpenAIEmbeddings({
  apiKey: dashscopeApiKey,
  model: "text-embedding-v4",
  batchSize: 10,
  configuration: {
    baseURL: "https://dashscope.aliyuncs.com/compatible-mode/v1",
  },
});

初始化向量数据库:

ts 复制代码
import { Chroma } from "@langchain/community/vectorstores/chroma";
import { OpenAIEmbeddings } from "@langchain/openai";

let vectorStore: Chroma;

async function main(): Promise<void> {
  // 初始化向量数据库
  vectorStore = await Chroma.fromExistingCollection(embeddings, {
    collectionName: "hongloumeng",
    url: "http://localhost:8000",
  });
}

fromExistingCollection 的作用是: 连接一个已经存在的 Chroma 集合,并返回可直接读写/检索的 vectorStore 实例 。

  • collectionName: "hongloumeng" :指定要连接的集合名
  • url: "http://localhost:8000" :指定 Chroma 服务地址
  • embeddings :绑定向量化模型,后续写入与检索都要用

文本切分并写入向量数据库

这里创建 npm run ingest 的 npm script 命令,用于完成《红楼梦》小说的文本切分,写入 Chroma 向量数据库,完成知识库"入库"步骤。

ts 复制代码
async function ingestData() {
  console.log("📚 正在读取《红楼梦》文本...");
  const rawText = fs.readFileSync("./data/hong.txt", "utf-8");

  console.log("🔪 正在进行文本分割...");
  const textSplitter = new RecursiveCharacterTextSplitter({
    chunkSize: 1000, // 每个块的大小
    chunkOverlap: 200, // 块之间的重叠,保持上下文连贯性
    separators: ["\n\n", "\n", "。", "!", "?", ";", ",", " "], // 针对中文优化的分隔符
  });

  const docs = await textSplitter.createDocuments([rawText]);

  console.log(`📝 分割完成,共 ${docs.length} 个片段。正在存入向量数据库...`);

  // 将文档添加到现有的 Chroma 集合中
  await vectorStore.addDocuments(docs);

  console.log("✅ 数据入库完成!");
}
  • 读取《红楼梦》文本文件:./data/hong.txt

  • 使用 RecursiveCharacterTextSplitter 对文本进行切分,将文本切分为多个片段,每个片段大小为 1000 字节,重叠度为 200 字节。

  • 生成文档数组: createDocuments([rawText])

  • vectorStore.addDocuments(docs) 写入向量库

并将 ingestData 封装成 npm script 命令,方便调用:

ts 复制代码
async function main(): Promise<void> {
  const command = process.argv[2];
  if (command === "ingest") {
    await ingestData();
    return;
  }
}

process.argv[2] 的作用是: 读取命令行传给脚本的第一个"业务参数" 。

在 Node 里:

  • process.argv[0] :Node 可执行文件路径
  • process.argv[1] :当前脚本路径
  • process.argv[2] :你手动传入的第一个参数

比如执行:

  • pnpm ingest 时,process.argv[2] 就是 ingest

执行 pnpm ingest 命令,将《红楼梦》的文本切分并写入 Chroma 向量数据库。

注意在执行 pnpm ingest 命令前,需要先启动本地 Chroma 数据库服务:

bash 复制代码
pnpm exec chroma run --path ./getting-started

上面命令的作用:启动本地 Chroma 数据库服务,并把数据持久化到 ./getting-started 目录 。

检索向量数据库,结合 LLM 生成答案

基于 Chroma 检索到的《红楼梦》相关片段,调用大模型生成并返回答案。

  • 设置检索器,获取最相关的 4 个片段

  • 定义提示词模板

  • 创建文档组合链

  • 创建检索链

  • 执行并返回

ts 复制代码
async function chatWithHongLouMeng(query: string) {
  console.log(`\n🔍 正在检索关于 "${query}" 的内容...`);

  // 设置检索器,获取最相关的 4 个片段
  const retriever = vectorStore.asRetriever({
    searchType: "similarity",
    k: 4,
  });

  // 定义提示词模板
  const prompt = ChatPromptTemplate.fromTemplate(`
    你是一个精通《红楼梦》的文学助手。请根据以下检索到的上下文信息回答用户的问题。
    如果上下文中没有相关信息,请直接说明你不知道,不要编造内容。

    上下文信息:
    {context}

    用户问题:{input}
    回答:
  `);

  // 创建文档组合链
  const combineDocsChain = await createStuffDocumentsChain({
    llm: model,
    prompt,
    outputParser: new StringOutputParser(),
  });

  // 创建检索链
  const chain = await createRetrievalChain({
    retriever,
    combineDocsChain,
  });

  // 执行并返回
  const response = await chain.invoke({ input: query });
  return response.answer;
}

将 RAG 能力封装成 API

使用 express 将 RAG 系统封装成 API,供前端调用。

  • 创建 express 服务器,监听 18000 端口

  • 配置 json 解析中间件和 CORS 响应头

  • 将 chatWithHongLouMeng 函数封装成 POST 请求,供前端调用

ts 复制代码
const PORT = 18000;

const app = express();

// 初始化 Express 服务并配置基础中间件(JSON 解析 + CORS 响应头) 。
app.use(express.json());
app.use((_, res, next) => {
  res.header("Access-Control-Allow-Origin", "*");
  res.header("Access-Control-Allow-Methods", "POST,OPTIONS");
  res.header("Access-Control-Allow-Headers", "Content-Type");
  next();
});

app.post("/hongloumeng/chat", async (req, res) => {
  const questionParam = req.body?.question;
  const question =
    typeof questionParam === "string" ? questionParam.trim() : "";

  if (!question) {
    res.status(400).json({
      error: "Missing body parameter `question`",
    });
    return;
  }

  try {
    const answer = await chatWithHongLouMeng(question);
    res.json({ question, answer });
  } catch (error) {
    console.error("chat api failed:", error);
    res.status(500).json({
      error: "Failed to generate answer",
    });
  }
});

app.listen(PORT, () => {
  console.log(`API server running at http://localhost:${PORT}`);
  console.log(
    'Example: POST /hongloumeng/chat with body {"question":"贾宝玉的外貌如何?"}'
  );
});

RAG 系统前端部分开发

前端部分使用的技术栈为 Vue.js + Vite + Element Plus

由于 qwen-plus 大模型返回的是 markdown 格式的文本,要将 markdown 文本展示到 Web 上,则需要借助 marked 库,其作用是将 markdown 转换成 html 。

同时为防止 XSS 攻击,需要使用 dompurifymarked 解析出的 html 字符串进行消毒处理,过滤 html 中的恶意内容。

dompurify 通过白名单机制严格过滤 html ,只保留安全的标签和属性,移除所有潜在危险内容。

ts 复制代码
import { marked } from "marked";
import DOMPurify from "dompurify";

function renderMarkdown(content: string): string {
  const html = marked.parse(content, { breaks: true });
  return DOMPurify.sanitize(typeof html === "string" ? html : "");
}

RAG 系统后端服务监听 18000 端口,为了让前端调试服务器能够访问后端接口,需要设置 Vite 的代理:

ts 复制代码
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";

export default defineConfig({
  plugins: [vue()],
  server: {
    port: 5173,
    proxy: {
      "/hongloumeng": {
        target: "http://localhost:18000",
        changeOrigin: true,
      },
    },
  },
});

然后就能在前端正常调用 /hongloumeng/chat 接口:

ts 复制代码
type Role = "user" | "assistant";

interface Message {
  role: Role;
  content: string;
}

const messages = ref<Message[]>([
  {
    role: "assistant",
    content: "你好,我是红楼梦问答助手。你想了解哪位人物?",
  },
]);

const response = await fetch("/hongloumeng/chat", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ question: input }),
});
const data = (await response.json()) as { answer?: string; error?: string };

if (!response.ok) {
  throw new Error(data.error ?? "请求失败");
}

messages.value.push({
  role: "assistant",
  content: data.answer ?? "未返回答案",
});

上面就是《红楼梦》问答助手前端部分核心代码,下面正式执行

最终《红楼梦》问答助手的效果如下:

总结

本文介绍了如何使用 Langchain + Chroma + qwen-plus 构建一个《红楼梦》RAG 问答系统的完整流程:

  1. 初始化向量数据库 - 配置文本嵌入模型和连接本地 Chroma
  2. 数据入库 - 切分《红楼梦》文本并写入向量数据库
  3. 检索生成 - 根据用户问题检索相关片段,调用 LLM 生成回答
  4. 后端 API - 用 Express 封装 RAG 能力为 HTTP 接口
  5. 前端展示 - Vue.js + Vite 实现聊天界面,使用 marked 和 dompurify 处理 markdown 并防 XSS

核心工作流程:文本入库 → 语义检索 → LLM 生成答案 → API 调用 → 前端展示

相关推荐
战族狼魂2 分钟前
高频面试题精选:分治与AI Agent架构
人工智能·算法·大模型·大语言模型
zSD55rt5a1 小时前
方差在扩散模型保护中的作用
人工智能·harmonyos
人生百态,人生如梦1 小时前
情感交互仿生人从技术到落地构想3——技术交流贴(2026.7)
人工智能·机器学习·人机交互·交互·具身智能
瑞瑞小同学1 小时前
处理echarts x轴内容过多,重叠问题
前端·javascript·echarts
用户652238438111 小时前
为什么成熟的 LLM 应用,都把"后端地址"做成可切换的配置
人工智能
Aa99883341 小时前
AI视觉检测设备厂家的技术评估框架——以及AI视觉检测的检出边界与适用条件
人工智能
wuhanzhanhui1 小时前
当AI遇见储能!2026武汉国际数字能源产业与新型电力及储能技术展览会
人工智能·能源
Tangyuewei2 小时前
给 AI 套上缰绳:Harness Engineering 是什么
人工智能
AI棒棒牛2 小时前
第11讲 | 目标检测任务:边界框、IoU、类别与置信度
人工智能·yolo·目标检测·yolo26
allione2 小时前
AI时代测试方向AI for Testing和Testing for AI
人工智能·ai测试