前端转agent-【python】-19 构建私有 RAG 知识库:让 AI 读懂你的所有文档

前端转 Agent-【Python】-19 构建私有 RAG 知识库:让 AI 读懂你的所有文档(含精准 OCR 识别)

在前面的文章里,我们用 ChromaDB 存了几条模拟文档,让 Agent 能回答产品问题。

但真正的企业场景是:上百份 PDF 合同、Markdown 技术文档、Word 手册散落在文件夹里,其中很多还是扫描件 ------图片里的文字用普通 PDF 解析根本读不出来。

今天我们就来搭一个私有 RAG 知识库 ,把这些文档全部"喂"给 AI,并且加入图片文字精准识别(OCR)方案 ,全程数据不出本地,安全、免费。

如果你在 Vue 3 项目里做过文件上传 + 搜索,你会惊喜地发现:后端的处理管道完全对应前端的交互流程。

什么是私有 RAG 知识库?

RAG = 检索增强生成。私有 RAG 就是:

  1. 你的文档(PDF/Word/Markdown/TXT)→ 切片 → 向量化 → 存入本地向量库
  2. 用户提问 → 语义检索 → 把最相关的片段注入提示词 → 本地 LLM 生成答案

全程不需要 OpenAI,不需要联网,所有数据留在你的机器上。

Vue 3 类比 :你在前端做文件上传,后端存文件、建索引;用户在搜索框输入问题,后端返回 AI 生成的答案。我们这次写的就是整个后端管道

环境准备

bash 复制代码
pip install langchain langchain-ollama langchain-chroma pypdf python-docx unstructured
pip install pdf2image pytesseract paddlepaddle paddleocr  # OCR 相关
ollama pull qwen3:4b
ollama pull nomic-embed-text

系统依赖(OCR 必备):

  • Tesseractsudo apt install tesseract-ocr tesseract-ocr-chi-sim (Linux) 或从 GitHub 下载 Windows 安装包。
  • Poppler (pdf2image 需要):sudo apt install poppler-utils (Linux) 或 Windows 下载 poppler 并添加到 PATH。
  • unstructured 也可能需要 libmagic,详见官方文档。

整体架构

ini 复制代码
docs/ 文件夹
  ├── 产品手册.pdf          ← 混合内容(文字 + 扫描页)
  ├── 技术白皮书.docx
  └── 公司政策.md
        ↓
  智能文档加载器(PyPDF 文本提取 + OCR 图片文字识别)
        ↓
  文本分割器 (RecursiveCharacterTextSplitter)
        ↓
  本地嵌入 (OllamaEmbeddings model="nomic-embed-text")
        ↓
  ChromaDB 向量存储 (持久化到 ./my_knowledge_db)
        ↓
  用户提问 → 检索相关文档片段 → 拼接上下文 → Qwen3:4b 生成答案

第一步:构建文档索引管道(含 OCR 增强)

我们写一个脚本,扫描指定文件夹,自动判断 PDF 是普通文字版还是扫描版,并精准识别图片中的文字 ,然后全部入库。只跑一次,或文档有更新时重跑。

python 复制代码
# build_index.py ------ 构建私有知识库索引(含图片文字 OCR)
import os
import sys
sys.stdout.reconfigure(encoding='utf-8') # 看情况加

from langchain_ollama import OllamaEmbeddings
from langchain_chroma import Chroma
from langchain_community.document_loaders import (
    PyPDFLoader,
    Docx2txtLoader,
    TextLoader,
    UnstructuredMarkdownLoader,
)
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_core.documents import Document

# OCR 相关
from pdf2image import convert_from_path
import pytesseract
# 如果追求更高精度(尤其中文),可选用 PaddleOCR
# from paddleocr import PaddleOCR

# 1. 配置
_SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
DOCS_DIR = os.path.join(_SCRIPT_DIR, "docs")
CHROMA_DIR = os.path.join(_SCRIPT_DIR, "my_knowledge_db")
EMBEDDING_MODEL = "nomic-embed-text"
CHUNK_SIZE = 500
CHUNK_OVERLAP = 50

# ----------  OCR 精准识别模块 ----------
def extract_text_from_scanned_pdf(file_path: str) -> list[Document]:
    """
    将扫描型 PDF 的每一页转为图片,用 OCR 提取文字。
    返回 LangChain 的 Document 列表(每页一个 Document)。
    """
    docs = []
    try:
        images = convert_from_path(file_path, dpi=300)  # 提高 DPI 提升识别精度
        for i, img in enumerate(images):
            # 方案 A:Tesseract OCR(开箱即用,支持中英文需事先安装语言包)
            text = pytesseract.image_to_string(img, lang='chi_sim+eng')  # 中文+英文

            # 方案 B:PaddleOCR(对中文更友好,布局分析更强,注释掉上面一行,取消下面注释)
            # ocr = PaddleOCR(use_angle_cls=True, lang='ch', use_gpu=False, show_log=False)
            # result = ocr.ocr(img, cls=True)
            # text = "\n".join([line[1][0] for line in result[0]]) if result[0] else ""

            if text.strip():
                docs.append(Document(
                    page_content=text,
                    metadata={"source": file_path, "page": i+1, "type": "scanned_pdf"}
                ))
        print(f"🖼️ OCR 完成: {file_path},共识别 {len(docs)} 页")
    except Exception as e:
        print(f"❌ OCR 失败 {file_path}: {e}")
    return docs

def load_pdf_smart(file_path: str) -> list[Document]:
    """
    智能加载 PDF:先用 PyPDF 提取文本,若文本过少(疑似扫描件),
    则自动回退到 OCR 方案。
    """
    # 先尝试普通文本提取
    text_docs = []
    try:
        loader = PyPDFLoader(file_path)
        text_docs = loader.load()
        # 合并所有页面文字,判断有效字符数
        full_text = "".join([d.page_content for d in text_docs])
        # 如果平均每页有效字符少于 50,认为它是扫描版
        if len(full_text) < 50 * max(1, len(text_docs)):
            print(f"🔍 检测到 {file_path} 可能是扫描件,切换至 OCR")
            return extract_text_from_scanned_pdf(file_path)
        else:
            print(f"📄 文本提取: {file_path}")
            for doc in text_docs:
                doc.metadata["source"] = file_path
                doc.metadata["type"] = "text_pdf"
            return text_docs
    except Exception as e:
        print(f"⚠️ PyPDF 失败,尝试 OCR: {e}")
        return extract_text_from_scanned_pdf(file_path)

# ---------------------------------------

def load_documents(docs_dir: str) -> list[Document]:
    all_docs = []
    for root, _, files in os.walk(docs_dir):
        for file in files:
            path = os.path.join(root, file)
            try:
                if file.endswith(".pdf"):
                    # 使用智能加载器(自动识别文字/扫描件)
                    docs = load_pdf_smart(path)
                elif file.endswith(".docx"):
                    docs = Docx2txtLoader(path).load()
                    for d in docs: d.metadata["source"] = path
                elif file.endswith(".md"):
                    docs = UnstructuredMarkdownLoader(path).load()
                    for d in docs: d.metadata["source"] = path
                elif file.endswith(".txt"):
                    docs = TextLoader(path, encoding="utf-8").load()
                    for d in docs: d.metadata["source"] = path
                else:
                    continue
                all_docs.extend(docs)
                print(f"✅ 已加载: {file}")
            except Exception as e:
                print(f"❌ 加载失败 {file}: {e}")
    return all_docs

# 2. 分割文档
raw_docs = load_documents(DOCS_DIR)
splitter = RecursiveCharacterTextSplitter(
    chunk_size=CHUNK_SIZE,
    chunk_overlap=CHUNK_OVERLAP,
    separators=["\n\n", "\n", "。", ";", ",", " ", ""]
)
chunks = splitter.split_documents(raw_docs)
print(f"📊 共生成 {len(chunks)} 个文本块")

# 3. 向量化并存入 ChromaDB
embedding = OllamaEmbeddings(model=EMBEDDING_MODEL)
vectorstore = Chroma.from_documents(
    documents=chunks,
    embedding=embedding,
    persist_directory=CHROMA_DIR
)
print(f"💾 向量库已保存到 {CHROMA_DIR}")

执行一次:

bash 复制代码
python build_index.py

你会看到包含 OCR 的详细输出,扫描件会被自动识别并转换。

第二步:搭建 RAG 问答接口

索引建好后,问答环节和之前一样,但我们可以利用元数据告诉用户答案来自哪一页、是文字还是扫描件。

python 复制代码
# ask_knowledge.py ------ 向私有知识库提问
from langchain_ollama import ChatOllama, OllamaEmbeddings
from langchain_chroma import Chroma
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough

embedding = OllamaEmbeddings(model="nomic-embed-text")
vectorstore = Chroma(
    persist_directory="./my_knowledge_db",
    embedding_function=embedding
)
retriever = vectorstore.as_retriever(search_kwargs={"k": 3})

llm = ChatOllama(model="qwen3:4b", temperature=0)

prompt = ChatPromptTemplate.from_template("""你是一个知识库助手,只能根据以下已知信息回答问题。
如果已知信息中找不到答案,请直接说"知识库中暂时没有相关信息",不要编造。

已知信息:
{context}

用户问题:{question}
回答:""")

def format_docs(docs):
    formatted = []
    for doc in docs:
        source = doc.metadata.get('source', '未知')
        page = doc.metadata.get('page', '?')
        doc_type = doc.metadata.get('type', '文档')
        formatted.append(f"【来源:{source} 第{page}页 ({doc_type})】\n{doc.page_content}")
    return "\n\n".join(formatted)

rag_chain = (
    {"context": retriever | format_docs, "question": RunnablePassthrough()}
    | prompt
    | llm
    | StrOutputParser()
)

if __name__ == "__main__":
    print("📚 私有知识库问答系统 (输入 /bye 退出)")
    while True:
        q = input("\n🧑 你的问题: ")
        if q == "/bye":
            break
        answer = rag_chain.invoke(q)
        print(f"🤖 回答: {answer}")

第三步:增量更新与自动重索引(可选)

和之前一样,重新运行 build_index.py 即可。OCR 部分会再次识别扫描件,若有更新自动覆盖。

Vue 3 横向对比

RAG 流程 Vue 3 前端类似操作
选择文件夹、上传文档 <input type="file" webkitdirectory> 或拖拽上传组件
显示索引进度(含 OCR 进度) ElProgress 组件 + SSE 推送
提问并显示答案 搜索框 + 结果列表,类似 ChatGPT 对话界面
显示引用来源(含页码、类型) 答案下方用 v-for 列出源文件,可点击预览对应页面
后端管道 Pinia action 调用 FastAPI 接口 POST /ask

前端开发者完全可以基于这套 Python 管道,用 FastAPI 封装 POST /upload(接收文件并触发重索引)和 POST /ask,打造一个内部 AI 知识库平台。

进阶:更精准的 OCR 方案对比

方案 优点 缺点 适用场景
Tesseract + chi_sim 离线免费,部署简单 密集文字、特殊排版易出错 清晰扫描件、简单表格
PaddleOCR 中文识别极强,支持倾斜、密集文字 安装稍重,首次运行需下载模型 复杂排版、手写体、合同扫描件
Surya OCR 多语言、检测行级准确 需要 GPU 加速才快 学术论文、多栏 PDF
Ollama 视觉模型 端到端,无需单独 OCR,理解上下文 慢,成本高,不适合大规模索引 少量图片实时问答

推荐组合 :批量索引用 PaddleOCR(精准+纯本地),实时图片问答才考虑视觉模型。

总结

  • 私有 RAG 是让 AI 理解企业内部文档的最实用方案,全程本地运行,数据安全。
  • 今天加入了 图片文字精准识别(OCR),自动区分文本 PDF 和扫描件,补齐了真实场景的最大短板。
  • 整体流程 = 智能加载(文本+OCR)→ 分割 → 嵌入 → 存储 → 检索 → 生成。
  • 前端开发者可以轻松把这套管道包装成 Web 应用,做一个内部"ChatGPT"。
  • 结合之前学的人机协同、可视化、评估,你完全可以交付一个生产级的企业知识库系统。

现在就去把你电脑里积灰的文档文件夹"喂"给 AI 吧,让它成为你的第二大脑!

bash 复制代码
python build_index.py    # 先建索引(自动 OCR 扫描件)
python ask_knowledge.py  # 再提问
相关推荐
程序员cxuan1 小时前
读懂 Claude Code 架构分析系列,第二篇:Claude Code 是怎样启动的?
人工智能·后端·程序员
多年小白1 小时前
全球 AI 行业近三年市场增长数据深度分析报告(2023-2026)
人工智能·信息可视化
ACP广源盛139246256731 小时前
GSV2221@ACP# DP1.4 MST 多流显示转换芯片国产工业多屏
大数据·人工智能·分布式·嵌入式硬件·spark
xiaoxiaoxiaolll1 小时前
FDTD与Python联合仿真的超表面智能设计
人工智能·机器学习
霸道流氓气质1 小时前
Harness Engineering 从理论到实战:基于 Spring AI Alibaba 的完整实现指南
数据库·人工智能·spring·harness
hangyuekejiGEO1 小时前
临沂GEO技术服务方案对比分析
大数据·人工智能·python
happyprince1 小时前
08-PEFT源码阅读-使用指南与实战案例
人工智能·peft
Honker_yhw2 小时前
大数据管理与应用系列丛书《数据挖掘》(吕欣等著)读书笔记-Apriori 算法
人工智能·数据挖掘
IT_陈寒2 小时前
Vue的响应式更新把我坑惨了,原来是这个原因
前端·人工智能·后端