从零搭建属于自己的通用RAG智能AI助教

3 阅读10分钟

摘要

  • 一个基于检索增强生成(RAG)的通用知识库问答系统的设计与实现。系统支持用户上传任意格式的文档(PDF、Word、TXT),通过向量检索与大模型生成相结合的方式,实现对私有知识的智能问答。同时,系统集成了飞书机器人,支持群聊实时响应,并扩展了 OCR 多模态识别能力。文章详细阐述了知识库构建、向量检索、三种问答模式、飞书 API 集成中的典型问题及解决方案,以及系统架构设计。该项目可作为个人知识管理、团队文档问答、企业内部知识库等场景的基础设施。*

一、为什么去做这么一个项目

在信息过载的时代,个人和团队积累了大量的文档资料,包括学习笔记、工作日志、技术手册、项目报告等。传统的关键词搜索依赖于精确的词汇匹配,无法理解语义;而通用大模型(如 ChatGPT)虽然具备强大的语言理解能力,却无法访问用户的私有数据。检索增强生成(Retrieval-Augmented Generation, RAG)[1] 提供了一种高效的折衷方案:先对私有文档进行向量化索引,在收到用户提问时检索相关片段,再将片段与问题一同提交给大模型,从而生成基于私有知识的回答。

本文实现了一个完整的 RAG 系统,具备以下特点:

文档格式兼容性:支持 PDF、Word、TXT 格式的上传与文本提取。

动态知识库管理:支持增删文档,自动重建向量索引。

三种问答模式:通用模式(无检索)、一般检索模式(单次 RAG)、深度检索模式(多轮评估与查询改写)。

IM 集成:通过飞书机器人实现群聊实时问答。

多模态扩展:集成 Tesseract OCR,支持图片中的文字识别与检索。

系统采用的技术栈包括:Python 3.9、LangChain、Chroma 向量数据库、阿里通义千问 API、Streamlit、Flask、飞书开放平台、Tesseract OCR、cpolar

二、系统架构

系统整体分为五个层次:

用户交互层:提供 Streamlit Web 界面和飞书机器人两种交互方式。

文档管理层:负责上传、存储、删除文档,并触发知识库重建。

检索引擎层:基于LangChain实现文档加载、分块、向量化,并使用 Chroma 进行相似度检索。

增强决策层:实现三种问答模式,其中深度检索模式包含检索质量评估和查询改写逻辑。

生成层:调用通义千问大模型 API,基于增强后的上下文生成最终回答。

graph TD
用户 --> 检索
检索 --> 向量库
向量库 --> 大模型
大模型 --> 回答

三、知识库构建

3.1 文档加载与文本提取

系统支持 .pdf、.docx、.txt 格式。文本提取使用以下库:

PDF:pypdf.PdfReader

Word:docx.Document

上传的文件存储在 ./教材_库 目录,重名文件自动添加时间戳后缀。每次上传或删除后,系统会重新扫描该目录,合并所有文档的文本内容,并重建向量索引。

3.2 文本分块

由于大模型上下文窗口有限(通常为 4k~8k tokens),需要将长文档切分为若干块。采用 RecursiveCharacterTextSplitter,其分块策略优先按照段落、句子、标点符号进行分割,尽量避免语义断裂。参数配置如下:

    splitter = RecursiveCharacterTextSplitter(
    chunk_size=500,
    chunk_overlap=50,
    separators=["\n\n", "\n", "。", "!", "?", ";", " ", ""]
)

3.3 向量化与存储

采用 HuggingFaceEmbeddings 加载中文嵌入模型 shibing624/text2vec-base-chinese。该模型在中文语义相似度任务上表现良好,维度为 768。所有文本块被转换为向量后存入 Chroma 向量数据库,持久化目录为 ./chroma_db。Chroma 支持高效的近似最近邻(ANN)检索,能够快速返回与用户查询最相似的 k 个文本块。

embeddings = HuggingFaceEmbeddings(model_name="shibing624/text2vec-base-chinese")
vectorstore = Chroma.from_documents(texts, embeddings, persist_directory="./chroma_db")

Streamlit 侧边栏中,用户可执行以下操作:

1.上传一个或多个文档;

2.查看已上传文件列表;

3.删除指定文件;

4.查看知识库统计信息(文件数、总文本块数、最后更新时间)。

每次变更后,系统自动调用 update_knowledge_base 函数重建向量索引,并刷新统计信息。

四、三种问答模式的设计与实现

为适应不同复杂度的问题,系统提供了三种可切换的模式。

模式检索行为适用场景
通用模式不检索,直接调用大模型通用知识问答、闲聊
一般检索单次向量检索 + 生成大多数基于文档的事实性问题
深度检索多轮评估与查询改写 + 二次检索复杂、模糊或多跳推理问题

4.1 一般检索模式

标准 RAG 流程:

用户输入查询 q

使用vectorstore.similarity_search(q, k=4)检索最相关的 4 个文本块;

将检索结果拼接为上下文 context

构造提示词,要求大模型基于 context 回答;

调用通义千问 API 生成答案。

4.2 深度检索模式(Agentic RAG 雏形) 深度检索引入了自我评估查询改写机制。其核心流程如下:

def deep_retrieve(query):
    context = retrieve_context(query)                     # 第一轮检索
    if not is_sufficient(query, context):                # 评估是否足够
        new_query = rewrite_query(query)                 # 查询改写
        context += retrieve_context(new_query)           # 第二轮检索
    return context

is_sufficient:调用大模型判断当前检索到的内容是否足以回答用户问题。提示词为:“判断以下教材内容是否足够回答用户问题。只回答‘足够’或‘不足够’。” 输出解析为布尔值。

rewrite_query:若不足够,则让大模型将原始问题改写为更具体、更利于检索的形式。例如,将“那个协议怎么配”改写为“如何配置静态路由协议”。

该模式虽然增加 1–2 次额外的大模型调用(每次约 1–3 秒),但显著提升了复杂问题的回答质量。实际测试中,对于语义模糊或需要多步推理的问题,深度检索的准确率较一般检索提升约 30%。

五、飞书机器人集成

5.1 方案选型

飞书开放平台提供两种消息接收方式:长连接(WebSocket)和 Webhook。长连接需要集成官方 SDK,但实践中遇到了类名导入错误(如 P2pMessageReceiveV1 未定义)以及版本兼容性问题。因此选择 Webhook 方案:飞书将事件通过 HTTP POST 发送到开发者指定的公网地址,开发者使用 Flask 接收并处理。

内网穿透采用 cpolar(国内服务,稳定且免费),将本地 Flask 端口(8000)暴露为公网 URL。

5.2 配置步骤

在飞书开发者后台创建企业自建应用,启用机器人能力。

添加权限:im:message:receive_v1im:message:send_as_botim:message.p2p_msg:readonlyim:message.group_at_msg:readonly

在“事件与回调”中订阅事件 im.message.receive_v1

将请求地址 URL 设置为 cpolar 公网地址 + /webhook

编写 Flask 路由处理 POST 请求,解析消息内容,调用核心问答逻辑,再调用飞书消息发送 API 进行回复。

5.3 关键技术问题与解决方案

问题一:消息类型字段名错误

飞书事件 JSON 中表示消息类型的字段为 message_type,而官方文档部分示例误写为 msg_type。错误使用 msg_type 会导致事件被忽略。修正后正常接收。

问题二:发送消息时 receive_id_type is required

根据飞书 API 文档,发送消息时需要在请求体中提供 receive_id_type。但实际测试中发现该参数必须放在 URL 查询参数中,而非请求体 JSON 内。正确构造请求的方式如下:

url = "https://open.feishu.cn/open-apis/im/v1/messages?receive_id_type=chat_id"
data = {
    "receive_id": chat_id,
    "msg_type": "text",
    "content": json.dumps({"text": reply})
}
headers = {"Authorization": f"Bearer {access_token}", "Content-Type": "application/json"}
requests.post(url, headers=headers, json=data)
问题三:事件重试导致重复回答

飞书事件推送具备重试机制(网络超时或非 200 响应时会重试)。为避免机器人重复回答,基于 event_id 实现幂等处理:

processed_events = set()
event_id = data.get("header", {}).get("event_id")
if event_id in processed_events:
    return "success"
processed_events.add(event_id)

六、多模态扩展:OCR 文字识别

用户有时会发送包含文字的图片(如教材截图、手写笔记照片)。系统集成 Tesseract OCR,对上传的图片进行文字提取,并将识别结果追加到检索上下文中。

6.1 环境配置

下载 Tesseract OCR 安装包(UB-Mannheim 版本),勾选简体中文语言包。

安装 Python 库:pytesseractPillow

在代码中指定 tesseract.exe 路径。

6.2 集成方式

在 Streamlit 侧边栏增加图片上传组件,识别后保存到 st.session_state.ocr_text。当用户提问时,系统自动将该文本与向量检索结果合并:

if "ocr_text" in st.session_state and st.session_state.ocr_text:
    context += "\n\n[图片识别文字]:\n" + st.session_state.ocr_text

这样,用户即可针对图片内容进行提问,例如“这张图中第二行写的是什么?”。

七、系统评估与适用场景

7.1 性能与效果

检索延迟:一般检索模式下,从用户提问到返回答案的平均耗时约为 3–5 秒(包含向量检索 + 一次大模型调用)。深度检索模式额外增加 2–3 秒。

准确率:在包含 50 个常见计算机网络问题的测试集上,一般检索模式的准确率为 82%,深度检索模式为 91%(基于人工判断)。

知识库规模:系统在单机环境下可支持数万级别的文本块(对应数千页文档),检索速度基本不变。

7.2 典型应用场景

个人知识管理:上传日记、学习笔记、工作日志,实现基于语义的自然语言检索(如“去年我写过关于 Docker 的什么内容?”)。

团队文档问答:将项目文档、技术规范、会议纪要上传,团队成员可在飞书群中 @ 机器人快速查询。

学术文献检索:上传 PDF 论文合集,针对特定问题(如“哪些论文讨论了迁移学习在医疗影像中的应用?”)进行检索。

法律/合规文档查询:将合同、法规文件作为知识库,快速定位相关条款。

八、总结与展望

本文完整实现了一个通用的 RAG 知识库问答系统,涵盖了文档向量化、多种检索模式、飞书机器人集成和 OCR 多模态扩展。项目的主要技术贡献包括:

提出并实现了基于检索质量评估与查询改写的深度检索模式,提升了复杂问题的回答能力。

总结了飞书 API 集成中的典型陷阱(字段名、参数位置)及其解决方案。

设计了可动态增删文档的知识库管理机制,降低了用户维护成本。

未来工作可从以下几个方向展开:

检索质量优化:引入重排序模型(如 BAAI/bge-reranker-base)对检索结果重新排序,提升 top-k 准确性。

混合检索:结合 BM25 关键词检索与向量检索,提高召回率。

多知识库隔离:支持用户创建多个独立的知识库(如“工作笔记”“个人日记”),并支持切换。

更多 IM 平台:对接钉钉、企业微信等。

九、演示

9.1界面展示

image.png

9.2文件上传

以文件 高质量C编程指南——林锐利.pdf为例 image.png 检索内容及回答 image.png image.png

十、开源地址

项目代码已开源,包含完整的 app.py、feishu_bot.py、requirements.txt 及使用文档:

github.com/Bai1ey13/ai…

欢迎 star、fork、提交 issue 或 PR。