LangChain 学习实践(五):用 Ollama + FAISS 搭建本地 RAG 问答机器人

📅 2026/7/22 1:43:32
LangChain 学习实践(五):用 Ollama + FAISS 搭建本地 RAG 问答机器人
前四篇分别处理了模型、Prompt、Chain 和输出。RAG 把这些能力落到“基于文档回答问题”的真实场景先把知识离线写入向量库再在用户提问时检索相关片段最后要求模型严格依据上下文作答。阅读说明本文基于本地 LangChain 学习代码与课程笔记整理。示例中的模型、文件路径和参数需要按实际环境调整涉及 API Key 时只使用环境变量或占位符。1 学习目标理解RAG概念和作用理解RAG工作原理用 LangChain 构建简单RAG系统2 RAG相关介绍2.1 RAG概念是什么RAG是一种在大模型回答前先检索外部知识再生成答案的技术。通⽤的基础⼤模型存在一些问题幻觉问题LLM有时会⽣成看似合理但实际错误的信息LLM的知识不是实时的模型训练好后不自动更新知识导致部分信息滞后LLM领域知识是缺乏的大模型的知识来源于训练数据这些数据主要来自公开的互联网和开源数据集无法覆盖特定领域或高度专业化的内部知识为解决或缓解上述问题2020年Facebook发表了一篇论文——《Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks》提出了RAGRetrieval Augmented Generation检索增强生成技术。RAG是一种将大规模语言模型LLM与外部知识源的检索相结合以改进问答能力的工程框架。它使用来自私有或专有数据源的信息来辅助文本生成从而弥补LLM的局限性特别是在解决幻觉问题和提升时效性方面。对比四种主流方案维度PromptRAG微调Agent核心原理设计提示引导模型检索知识 生成数据训练改参数LLM 工具 多步决策是否新增知识❌✅✅依赖外部工具是否实时数据❌✅❌✅是否改模型❌❌✅❌开发成本低中高很高推理成本中中低高典型场景文案/问答知识库/客服分类/垂直领域自动化任务核心优点快速低成本可更新知识稳定可控可执行复杂任务当前定位基础能力主流辅助增强前沿2.2 RAG作用克服LLM“幻觉”问题LLM在生成文本时有时会“一本正经地胡说八道”即生成听起来合理但实际上不准确或捏造的信息这被称为“幻觉”。RAG通过提供外部事实依据显著减少了这种幻觉现象让LLM的输出更具事实性和可靠性。获取最新信息LLM的训练数据通常是静态的这意味着它们无法获取到训练截止日期之后发生的事件或更新的信息。RAG允许LLM连接到实时或定期更新的外部数据源如新闻、数据库、内部文档等从而提供最新、最及时的答案。领域特定知识增强对于特定行业或企业内部的知识LLM的通用训练数据往往不足。RAG能够将LLM与企业内部的知识库文档或特定领域的数据连接起来使LLM能够回答高度专业化的问题并提供更符合上下文的答案。成本低于模型微调传统上为了让LLM适应特定任务或数据需要进行昂贵的微调Fine-tuning。RAG提供了一种更经济高效的替代方案它无需修改LLM的底层参数只需更新外部知识库即可大大降低了维护和更新模型的成本。提高答案的可解释性和溯源性RAG可以引用其获取信息的来源这意味着用户可以查看LLM答案所依据的原始文档或数据增强了答案的透明度和用户信任度。RAG通过将检索和生成相结合既保留了传统检索问答的可靠性又获得了LLM的灵活性和自然表达能力。它能让AI始终基于最新的、可信的知识来回答问题同时保持对话的流畅自然。传统检索式问答 (Retrieval QA)✅ 可靠性高答案直接来自知识库有明确的来源✅ 知识可更新添加新文档即可更新知识❌ 灵活性差只能返回知识库中已有的内容❌ 表达生硬难以用自然语言组织答案纯LLM问答✅ 表达自然能用流畅的语言组织答案✅ 灵活理解可以理解各种表达方式的问题❌ 知识固化知识仅限于训练数据无法及时更新❌ 可靠性差容易产生幻觉难以验证答案准确性RAG方案✅ 可靠且可溯源答案基于检索到的具体文档✅ 知识可更新可以持续添加新的知识✅ 表达自然利用LLM的语言能力组织答案✅ 灵活理解能理解各种形式的问题✅ 成本可控主要消耗在必要的API调用上RAG的典型应用场景企业知识库问答帮助企业构建对内员工知识库或对外客户问答系统。法律法规、论文等参考场景需要给出权威来源或证据的回答。任何需要带有引用信息的回答场景。2.3 RAG 的工作原理1 工作流程图解2 RAG标准流程RAG 标准流程由索引Indexing、检索Retriever和生成Generation三个核心阶段组成。索引阶段通过处理多种来源多种格式的文档提取其中文本将其切分为标准长度的文本块chunk并进行嵌入向量化embedding向量存储在向量数据库vector database中。加载文件内容提取文本分割 形成chunk文本向量化将向量存储到向量数据库常见的向量数据库如下检索阶段用户输入的查询query被转化为向量表示通过相似度匹配从向量数据库中检索出最相关的文本块。query向量化在文本向量中匹配出与问句向量相似的top_k个生成阶段检索到的相关文本与原始查询共同构成提示词Prompt输入大语言模型LLM生成精确且具备上下文关联的回答。匹配出的文本作为上下文和问题一起添加到prompt中提交给LLM生成答案3 RAG问答机器人3.1 项目背景物流行业的客服场景。客户在查询物流信息时常常会提出关于运输状态、配送时效、包裹轨迹和派送范围等问题。随着业务量的增长传统人工客服难以做到实时、统一地答复。为此XX公司需要一个RAG问答机器人实现基于物流信息文档与运单数据构建知识库并利用RAG技术搭建智能物流查询系统以自动解答客户常见问题减轻客服压力提升物流信息服务的准确性和响应速度。3.2 项目思路离线部分本地知识文件加载读取文本切分向量化存入向量库在线部分query 向量化在文本向量中匹配出与问句向量相似的top_k个匹配出的文本作为上下文和问题一起添加到prompt中提交给LLM生成答案3.3 项目代码项目结构1 构建向量数据库目的读取文本切分向量化存入向量数据库构建检索器 演示 RAG系统的离线部分由知识库生成向量数据库。 工作流程 知识库 - 文档加载 - 文本分割 - 嵌入 - 向量数据库 fromlangchain_community.document_loadersimportPyMuPDFLoader# from langchain_unstructured import UnstructuredLoaderfromlangchain_text_splittersimportRecursiveCharacterTextSplitterfromlangchain_ollamaimportOllamaEmbeddingsfromlangchain_community.vectorstoresimportFAISS# 1.定义函数生成向量数据库defget_vector_db():# 1.加载文档# loader UnstructuredLoader(物流信息.txt)loaderPyMuPDFLoader(物流信息.pdf)dataloader.load()print(fdata-{data})# 2.文本分割text_splitterRecursiveCharacterTextSplitter(chunk_size128,chunk_overlap4)chunkstext_splitter.split_documents(data)# 3.创建Embedding模型embed_modelOllamaEmbeddings(modelmxbai-embed-large)# 4.生成向量数据库vector_dbFAISS.from_documents(chunks,embed_model)vector_db.save_local(./faiss/logistics)print(f向量数据库保存成功)returnchunks# 主程序if__name____main__:get_vector_db()e:\conda_envs\langchain_project\Lib\site-packages\tqdm\auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html from .autonotebook import tqdm as notebook_tqdm >2 构建RAG主逻辑目的定义prompt 并构建rag检索问答逻辑 演示 一个简单的RAG问答系统-物流信息查询助手。 工作流程 1.离线部分由知识库生成向量数据库。 2.在线部分用户输入问题系统从向量数据库中检索相关文档构造提示词并结合大模型进行回答。 importtimefromlangchain_core.promptsimportPromptTemplatefromlangchain_ollamaimportOllamaLLMfromlangchain_ollamaimportOllamaEmbeddingsfromlangchain_community.vectorstoresimportFAISS# 1.加载向量数据库embed_modelOllamaEmbeddings(modelmxbai-embed-large)vector_dbFAISS.load_local(./faiss/logistics,embed_model,allow_dangerous_deserializationTrue)start_timetime.time()# 2.定义函数将文档转为文本defget_related_docs(related_docs): 将相关文档列表转换为文本字符串供提示词使用。 related_content[]fordocinrelated_docs:related_content.append(doc.page_content.replace(\n\n,\n))return\n.join(related_content)# 3.定义函数构造提示词defdefine_prompt(): 构造提示词包含用户问题和相关文档内容。 question我的快递出发地是哪预计几天的时间到达# 1.从向量数据库中检索相关文档docsvector_db.similarity_search(question,k2)# 2.将相关文档转换为文本related_contentget_related_docs(docs)# 3.构造提示词prompt_template 基于已知信息简洁专业的回答用户问题不允许在答案中添加编造成分。 已知信息为 {related_content}。 用户问题为 {question}。 promptPromptTemplate(input_variables[related_content,question],templateprompt_template,)promptprompt.format(related_contentrelated_content,questionquestion)returnprompt# 4.定义函数调用大模型进行回答defget_answer():modelOllamaLLM(modelqwen3.5:4b)promptdefine_prompt()print(f构造的提示词:{prompt})resultmodel.invoke(prompt)returnresult# 主程序if__name____main__:resultget_answer()print(f答案:{result})end_timetime.time()print(f总耗时:{end_time-start_time:.4f}s)构造的提示词: 基于已知信息简洁专业的回答用户问题不允许在答案中添加编造成分。 已知信息为 出发地⼴州 ⽬的地重庆 预计运输时间3天 仓储信息 仓库名称东⽅仓储中⼼ 仓库位置深圳市 存储货物类型电⼦产品 存储条件常温仓储 当前库存量1000件 物流公司速达物流 公司总部北京市 业务范围国际快递、仓储管理 货物追踪 货物编号ABC123456 发货⽇期2023-01-15 当前位置上海分拨中⼼ 预计到达⽇期2023-01-20 运输⽅式 运输公司快运通 运输⽅式陆运。 用户问题为 我的快递出发地是哪预计几天的时间到达。 答案: 出发地是广州预计3天到达。 总耗时: 33.8501s3 构建RAG前后端交互目的通过Gradio来实现前后端交互。# Logistics_Assistant_Web.pyimportgradioasgrimporttime# from get_vector_ollama import *fromlangchain_core.promptsimportPromptTemplatefromlangchain_ollamaimportOllamaLLMimportos# 全局变量存储向量数据库current_dbNonedefcreate_vector_db_from_file(file_path): 从上传的文件创建向量数据库 支持 txt, pdf, docx 格式 try:iffile_path.endswith(.txt):fromlangchain_community.document_loadersimportTextLoader loaderTextLoader(file_path,encodingutf-8)eliffile_path.endswith(.pdf):fromlangchain_community.document_loadersimportPyPDFLoader loaderPyPDFLoader(file_path)eliffile_path.endswith(.docx):fromlangchain_community.document_loadersimportDocx2txtLoader loaderDocx2txtLoader(file_path)else:raiseValueError(f不支持的文件格式:{file_path})documentsloader.load()# 文本分割fromlangchain_text_splittersimportRecursiveCharacterTextSplitter text_splitterRecursiveCharacterTextSplitter(chunk_size500,chunk_overlap50)textstext_splitter.split_documents(documents)# 创建向量数据库embeddingsOllamaEmbeddings(modelmxbai-embed-large,temperature0)dbFAISS.from_documents(texts,embeddings)returndbexceptExceptionase:raiseedefload_default_db(): 加载默认的向量数据库 globalcurrent_dbtry:embeddingsOllamaEmbeddings(modelmxbai-embed-large,temperature0)current_dbFAISS.load_local(faiss/wuliu,embeddings,allow_dangerous_deserializationTrue)exceptExceptionase:print(f加载默认向量数据库失败:{e})current_dbNonedefhandle_file_upload(file_obj): 处理文件上传并创建向量数据库 globalcurrent_dbtry:iffile_objisNone:return未选择文件,Nonefile_pathfile_obj.name current_dbcreate_vector_db_from_file(file_path)returnf成功加载文件:{os.path.basename(file_path)},current_dbisnotNoneexceptExceptionase:returnf处理文件时出错:{str(e)},Nonedefget_related_content(related_docs): 提取相关文档内容 related_content[]fordocinrelated_docs:related_content.append(doc.page_content.replace(\n\n,\n))return\n.join(related_content)defanswer_question(question): 回答用户提出的问题 globalcurrent_db start_timetime.time()try:# 如果没有加载向量数据库则尝试加载默认数据库ifcurrent_dbisNone:embeddingsOllamaEmbeddings(modelmxbai-embed-large,temperature0)current_dbFAISS.load_local(faiss/wuliu,embeddings,allow_dangerous_deserializationTrue)# 搜索相关文档docscurrent_db.similarity_search(question,k2)related_contentget_related_content(docs)# 构建提示词模板PROMPT_TEMPLATE 基于以下已知信息简洁和专业的来回答用户的问题。不允许在答案中添加编造成分。 已知内容: {context} 问题: {question}promptPromptTemplate(input_variables[context,question],templatePROMPT_TEMPLATE,)formatted_promptprompt.format(contextrelated_content,questionquestion)# 使用模型生成答案modelOllamaLLM(modelqwen3.5:4b)resultmodel.invoke(formatted_prompt)print(f答案{result})end_timetime.time()retrieved_content_outputrelated_content[:500]...iflen(related_content)500elserelated_content# 返回答案和相关信息returnresult,f{end_time-start_time:.4f}s,retrieved_content_outputexceptExceptionase:end_timetime.time()returnf处理过程中出现错误:{str(e)},f{end_time-start_time:.4f}s,defcreate_gradio_interface(): 创建Gradio界面 withgr.Blocks(title智能物流助手)asdemo:gr.Markdown(# 智能物流助手)gr.Markdown(基于向量数据库的物流信息查询系统)withgr.Row():withgr.Column():# 文件上传组件file_inputgr.File(label上传知识库文件,file_types[.txt,.pdf,.docx],file_countsingle)upload_btngr.Button(加载文件到知识库)upload_statusgr.Textbox(label上传状态,interactiveFalse)question_inputgr.Textbox(label请输入您的物流问题,placeholder例如我的快递出发地是哪预计几天的时间到达,lines3)submit_btngr.Button(获取答案,variantprimary)# 示例问题gr.Examples(examples[我的快递出发地是哪,预计几天的时间到达,我的订单状态如何,物流信息是什么],inputsquestion_input)withgr.Column():processing_time_outputgr.Textbox(label处理时间,interactiveFalse)retrieved_content_outputgr.Textbox(label检索到的相关内容,lines10,interactiveFalse)answer_outputgr.Textbox(labelAI回答,lines8,interactiveFalse)# 事件绑定upload_btn.click(fnhandle_file_upload,inputsfile_input,outputs[upload_status,gr.State()])submit_btn.click(fnanswer_question,inputsquestion_input,outputs[answer_output,processing_time_output,retrieved_content_output])returndemoif__name____main__:# 初始化时加载默认数据库load_default_db()interfacecreate_gradio_interface()interface.launch(server_name127.0.0.1,server_port7860,shareFalse,# 设为True可创建公共链接debugTrue)本篇小结RAG 的重点不是单纯接上向量数据库而是检索质量、上下文约束和可验证性。后续可以继续扩展引用溯源、Rerank、评测集、权限控制和增量索引。