不用向量数据库,手写一个最小 RAG 问答系统
最近在学习 RAG。为了理解 RAG 的基本流程,我没有一开始就使用 LangChain、LlamaIndex 或向量数据库,而是使用 Python 标准库实现了一个最小版本。
项目代码:
GitHub:rag-qa-learning
一、RAG 是什么
RAG 的全称是 Retrieval-Augmented Generation,中文一般翻译为“检索增强生成”。
它的基本流程是:
用户问题 ↓ 从文档中检索相关片段 ↓ 把相关片段交给大模型 ↓ 生成带上下文的回答普通大模型只能根据训练数据回答问题。如果问题涉及自己的文档、公司资料或最新内容,模型可能不知道,甚至会产生错误信息。
RAG 会先从用户提供的文档中找到相关内容,再根据这些内容回答问题。
二、这个项目实现了什么
这个学习项目实现了以下功能:
- 在网页中粘贴文档;
- 给文档设置来源名称;
- 按段落和句子切分文本;
- 建立内存中的文档索引;
- 使用关键词匹配检索相关片段;
- 返回检索到的内容;
- 显示引用来源;
- 提供一个本地网页界面。
启动后访问:
http://127.0.0.1:8097/三、项目结构
项目目前比较简单:
rag-qa-stage07/ ├── README.md ├── server.py └── .gitignoreserver.py同时包含:
- HTTP 服务;
- 网页界面;
- 文档切分;
- 关键词提取;
- 文档检索;
- JSON 接口。
四、如何运行
环境要求:
Python 3.10+启动程序:
cd rag-qa-stage07 python3 server.py然后打开浏览器:
http://127.0.0.1:8097/左侧输入文档内容,填写来源名称,点击“建立索引”。
右侧输入问题,点击“检索并回答”。
五、文档切分
项目使用chunk_text函数切分文档:
def chunk_text(text, source='粘贴文档'): parts = [ p.strip() for p in re.split( r'\n\s*\n|(?<=[。!?.!?])\s+', text ) if p.strip() ] return [ { 'id': len(DOCS) + i + 1, 'source': source, 'text': p } for i, p in enumerate(parts) ]这里使用正则表达式按照两种情况切分:
- 空行;
- 中英文句号、问号和感叹号。
例如:
退款期限是七天。 超过七天后不支持退款。会被切成两个文档片段。
真实项目中的文本切分通常还会考虑:
- 标题层级;
- 段落结构;
- Token 数量;
- 上下文重叠;
- Markdown 和 HTML 结构;
- PDF 页面和表格。
六、关键词检索
项目使用terms函数提取关键词:
def terms(s): return set( re.findall(r'[\w一-龥]{2,}', s.lower()) )它会提取长度至少为两个字符的中英文词语。
检索函数如下:
def retrieve(q, k=4): qt = terms(q) scored = [] for d in DOCS: dt = terms(d['text']) score = len(qt & dt) / (len(qt) or 1) if score: scored.append((score, d)) return [ d for _, d in sorted( scored, key=lambda x: x[0], reverse=True )[:k] ]计算方式比较简单:
问题关键词和文档关键词的交集数量 ÷ 问题关键词数量交集越多,分数越高,越可能被返回。
七、为什么要返回引用片段
项目不会只返回一个答案,还会返回检索到的原文片段:
{ "answer": "根据检索到的内容:...", "hits": [ { "source": "product.md", "text": "退款期限是七天" } ] }这样用户可以看到答案来自哪份文档,也可以检查回答是否符合原文。
这对于企业知识库很重要,因为 RAG 系统不仅要回答问题,还要让用户知道答案依据是什么。
八、这个项目还不是真正的生产级 RAG
当前版本主要用于学习 RAG 的基础流程,只是一个运行小demo,还存在一些限制:
- 文档只保存在内存中;
- 程序重启后索引会丢失;
- 使用关键词匹配,没有使用 Embedding;
- 没有接入向量数据库;
- 没有调用大语言模型;
- 没有用户权限;
- 没有文档版本管理;
- 没有敏感信息过滤;
- 没有完善的评测集;
- 没有流式输出。
因此它更适合作为理解原理的最小 Demo。
九、下一步改进方向
后续可以按照下面的顺序升级:
1. 增加 Markdown、PDF 和 Word 导入
目前只能在网页中粘贴文本,后续可以使用解析器读取文件。
2. 使用 Embedding
将关键词匹配替换为向量相似度检索,支持语义相近但关键词不同的问题。
3. 接入向量数据库
可以尝试:
- FAISS;
- Chroma;
- Milvus;
- pgvector。
4. 接入大语言模型
将检索到的片段放入 Prompt,让大模型根据上下文生成自然语言回答。
5. 增加评测
可以使用:
- Recall@K;
- Hit@K;
- MRR;
- 答案准确率;
- 引用正确率。
十、总结
通过这个项目,我理解了一个最小 RAG 系统至少包含以下几个步骤:
文档导入 → 文本切分 → 建立索引 → 检索相关片段 → 返回引用 → 生成回答虽然这个项目还没有使用向量数据库和大语言模型,但它帮助我理解了 RAG 的核心数据流。后续使用 LlamaIndex、LangChain 等框架时,可以更容易理解它们封装了哪些功能。
完整代码:
GitHub - jaijiaying/rag-qa-learning: A learning project for building a minimal RAG question-answering system with Python. · GitHub