摘要
- 一个基于检索增强生成(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_v1、im:message:send_as_bot、im:message.p2p_msg:readonly、im: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 库:pytesseract、Pillow。
在代码中指定 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界面展示
9.2文件上传
以文件 高质量C编程指南——林锐利.pdf为例
检索内容及回答
十、开源地址
项目代码已开源,包含完整的 app.py、feishu_bot.py、requirements.txt 及使用文档:
欢迎 star、fork、提交 issue 或 PR。