# 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)。