# rag-starter **Repository Path**: harlem097/rag-starter ## Basic Information - **Project Name**: rag-starter - **Description**: RAG 知识库问答系统 - **Primary Language**: JavaScript - **License**: Apache-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 1 - **Forks**: 0 - **Created**: 2026-01-29 - **Last Updated**: 2026-02-10 ## Categories & Tags **Categories**: Uncategorized **Tags**: JavaScript, langchain, Nodejs, React, Express ## README # RAG 知识库问答系统 ## 📐 1. 整体架构说明 系统采用经典的 **RAG (Retrieval-Augmented Generation)** 架构,前后端分离。 * **前端层 (Frontend)**: 基于 **React 19 + Vite**,使用 **Ant Design** 组件库。负责用户交互、知识库上传和 AI 回答展示(包含引用来源)。不包含任何 AI 逻辑。 * **后端层 (Backend)**: 基于 **Node.js + Express**。负责 API 服务、文件处理、向量化及 LangChain 编排。 * **RAG 引擎**: 使用 **LangChain** 框架,模块化设计了 Loader (加载)、Splitter (切分)、Embedding (向量化)、VectorStore (存储) 和 Retriever (检索)。 ## 📂 2. 后端目录结构 (`server/`) 严格遵循分层和单一职责原则设计: ```text server/ ├── index.js # 程序入口,中间件配置 ├── package.json # 依赖管理 (ES Modules) ├── routes/ # API 路由层 │ ├── chat.js # 问答接口 (/chat) │ ├── ingest.js # 知识接入接口 (/ingest) │ └── knowledge.js # 知识库管理接口 └── rag/ # RAG 核心层 ├── chain/ # LangChain 编排 │ └── index.js ├── embeddings/ # 向量化模型适配 │ └── index.js ├── loaders/ # 多格式文档加载器 (PDF/TXT) │ └── index.js ├── prompt/ # Prompt 模板管理 (强约束) │ └── index.js ├── retriever/ # 检索策略配置 │ └── index.js ├── splitter/ # 文本切分策略 │ └── index.js └── vectorstore/ # 向量数据库接口 (当前实现为 Memory/Local) └── index.js ``` ## 🔁 3. RAG 核心流程说明 1. **知识入库 (Ingest)**: * 用户上传文件 -> `routes/ingest` 接收。 * `loaders` 识别文件类型 (PDF, TXT, MD) 并加载内容,附加 Metadata。 * `splitter` 使用 `RecursiveCharacterTextSplitter` 将文本切分为 Chunk (1000/200)。 * `embeddings` 将 Chunk 转化为向量。 * `vectorstore` 存储向量及原始文本。 2. **问答流程 (Chat)**: * 用户提问 -> `routes/chat` 接收。 * `retriever` 在向量库中检索 Top-K (默认 3 条) 相关片段。 * `chain` 拼接 Prompt (包含 context)。 * LLM 生成回答。 * API 返回 `answer` 和 `sources`。 ## 🧠 4. LangChain Chain 设计 使用了现代化 LCEL (LangChain Expression Language) 或标准 Chain 组合: * **核心 Chain**: `createRetrievalChain` + `createStuffDocumentsChain`。 * **Prompt**: 注入严厉的系统指令,防止模型幻觉。 * **Retriever**: 与具体的 VectorStore 解耦,支持配置 TopK。 ## 🧩 5. 关键代码示例 ### A. 强约束 Prompt (`server/rag/prompt/index.js`) 这是防止模型“胡编乱造”的核心。 ```javascript import { ChatPromptTemplate } from "@langchain/core/prompts"; export const getPrompt = () => { const systemTemplate = `你是一个基于知识库的问答助手,只能使用提供的资料进行回答。 如果你无法从提供的资料中找到答案,请明确回答“无法从知识库中得到答案”,不要编造信息。 不允许引入外部常识。 回答需结构清晰、语言专业。 {context} Question: {input}`; return ChatPromptTemplate.fromTemplate(systemTemplate); }; ``` ### B. RAG Chain 构建 (`server/rag/chain/index.js`) ```javascript import { ChatOpenAI } from "@langchain/openai"; import { createStuffDocumentsChain } from "langchain/chains/combine_documents"; import { createRetrievalChain } from "langchain/chains/retrieval"; import { getRetriever } from "../retriever/index.js"; import { getPrompt } from "../prompt/index.js"; export const getChain = async () => { const llm = new ChatOpenAI({ modelName: "gpt-3.5-turbo", temperature: 0, // 设为 0 以保证回答的确定性 }); const retriever = await getRetriever(); const prompt = getPrompt(); // 文档处理链 const combineDocsChain = await createStuffDocumentsChain({ llm, prompt, }); // 检索增强链 const chain = await createRetrievalChain({ retriever, combineDocsChain, }); return chain; }; ``` ### C. 知识入库 API (`server/routes/ingest.js`) ```javascript // ...imports router.post('/', upload.single('file'), async (req, res) => { // 1. 加载 const rawDocs = await loadDocument(req.file.path, null, { originalName: req.file.originalname }); // 2. 切分 const splitDocs = await splitDocuments(rawDocs); // 3. 向量化并存储 await addDocumentsToStore(splitDocs); // ...cleanup res.json({ success: true, chunks: splitDocs.length }); }); ``` ## 🎨 6. 前端调用方式 前端实现了 `ChatInterface` 组件,接收用户输入并展示结果及引用。 ```javascript // POST /api/chat 调用示例 const response = await axios.post('/api/chat', { question: "什么是 RAG?", knowledgeBaseId: 'default' }); // 展示回答 console.log(response.data.answer); // 展示引用来源 response.data.sources.forEach(source => { console.log(`来源: ${source.metadata.originalName}`); console.log(`内容: ${source.content}`); }); ``` ## ⚠️ 7. 常见坑 & 设计注意点 1. **Metadata 丢失问题**: 在切分文档 (`splitter`) 时,务必保留 `metadata` (如文件名、页码)。我在 `loaders` 中已经做了处理,确保每个 Chunk 都携带来源信息,否则前端无法展示引用。 2. **向量库持久化**: 当前演示使用的是 `MemoryVectorStore`,服务器重启后数据会丢失。而在生产环境中,只需替换 `server/rag/vectorstore/index.js` 中的实现为 `Chroma`、`Pinecone` 或 `pgvector` 即可,其他业务代码无需修改。 3. **Prompt 注入**: 系统 Prompt 必须放在 `{context}` 之前,并且使用明确的 XML 标签(如 ``)包裹检索内容,能有效减少 LLM 混淆指令和数据的情况。 4. **Token 限制**: 如果一次性检索回来的文档过长 (TopK 设置过大),可能会超出 LLM 的上下文窗口。建议在生产环境使用 `MapReduce` 或 `Refine` 类型的 Chain,或者使用支持长上下文的模型 (如 GPT-4-turbo / Claude 3)。