基于 LangChain 框架构建本地化 RAG 系统:无需调用云端 API 的完整实践

1. 引言

检索增强生成(Retrieval-Augmented Generation,RAG)是当前大语言模型应用中最实用的范式之一。它通过先检索外部知识库中的相关文档片段,再将这些片段作为上下文注入 Prompt,引导大模型基于给定资料生成回答,从而有效缓解模型幻觉、知识过时等问题。

本文将以一个完整的本地化 RAG 系统为例,基于 LangChain 框架,从文档加载、文本切分、向量化存储到检索问答,逐步拆解整个构建流程。项目全部使用本地模型,无需调用云端 API,完全离线运行,适合在数据敏感、网络受限或追求低成本部署的场景下使用。

2. 系统架构与核心组件

一个典型的 RAG 系统由以下核心组件构成:

  • 文档加载器(Loader):读取本地文档(如 Word、PDF、TXT),将其转换为统一的 Document 对象。
  • 文本切分器(Splitter):将长文档按语义或固定长度切分为适合检索的片段。
  • 嵌入模型(Embedding Model):将文本片段转换为稠密向量,用于语义相似度计算。
  • 向量数据库(Vector Store):存储文档向量,并提供高效的相似度检索能力。
  • 大语言模型(LLM):根据检索到的上下文和用户问题,生成最终回答。
  • 提示词模板(Prompt Template):将检索结果与用户问题组装成结构化的模型输入。

整体流程如下图所示:
#mermaid-svg-hFPS7KZxbNyMEFR5{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-hFPS7KZxbNyMEFR5 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-hFPS7KZxbNyMEFR5 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-hFPS7KZxbNyMEFR5 .error-icon{fill:#552222;}#mermaid-svg-hFPS7KZxbNyMEFR5 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-hFPS7KZxbNyMEFR5 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-hFPS7KZxbNyMEFR5 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-hFPS7KZxbNyMEFR5 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-hFPS7KZxbNyMEFR5 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-hFPS7KZxbNyMEFR5 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-hFPS7KZxbNyMEFR5 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-hFPS7KZxbNyMEFR5 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-hFPS7KZxbNyMEFR5 .marker.cross{stroke:#333333;}#mermaid-svg-hFPS7KZxbNyMEFR5 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-hFPS7KZxbNyMEFR5 p{margin:0;}#mermaid-svg-hFPS7KZxbNyMEFR5 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-hFPS7KZxbNyMEFR5 .cluster-label text{fill:#333;}#mermaid-svg-hFPS7KZxbNyMEFR5 .cluster-label span{color:#333;}#mermaid-svg-hFPS7KZxbNyMEFR5 .cluster-label span p{background-color:transparent;}#mermaid-svg-hFPS7KZxbNyMEFR5 .label text,#mermaid-svg-hFPS7KZxbNyMEFR5 span{fill:#333;color:#333;}#mermaid-svg-hFPS7KZxbNyMEFR5 .node rect,#mermaid-svg-hFPS7KZxbNyMEFR5 .node circle,#mermaid-svg-hFPS7KZxbNyMEFR5 .node ellipse,#mermaid-svg-hFPS7KZxbNyMEFR5 .node polygon,#mermaid-svg-hFPS7KZxbNyMEFR5 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-hFPS7KZxbNyMEFR5 .rough-node .label text,#mermaid-svg-hFPS7KZxbNyMEFR5 .node .label text,#mermaid-svg-hFPS7KZxbNyMEFR5 .image-shape .label,#mermaid-svg-hFPS7KZxbNyMEFR5 .icon-shape .label{text-anchor:middle;}#mermaid-svg-hFPS7KZxbNyMEFR5 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-hFPS7KZxbNyMEFR5 .rough-node .label,#mermaid-svg-hFPS7KZxbNyMEFR5 .node .label,#mermaid-svg-hFPS7KZxbNyMEFR5 .image-shape .label,#mermaid-svg-hFPS7KZxbNyMEFR5 .icon-shape .label{text-align:center;}#mermaid-svg-hFPS7KZxbNyMEFR5 .node.clickable{cursor:pointer;}#mermaid-svg-hFPS7KZxbNyMEFR5 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-hFPS7KZxbNyMEFR5 .arrowheadPath{fill:#333333;}#mermaid-svg-hFPS7KZxbNyMEFR5 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-hFPS7KZxbNyMEFR5 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-hFPS7KZxbNyMEFR5 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-hFPS7KZxbNyMEFR5 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-hFPS7KZxbNyMEFR5 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-hFPS7KZxbNyMEFR5 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-hFPS7KZxbNyMEFR5 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-hFPS7KZxbNyMEFR5 .cluster text{fill:#333;}#mermaid-svg-hFPS7KZxbNyMEFR5 .cluster span{color:#333;}#mermaid-svg-hFPS7KZxbNyMEFR5 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-hFPS7KZxbNyMEFR5 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-hFPS7KZxbNyMEFR5 rect.text{fill:none;stroke-width:0;}#mermaid-svg-hFPS7KZxbNyMEFR5 .icon-shape,#mermaid-svg-hFPS7KZxbNyMEFR5 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-hFPS7KZxbNyMEFR5 .icon-shape p,#mermaid-svg-hFPS7KZxbNyMEFR5 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-hFPS7KZxbNyMEFR5 .icon-shape .label rect,#mermaid-svg-hFPS7KZxbNyMEFR5 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-hFPS7KZxbNyMEFR5 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-hFPS7KZxbNyMEFR5 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-hFPS7KZxbNyMEFR5 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 本地文档加载
文本切分
嵌入向量化
向量数据库存储
用户提问
问题向量化
相似度检索
组装 Prompt
大模型生成回答

3. 环境准备与依赖安装

本项目基于 Python 与 LangChain 生态,建议使用 Python 3.9 及以上版本。所有依赖均为开源库,模型文件全部存放在本地,全程不调用任何云端 API。核心依赖如下:

bash 复制代码
pip install langchain langchain-community langchain-chroma langchain-text-splitters
pip install chromadb transformers torch python-docx

其中:

  • langchain:框架核心,提供链式编排能力。
  • langchain-community:包含各类文档加载器与第三方集成。
  • langchain-chroma:Chroma 向量数据库的 LangChain 封装。
  • transformerstorch:用于加载本地大模型与嵌入模型。
  • python-docx:用于解析 Word 文档。

4. 文档加载与文本切分

4.1 加载本地 Word 文档

使用 Docx2txtLoader 可以方便地读取 .docx 格式的文档。通过 os.path 拼接文件路径,可以确保脚本在不同目录下运行时都能正确定位文件。

python 复制代码
import os
from langchain_community.document_loaders import Docx2txtLoader

current_folder = os.path.dirname(os.path.abspath(__file__))
docx_path = os.path.join(current_folder, "agent_doc.docx")

loader = Docx2txtLoader(docx_path)
documents = loader.load()
print(f"文档包含 {len(documents)} 个片段")

4.2 文本切分策略

原始文档往往较长,直接向量化会导致检索精度下降。这里使用 RecursiveCharacterTextSplitter,它按优先级依次尝试分隔符(\n\n\n、空格),尽可能在语义完整的位置切分。

python 复制代码
from langchain_text_splitters import RecursiveCharacterTextSplitter

text_splitter = RecursiveCharacterTextSplitter(
    chunk_size=800,
    chunk_overlap=10,
    separators=["\n\n", "\n", " ", ""]
)

documents = text_splitter.split_documents(documents)
print(f"文档切分为 {len(documents)} 个片段")

chunk_size 控制每个片段的字符数,chunk_overlap 让相邻片段保留少量重叠,避免关键信息被截断在边界处。

5. 嵌入模型与向量数据库

5.1 加载本地嵌入模型

为了完全离线运行,这里使用 HuggingFace 的 MiniLM-L6-v2 模型。该模型体积小、速度快,适合作为中文与英文文本的嵌入模型。

python 复制代码
from langchain_community.embeddings import HuggingFaceEmbeddings

local_model_path = r"F:\agent_project\MiniLM-L6-v2"

miniLMEmbeddings = HuggingFaceEmbeddings(
    model_name=local_model_path,
    model_kwargs={"device": "cuda"},
    encode_kwargs={"normalize_embeddings": True}
)

normalize_embeddings=True 会对向量做 L2 归一化,使余弦相似度计算更加稳定。若显存不足,可将 device 改为 "cpu"

5.2 构建向量数据库

使用 Chroma 作为向量存储,将切分后的文档片段批量写入数据库,并持久化到本地目录。

python 复制代码
from langchain_chroma import Chroma

db = Chroma.from_documents(
    collection_name="demo",
    documents=documents,
    embedding=miniLMEmbeddings,
    persist_directory="chroma_db"
)
print(f"向量数据库构建完成,包含 {db._collection.count()} 个向量")

首次运行会执行全量向量化,之后可复用 persist_directory 中的持久化数据,避免重复计算。

6. 本地大模型加载

6.1 加载模型与分词器

本项目使用 LiteLlama-460M-1T 作为生成模型,这是一个轻量级 Llama 架构模型,适合在消费级显卡上运行。加载时需指定 local_files_only=True,确保完全离线、不访问云端

python 复制代码
import torch
from transformers import AutoTokenizer, AutoModelForCausalLM

local_model_path = "F:/agent_project/LiteLlama-460M-1T"

tokenizer = AutoTokenizer.from_pretrained(
    local_model_path,
    local_files_only=True,
    trust_remote_code=False
)

model = AutoModelForCausalLM.from_pretrained(
    local_model_path,
    dtype=torch.float16,
    local_files_only=True,
    trust_remote_code=False
).cuda()

使用 torch.float16 半精度加载可以显著降低显存占用。若显存不足,可移除 .cuda() 并改用 CPU 推理。

6.2 模型推理验证

在正式接入 RAG 流程前,建议先做一次简单的生成测试,确认模型与分词器工作正常:

python 复制代码
prompt = "Q: what is your name ?\nA:"
inputs = tokenizer(prompt, return_tensors="pt").to("cuda")

outputs = model.generate(
    **inputs, max_new_tokens=100, do_sample=True, temperature=1
)
print(tokenizer.decode(outputs[0], skip_special_tokens=True))

7. RAG 检索与问答流程

7.1 用户问题向量化与检索

当用户提出问题时,首先将问题转换为向量,然后在向量数据库中执行相似度检索,返回最相关的 k 个文档片段。

python 复制代码
query = "can you tell me what is the name of the author?"
print(f"用户问题:{query}")

query_embedding = miniLMEmbeddings.embed_query(query)

retrieved_docs = db.similarity_search_by_vector(
    embedding=query_embedding,
    k=2
)
print(f"检索到 {len(retrieved_docs)} 个相关文档片段")

k=2 表示返回最相关的 2 个片段。实际应用中可根据文档长度与问题复杂度调整该参数。

7.2 组装 Prompt

将检索到的文档片段拼接为上下文,与用户问题一起填入提示词模板。这里使用 ChatPromptTemplate 构建结构化的对话消息。

python 复制代码
from langchain_core.prompts import ChatPromptTemplate

context_text = ""
for i, doc in enumerate(retrieved_docs):
    context_text += f"Document excerpts {i+1}:{doc.page_content}\n"

prompt_template = ChatPromptTemplate.from_messages([
    ("system", "You are an assistant. Respond with A: followed by your answer. After finishing your answer after A:, stop generating completely."),
    ("user", "Here are some document contents:\n{context}\n\nPlease answer the user's question based on these contents: {query}")
])

formatted_prompt = prompt_template.format_prompt(
    context=context_text,
    query=query
)

系统提示词中明确要求模型以 A: 开头作答,并在回答结束后停止生成,这有助于后续解析模型输出。

7.3 模型生成与结果解析

将组装好的 Prompt 分词后送入模型生成,最后只解码模型新生成的部分,避免重复输出输入内容。

python 复制代码
model_inputs = tokenizer(formatted_prompt.to_string(), return_tensors="pt").to("cuda")

output_tokens = model.generate(
    **model_inputs,
    max_new_tokens=400
)

input_len = model_inputs["input_ids"].shape[1]
result = tokenizer.decode(output_tokens[0][input_len:])
print("模型回答:\n", result.split("Q:")[0])

通过 input_len 截断输入部分,确保只保留模型真正生成的回答内容。

8. 完整代码整合

将上述模块整合为三个文件,便于维护与复用:

  • embeddings.py:嵌入模型初始化。
  • models.py:大模型与分词器加载。
  • final_release.py:RAG 主流程。

8.1 embeddings.py

python 复制代码
from langchain_community.embeddings import HuggingFaceEmbeddings

# 填写本地模型绝对路径,加r防止路径转义
local_model_path = r"F:\agent_project\MiniLM-L6-v2"

miniLMEmbeddings = HuggingFaceEmbeddings(
    model_name=local_model_path,
    # 有显卡用cuda,显存不够改成 device="cpu"
    model_kwargs={"device": "cuda"},
    encode_kwargs={"normalize_embeddings": True}
)

8.2 models.py

python 复制代码
import torch
from transformers import AutoTokenizer, AutoModelForCausalLM

local_model_path = "F:/agent_project/LiteLlama-460M-1T"

tokenizer = AutoTokenizer.from_pretrained(
    local_model_path,
    local_files_only=True,
    trust_remote_code=False
)

model = AutoModelForCausalLM.from_pretrained(
    local_model_path,
    dtype=torch.float16,
    local_files_only=True,
    trust_remote_code=False
).cuda()

8.3 final_release.py

python 复制代码
import os
import time
from langchain_chroma import Chroma
from langchain_community.document_loaders import Docx2txtLoader
from langchain_core.prompts import ChatPromptTemplate
from langchain_text_splitters import RecursiveCharacterTextSplitter

from models import model, tokenizer
from embeddings import miniLMEmbeddings

current_folder = os.path.dirname(os.path.abspath(__file__))
docx_path = os.path.join(current_folder, "agent_doc.docx")

# 加载文档
loader = Docx2txtLoader(docx_path)
documents = loader.load()
print(f"文档包含 {len(documents)} 个片段")

# 文档切分
text_splitter = RecursiveCharacterTextSplitter(
    chunk_size=800,
    chunk_overlap=10,
    separators=["\n\n", "\n", " ", ""]
)
documents = text_splitter.split_documents(documents)
print(f"文档切分为 {len(documents)} 个片段")
time.sleep(1)

# 加载嵌入模型
embedding = miniLMEmbeddings
demo_vector = embedding.embed_query("Hello world")
print(f"嵌入向量维度:{len(demo_vector)}")

# 向量数据库构建
db = Chroma.from_documents(
    collection_name="demo",
    documents=documents,
    embedding=embedding,
    persist_directory="chroma_db"
)
print(f"向量数据库构建完成,包含 {db._collection.count()} 个向量")
time.sleep(1)

# RAG检索与问答流程
query = "can you tell me what is the name of the author?"
print(f"用户问题:{query}")
time.sleep(1)

query_embedding = embedding.embed_query(query)
time.sleep(1)

retrieved_docs = db.similarity_search_by_vector(
    embedding=query_embedding,
    k=2
)
print(f"检索到 {len(retrieved_docs)} 个相关文档片段")

# 拼接检索到的文档片段
context_text = ""
for i, doc in enumerate(retrieved_docs):
    context_text += f"Document excerpts {i+1}:{doc.page_content}\n"
time.sleep(1)

# 组装Prompt
prompt_template = ChatPromptTemplate.from_messages([
    ("system", "You are an assistant. Respond with A: followed by your answer. After finishing your answer after A:, stop generating completely."),
    ("user", "Here are some document contents:\n{context}\n\nPlease answer the user's question based on these contents: {query}")
])

formatted_prompt = prompt_template.format_prompt(
    context=context_text,
    query=query
)
print(f"组装后的Prompt内容:\n{formatted_prompt.to_string()}")

print("正在调用大模型,请稍候...")

# 分词并生成
model_inputs = tokenizer(formatted_prompt.to_string(), return_tensors="pt").to("cuda")
output_tokens = model.generate(
    **model_inputs,
    max_new_tokens=400
)

# 解码,只保留模型新输出内容
input_len = model_inputs["input_ids"].shape[1]
result = tokenizer.decode(output_tokens[0][input_len:])
print("模型回答:\n", result.split("Q:")[0])

9. 运行效果与调优建议

9.1 预期输出

运行 final_release.py 后,控制台会依次输出文档加载信息、切分结果、向量维度、数据库构建状态,以及最终的模型回答。整个过程完全离线,无需调用任何云端 API,数据自始至终不出本地机器。

9.2 调优方向

  • 检索精度 :适当增大 chunk_size 可保留更多上下文,但过大会引入噪声;k 值越大,模型可参考的信息越多,但也会增加 Prompt 长度与推理时间。
  • 生成质量 :可通过调整 temperaturetop_p 等采样参数控制回答的多样性与稳定性。
  • 向量数据库 :当文档规模增大时,可考虑使用 FAISSMilvus 等更高效的向量存储方案。
  • 模型选择:若追求更强生成能力,可替换为更大的本地模型(如 Qwen、ChatGLM 系列),但需注意显存占用。

10. 总结

本文基于 LangChain 框架,完整实现了一个本地化部署、无需调用云端 API 的 RAG 系统。从文档加载、文本切分、向量化存储,到检索与生成,每一步都给出了可运行的代码示例。该方案完全离线运行,兼顾了数据隐私与部署成本,适合作为企业知识库问答、私有文档检索等场景的基础框架。

后续可以在此基础上扩展多轮对话、文档增量更新、混合检索(关键词 + 向量)等能力,进一步提升系统的实用性与鲁棒性。

相关推荐
布鲁飞丝1 小时前
彩笔运维勇闯机器学习--cpu与qps的线性关系
运维·人工智能·机器学习
ZHOU_WUYI1 小时前
5. light wam 中video pre阶段
人工智能
2501_926978331 小时前
提示工程的实战报告(二):模型的失败模式与边界行为
人工智能·深度学习·算法
行走的小派1 小时前
Zero 4不是Zero 3W的升级版,是香橙派的策略补位
人工智能·香橙派·边缘ai
IanSkunk1 小时前
企业AI办公落地技术拆解:从WorkBuddy任务引擎到JOTO实施路径
大数据·人工智能
Mycdn_WD2 小时前
项目交付后的机房,能参与边缘节点协作吗?
人工智能·cdn·pcdn·城域网·pcdn资源招募
LayZhangStrive2 小时前
线性代数 - 第1章 行列式
人工智能·线性代数·机器学习
Michaelliu_dev2 小时前
多模态大模型推理流程解析
人工智能·llm·多模态大模型·vit·llava·rope·mllm
正经教主2 小时前
Trae Work 提示词使用要求与教程
人工智能