
通过多篇博文我们也反复介绍说明了大模型知识滞后、生成幻觉成为制约智能问答、企业知识库等场景落地的核心痛点,检索增强生成(RAG)技术通过“外部知识检索 + LLM 生成” 的模式,为解决这些问题提供了关键思路,而向量数据库则是 RAG 发挥价值的核心底座。今天我们从一个新的视角,以本地员工手册智能问答系统为内容载体,从基础概念到实践,系统拆解 RAG 与向量数据库的深度融合逻辑,同时引入 Ollama 这一轻量级本地大模型运行工具,增加实用性和便捷性。
Ollama 作为开源的本地大模型运行框架,能够一键部署、自定义配置通义千问、Llama3 等主流模型,无需依赖云端算力,既保障了数据隐私,又降低了技术落地成本。今天我们从文档智能分块、向量入库、RAG 问答链搭建到调试优化,通过 FAISS 构建轻量级向量库,解决非结构化文本的语义检索问题;借助 Ollama 实现本地 LLM 的灵活调用,结合调试输出直观呈现检索、生成全流程的关键细节。

RAG 全称为 Retrieval-Augmented Generation,是一种将外部知识库检索与大模型生成相结合的技术框架。简单来说,它就像带参考书考试的学生,生成答案前先从外部知识源中查找相关信息,再结合自身能力组织语言输出。
向量数据库是专门用于存储、管理和高效检索高维向量数据的数据库系统。计算机无法直接理解文本、图像等非结构化数据,而向量数据库能通过嵌入模型将这些数据转化为类似 “数字指纹” 的高维向量,再通过相似度算法实现快速检索。
Ollama 是一款开源、轻量级的本地大模型运行与管理工具,核心定位是让普通开发者和中小企业能够以极低的成本、零门槛在本地环境部署和使用主流大模型。它并非自研大模型,而是搭建了一套简洁的模型运行框架,将模型下载、环境配置、参数调优、服务启动等复杂流程封装为简单的命令行操作,大幅降低了大模型本地化落地的技术门槛。
Ollama 的价值体现:
在实际应用中,Ollama 常与 RAG、向量数据库组合使用:
向量数据库是 RAG 系统的 “记忆中枢”:
二者融合后,既发挥了 RAG“灵活调用外部知识”的优势,又借助向量数据库解决了 RAG 检索效率和精度的核心问题,成为搭建实用智能问答、知识库系统的黄金组合。
RAG 与向量数据库的融合贯穿于整个 RAG 系统的离线建库和在线问答全流程,每个环节的优化都直接影响系统性能,具体可分为两大阶段。
构建可检索的知识底座,该阶段的核心是将非结构化知识转化为向量数据库可存储、可检索的格式,为后续检索做准备,步骤如下:

实时检索与智能生成,用户发起查询时,二者通过协同完成从查询到回答的全链路流程,步骤如下:

访问 Ollama 官方网站,根据自身系统选择对应安装包,下载 OllamaSetup.exe 安装文件; 双击安装包,按提示完成下一步安装(默认路径即可,无需额外配置),完成基础安装; 打开终端/命令提示符,执行 :

本示例使用的比较小的模型deepseek-r1:1.5b部署,第一次通过ollama pull deepseek-r1:1.5b命令进行下载,下载完后通过ollama run deepseek-r1:1.5b运行即可;

Ollama的部署应用明细可参考《构建AI智能体:DeepSeek的Ollama部署FastAPI封装调用》
# 支持动态切换模型(如qwen2:1.5b、llama3:8b)
def switch_llm(model_name):
return Ollama(model=model_name, temperature=0.1)
llm_1.5b = switch_llm("qwen2:1.5b")# 流式回答(逐字输出)
from langchain_core.callbacks import StreamingStdOutCallbackHandler
llm_stream = Ollama(model="deepseek-r1:1.5b", callbacks=[StreamingStdOutCallbackHandler()])
llm_stream.invoke("请详细说明年假政策")员工年假政策:正式员工入职满1年不满3年,每年享有5天带薪年假;入职满3年不满10年,每年享有10天带薪年假;入职满10年及以上,每年享有15天带薪年假。 请假流程:员工请假需提前3天通过企业OA提交申请,经直属领导审批通过后方可生效。紧急情况无法提前申请的,需24小时内补提申请。 加班调休规则:工作日加班可按1:1比例申请调休,调休需在加班后3个月内使用完毕,逾期作废。 # 新增冗余内容(用于调试检索过滤效果) 办公用品申领:每月5号可提交申领表,限领笔、笔记本等基础用品。 团建活动规则:每季度部门可申请1次团建经费,上限5000元。
from langchain_community.document_loaders import TextLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter
from rich import print # 美化调试输出
# 加载本地文档
loader = TextLoader("knowledge.txt", encoding="utf-8")
documents = loader.load()
print(f"【调试】原始文档加载结果:\n{documents}\n")
# 文本分块:100字符/块,50字符重叠率
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=100,
chunk_overlap=50,
separators=["\n\n", "\n", "。", "!", "?", ",", "、"] # 新增中文分隔符优化
)
chunks = text_splitter.split_documents(documents)
# 调试输出:分块数量+每个块内容
print(f"【调试】文本分块总数:{len(chunks)}")
for i, chunk in enumerate(chunks):
print(f"【调试】第{i+1}个分块内容:\n{chunk.page_content}\n---")输出结果:
【调试】原始文档加载结果: [Document(metadata={'source': 'knowledge.txt'}, page_content='员工年假政策:正式员工入职满1年不满3年,每年享有5天带薪年假;入职满3年不满10年,每年享有10天带薪年假;入职满10年及以 上,每年享有15天带薪年假。\n请假流程:员工请假需提前3天通过企业OA提交申请,经直属领导审批通过后方可生效。紧急情况无法提前申请的, 需24小时内补提申请。\n加班调休规则:工作日加班可按1:1比例申请调休,调休需在加班后3个月内使用完毕,逾期作废。\n# 新增冗余内容(用于调试检索过滤效果)\n办公用品申领:每月5号可提交申领表,限领笔、笔记本等基础用品。\n团建活动规则:每季度部门可申 请1次团建经费,上限5000元。')] 【调试】文本分块总数:4 【调试】第1个分块内容: 员工年假政策:正式员工入职满1年不满3年,每年享有5天带薪年假;入职满3年不满10年,每年享有10天带薪年假;入职满10年及以上,每年享有15天带薪年假。 --- 【调试】第2个分块内容: 请假流程:员工请假需提前3天通过企业OA提交申请,经直属领导审批通过后方可生效。紧急情况无法提前申请的,需24小时内补提申请。 --- 【调试】第3个分块内容: 加班调休规则:工作日加班可按1:1比例申请调休,调休需在加班后3个月内使用完毕,逾期作废。 # 新增冗余内容(用于调试检索过滤效果) 办公用品申领:每月5号可提交申领表,限领笔、笔记本等基础用品。 --- 【调试】第4个分块内容: 办公用品申领:每月5号可提交申领表,限领笔、笔记本等基础用品。 团建活动规则:每季度部门可申请1次团建经费,上限5000元。 ---
from langchain_community.embeddings import HuggingFaceEmbeddings
from langchain_community.vectorstores import FAISS
import numpy as np
# 初始化嵌入模型(新增模型参数优化)
embeddings = HuggingFaceEmbeddings(
model_name="D:/modelscope/hub/models/sentence-transformers/all-MiniLM-L6-v2",
model_kwargs={'device': 'cpu'}, # 指定CPU运行(无GPU时)
encode_kwargs={'normalize_embeddings': True} # 归一化向量,提升相似度计算精度
)
# 为分块添加元数据(扩展:便于后续过滤)
for i, chunk in enumerate(chunks):
chunk.metadata.update({
"chunk_id": i+1,
"content_type": "employee_rule",
"update_time": "2025-12-17"
})
# 文本块向量化并构建FAISS向量库
vectorstore = FAISS.from_documents(chunks, embeddings)
# 调试:查看向量维度+相似度计算示例
sample_chunk = chunks[0]
sample_vector = embeddings.embed_query(sample_chunk.page_content)
print(f"【调试】单个文本块向量维度:{len(sample_vector)}")
print(f"【调试】向量前10位数值:{np.round(sample_vector[:10], 4)}\n")
# 调试:测试相似性检索
query = "入职5年年假有多少天"
retrieved_docs = vectorstore.similarity_search(query, k=2)
print(f"【调试】测试检索结果(查询:{query}):")
retrieved_docs_with_scores = vectorstore.similarity_search_with_score(query, k=2)
for doc, score in retrieved_docs_with_scores:
print(f"- 匹配分块:{doc.page_content}(相似度:{score:.4f})")
# 保存向量库到本地
vectorstore.save_local("local_faiss_index")
print("\n【调试】向量库已保存到 local_faiss_index 目录") 输出结果:
【调试】单个文本块向量维度:384 【调试】向量前10位数值:[ 0.0227 0.0214 0.0536 0.0264 -0.0132 0.0831 -0.0024 -0.0287 -0.0151 0.0242] 【调试】测试检索结果(查询:入职5年年假有多少天): - 匹配分块:员工年假政策:正式员工入职满1年不满3年,每年享有5天带薪年假;入职满3年不满10年,每年享有10天带薪年假;入职满10年及以上, 每年享有15天带薪年假。(相似度:0.5489) - 匹配分块:请假流程:员工请假需提前3天通过企业OA提交申请,经直属领导审批通过后方可生效。紧急情况无法提前申请的,需24小时内补提申请 。(相似度:0.6494) 【调试】向量库已保存到 local_faiss_index 目录
from langchain_community.llms import Ollama
from langchain_core.prompts import ChatPromptTemplate
from langchain.chains import create_retrieval_chain
from langchain.chains.combine_documents import create_stuff_documents_chain
from langchain_core.runnables import RunnableConfig
# 初始化Ollama LLM(扩展:自定义参数)
llm = Ollama(
model="deepseek-r1:1.5b", # 使用自定义模型(或qwen2:0.5b)
temperature=0.1, # 低温度=低随机性
num_ctx=4096, # 扩展上下文窗口
timeout=30 # 超时时间(避免模型响应过慢)
)
# 调试:测试LLM基础调用
test_prompt = "你是谁?请用一句话回答"
llm_response = llm.invoke(test_prompt)
print(f"【调试】LLM基础调用测试:\n提问:{test_prompt}\n回答:{llm_response}\n")
# 定义提示模板(增强:明确来源标注)
prompt = ChatPromptTemplate.from_template(
"请严格根据以下上下文内容回答用户问题,禁止编造任何信息。\n"
"上下文来源:员工手册(2025版)\n"
"上下文内容:{context}\n\n"
"用户问题:{input}\n"
"回答要求:1. 仅使用上下文信息;2. 简洁明了;3. 标注信息对应的规则类型。"
)
# 创建文档整合链
document_chain = create_stuff_documents_chain(llm, prompt)
# 加载本地向量库并转为检索器(扩展:添加元数据过滤)
retriever = FAISS.load_local(
"local_faiss_index",
embeddings,
allow_dangerous_deserialization=True
).as_retriever(
search_kwargs={
"k": 2, # 检索Top2
"filter": {"content_type": "employee_rule"} # 仅检索员工规则类内容
}
)
# 构建完整RAG问答链
retrieval_chain = create_retrieval_chain(retriever, document_chain)
# 调试:查看检索链结构
print(f"【调试】RAG链结构:\n{retrieval_chain.get_graph().draw_ascii()}\n")输出结果:
【调试】LLM基础调用测试: 提问:你是谁?请用一句话回答 回答:<think> 您好!我是由中国的深度求索(DeepSeek)公司开发的智能助手DeepSeek-R1。如您有任何任何问题,我会尽我所能为您提供帮助。 </think> 您好!我是由中国的深度求索(DeepSeek)公司开发的智能助手DeepSeek-R1。如您有任何任何问题,我会尽我所能为您提供帮助。 【调试】RAG链结构: +------------------------+ | Parallel<context>Input | +------------------------+ *** *** ** ** ** ** +--------+ ** | Lambda | * +--------+ * * * * * * * +----------------------+ +-------------+ | VectorStoreRetriever | | Passthrough | +----------------------+ +-------------+ ***************************省略*********************
完整的RAG链结构:

RAG 链结构总结:
该 RAG 链是一套并行化设计的检索增强生成架构,核心特点是通过多分支并行处理、分层式数据流转,兼顾检索精准性与成效率,整体可拆解为三大核心阶段,各环节分工明确且协同高效:
1. 并行检索阶段(上游)
以Parallel<context>Input为统一输入入口,拆分为两条并行分支:
2. 提示构建阶段(中游)
检索结果与原始查询进入Parallel<answer>Input后,再次拆分并行分支处理:
3. 生成输出阶段(下游)
完整提示词传入Ollama(本地大模型)完成回答生成,生成结果经StrOutputParser(字符串输出解析器)格式化后,与透传的原始查询再次汇总至Parallel<answer>Output,最终输出 “原始问题 + 精准回答” 的完整结果。
简单总结:
print("="*50)
print("本地智能问答助手已启动(输入'quit'退出,输入'debug'查看检索详情)")
print("="*50)
while True:
user_question = input("\n请提问:")
if user_question.lower() == "quit":
print("助手已关闭!")
break
if user_question.lower() == "debug":
print("\n【调试模式】当前检索器配置:")
print(f"- 检索数量k:{retriever.search_kwargs['k']}")
print(f"- 元数据过滤条件:{retriever.search_kwargs.get('filter', '无')}")
print(f"- 向量库文档总数:{len(vectorstore.index_to_docstore_id)}")
continue
# 调用问答链并记录耗时
import time
start_time = time.time()
result = retrieval_chain.invoke({"input": user_question})
end_time = time.time()
# 详细调试输出
print("\n" + "-"*30 + "调试信息" + "-"*30)
print(f"【调试】检索到的上下文:")
for i, doc in enumerate(result["context"]):
print(f" {i+1}. {doc.page_content}(元数据:{doc.metadata})")
print(f"【调试】回答生成耗时:{end_time - start_time:.2f}秒")
print("-"*66)
# 最终回答输出
print(f"\n回答:{result['answer']}")输出结果:
================================================== 本地智能问答助手已启动(输入'quit'退出,输入'debug'查看检索详情) ================================================== 请提问:入职5年的正式员工有多少天年假? ------------------------------调试信息------------------------------ 【调试】检索到的上下文: 1.员工年假政策:正式员工入职满1年不满3年,每年享有5天带薪年假;入职满3年不满10年,每年享有10天带薪年假;入职满10年及以上,每年享有15天带 薪年假。 (元数据:{'source': 'knowledge.txt', 'chunk_id': 1, 'content_type': 'employee_rule', 'update_time': '2025-12-17'}) 2.请假流程:员工请假需提前3天通过企业OA提交申请,经直属领导审批通过后方可生效。紧急情况无法提前申请的,需24小时内补提申请。 (元数据:{'source': 'knowledge.txt', 'chunk_id': 2, 'content_type': 'employee_rule', 'update_time': '2025-12-17'}) 【调试】回答生成耗时:18.78秒 ------------------------------------------------------------------ 回答:<think> 好的,我现在需要解决用户的问题:“入职5年的正式员工有多少天年假?”首先,我要仔细阅读提供的上下文内容,确保理解所有相关的政策和规定。 根据上下文来源,特别是“员工年假政策”,我了解到正式员工的年假安排是基于入职时间的不同阶段。具体来说: 1. **入职满1年不满3年**:每年享有5天带薪年假。 2. **入职满3年不满10年**:每年享有10天带薪年假。 3. **入职满10年及以上**:每年享有15天带薪年假。 现在,问题中的员工是入职了5年。根据第二条政策,入职满3年不满10年,所以他们的年假是10天。接下来,我需要确认是否有其他相关的规则或例外情况影响这个结果。 在“请假流程”部分提到,员工必须提前3天通过企业OA提交申请,并经直属领导审批才能生效。这里的信息可能会影响是否可以享受年假,但根据上下文内容,年假的发放是基于入职时间的,而不是请假的具体安排。因此,即使有请假的情况,年假的发放仍然按照入职满1年的标准来计算。 此外,紧急情况无法提前申请的员工需要24小时内补提申请,这可能影响他们的年假享受,但同样,根据政策,他们仍然可以享受相应的年假天数。 综上所述,入职5年的正式员工应享有10天带薪年假。因此,回答时应该明确指出这一点,并标注对应的规则类型。 </think> 根据“员工年假政策”,入职满3年不满10年,员工每年享有10天带薪年假。因此,入职5年的正式员工有10天年假。 **答案:10天** 请提问:加班调休有效期是多久? ------------------------------调试信息------------------------------ 【调试】检索到的上下文: 1. 加班调休规则:工作日加班可按1:1比例申请调休,调休需在加班后3个月内使用完毕,逾期作废。 # 新增冗余内容(用于调试检索过滤效果) 办公用品申领:每月5号可提交申领表,限领笔、笔记本等基础用品。(元数据:{'source': 'knowledge.txt', 'chunk_id': 3, 'content_type': 'employee_rule', 'update_time': '2025-12-17'}) 2.请假流程:员工请假需提前3天通过企业OA提交申请,经直属领导审批通过后方可生效。紧急情况无法提前申请的,需24小时内补提申请。(元数据:{'source': 'knowledge.txt', 'chunk_id': 2, 'content_type': 'employee_rule', 'update_time': '2025-12-17'}) 【调试】回答生成耗时:10.93秒 ------------------------------------------------------------------ 回答:<think> 好的,我现在需要解决用户的问题:“加班调休有效期是多久?”根据提供的上下文和规则,我得仔细分析。 首先,查看上下文来源:员工手册(2025版)。里面明确提到“工作日加班可按1:1比例申请调休,调休需在加班后3个月内使用完毕,逾期作废。” 这里提到了“3个月”,所以直接引用这个信息作为回答。 接下来,用户的问题是关于有效期的,而规则中也明确说明了调休的有效期是3个月。因此,回答应该直接引用这条规则,不需要添加额外的信息或冗余内容。 最后,确保回答简洁明了,并且标注对应规则类型,这里就是加班调休规则。 </think> 根据员工手册(2025版)中的规则,加班调休有效期为3个月。 请提问:debug 【调试模式】当前检索器配置: - 检索数量k:2 - 元数据过滤条件:{'content_type': 'employee_rule'} - 向量库文档总数:4 请提问:quit 助手已关闭!
# 计算检索准确率(人工标注+自动化验证)
test_cases = [
("入职5年年假", ["10天带薪年假"]),
("加班调休有效期", ["3个月内使用完毕"])
]
def evaluate_retrieval(query, expected_answer):
result = retrieval_chain.invoke({"input": query})
return 1 if any(ans in result["answer"] for ans in expected_answer) else 0
# 计算整体准确率
accuracy = sum(evaluate_retrieval(q, e) for q, e in test_cases) / len(test_cases)
print(f"【调试】检索准确率:{accuracy*100}%")输出结果:
【调试】检索准确率:100.0%
今天我们完整呈现了 RAG 与向量数据库的深度融合应用,核心价值在于将抽象的技术逻辑转化为可落地的实战示例,同时借助 Ollama 让本地大模型部署与 RAG 结合更具实用性。
向量数据库作为 RAG 的 “记忆中枢”,解决了非结构化文本的语义检索难题,而合理的文本分块、元数据过滤、相似度调试等优化手段,进一步提升了检索精度;Ollama轻量化部署、自定义模型参数的特性,让我们也能低成本搭建私有化 RAG 系统,兼顾数据安全与响应效率。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。