首页 / 文章

基于LangChain、Ollama和Chroma的完全本地化PDF RAG系统

一个完全本地的PDF RAG应用:LangChain负责读取文件,Ollama负责嵌入这些文件,Chroma负责存储向量,从而实现离线回答,无需任何云API密钥。

发布者:REACTAPP.TOP阅读约 5 分钟1111 词

来源: Implement RAG with Langchain and local Ollama models

使用 LangChain、Ollama 和 ChromaDB 构建专用于 PDF 的完全本地化 RAG 流水线——无需云 API,数据也不会离开机器即可与文档进行对话。

为何选择本地化

云端大型语言模型虽然方便,但每次查询都会将文本传输到他人的网络中。法律合同、医疗记录和内部报告往往无法接受这样的权衡。本地化方案可以避免数据泄露,同时让你能够对自己的文件进行自然语言问答。

你将获得什么

  • 导入 PDF 文件到持久化向量存储中
  • 回答基于这些文档的自然语言问题
  • 在推理时离线运行——模型下载后无需 API 密钥

技术栈:用于任务协调的 LangChain,用于本地聊天与嵌入计算的 Ollama,以及作为向量存储的 ChromaDB。

架构设计

数据导入流程:PDF字节首先进入基于PyPDF的加载器,被分割成多个块,随后由Ollama将每个块嵌入到Chroma中。

查询流程:用户提出的问题会传递给检索模块,匹配到的块会被作为上下文,由本地的Ollama聊天模型生成答案。

通过两阶段设计控制成本:

  1. 数据导入——仅执行一次(或文档变更时):解析、分块、嵌入并持久化存储
  2. 数据检索——每次查询时:将最相关的块作为LLM的上下文

只有当内容发生变化时才会产生嵌入计算成本,而交互式查询仍能保持快速响应。

前置条件

请安装Ollama并下载此处使用的模型:

ollama pull embeddinggemma:latest   # embedding model
ollama pull gemma4:latest           # chat model

gemma4是经过指令微调的模型。若需更小的内存占用,可尝试使用gemma2:2b;若需更强的推理能力,则选用gemma4:27b——请相应地更新配置字符串。

项目设置

python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt

需求文件中指定的关键软件包:

langchain
langchain-chroma
langchain-community
langchain-ollama
langchain-text-splitters
pypdf
chromadb
python-dotenv

无需修改代码,可通过环境变量覆盖默认设置:

OLLAMA_BASE_URL=http://localhost:11434
OLLAMA_MODEL=gemma4:latest
OLLAMA_EMBED_MODEL=embeddinggemma:latest

数据导入流程(src/ingest.py)

加载文档

def load_documents(pdf_path: str | Path) -> list:
    path = Path(pdf_path)
    loader = (
        PyPDFLoader(str(path))
        if path.is_file()
        else PyPDFDirectoryLoader(str(path))
    )
    return loader.load()py

有一个辅助函数可用于处理单个文件或整个目录。无论哪种情况,LangChain的加载器都会返回格式统一的Document列表。

分割为块

def split_documents(documents: list) -> list:
    splitter = RecursiveCharacterTextSplitter(
        chunk_size=1000,
        chunk_overlap=200,
    )
    return splitter.split_documents(documents)

RecursiveCharacterTextSplitter会优先按段落分割,再按句子分割,最后按单词分割,以此保留语义边界。200个字符的重叠长度可确保跨越边界的句子同时存在于相邻的两个片段中,从而减少边缘位置的检索错误。

嵌入与持久化

def build_vector_store(chunks: list) -> Chroma:
    embeddings = OllamaEmbeddings(model=EMBEDDING_MODEL, base_url=OLLAMA_BASE_URL)
    VECTOR_STORE_DIR.mkdir(parents=True, exist_ok=True)
    return Chroma.from_documents(
        documents=chunks,
        embedding=embeddings,
        collection_name=COLLECTION_NAME,
        persist_directory=str(VECTOR_STORE_DIR),
    )

Ollama在端口11434上本地提供嵌入服务。Chroma的嵌入数据存储在vector_store/目录下(包含SQLite数据库及HNSW索引文件),因此进程重启时无需重新进行完整嵌入。

检索流程(src/retrieval.py)

LangChain LCEL通过|运算符来组合各个处理步骤。

提示词模板

RAG_PROMPT = ChatPromptTemplate.from_messages([
    (
        "system",
        "You are an assistant that answers questions based on the provided context "
        "from PDF documents. Use only the information in the context to answer. "
        "If the answer is not in the context, say so clearly.\n\nContext:\n{context}"
    ),
    ("human", "{question}"),
])

系统消息可将答案与检索到的上下文关联起来——这是RAG机制防止仅依赖预训练数据产生随意幻觉的核心保障。

构建检索链

def build_rag_chain(k: int = 4):
    store = load_vector_store()
    retriever = store.as_retriever(search_kwargs={"k": k})
    llm = ChatOllama(model=MODEL_NAME, base_url=OLLAMA_BASE_URL)
    return (
        {"context": retriever | _format_docs, "question": RunnablePassthrough()}
        | RAG_PROMPT
        | llm
        | StrOutputParser()
    )

从左到右:检索器通过嵌入相似度选出最相似的个片段,辅助工具将这些片段整合成一个上下文字符串,提示词模板将上下文与用户问题合并,最后ChatOllama生成答案。

交互式提问循环

# Index a directory of PDFs (default: data/pdfs/)
python main.py ingest

一个轻量级的命令行界面(或HTTP封装工具)会加载该处理链并持续接收问题,直到用户退出。可调整的值,从而在上下文大小与响应延迟之间取得平衡。

端到端运行

# Index a specific file
python main.py ingest --path /path/to/contract.pdf
# Ask a one-off question
python main.py query "What are the payment terms?"
# Interactive session
python main.py chat

将数据输入指向您的PDF文件夹,确认向量存储目录已填满,随后即可进行查询。所有操作——包括嵌入生成、向量处理以及答案生成——都在本地主机上完成。

总结

本地 RAG 已不再是什么新鲜事物。Ollama 提供模型,Chroma 存储向量,LangChain 负责构建图结构。对于私有语料库而言,这三者就足以打造出实用的文档助手,且无需支付云服务费用或传输数据到外部。