本文较长,建议点赞收藏。更多AI大模型应用开发学习视频及资料,在智泊AI。
你是否曾遇到过这样的窘境:当你问一个通用大模型(比如DeepSeek)关于你公司内部文档、专业领域论文或者某个小众爱好的问题时,它总是始一本正经地胡说八道?
这不怪大模型,毕竟大模型在训练的时候没有学习过我们的专属知识。但如果我们能给它“开小灶”,让它在回答问题前,先“阅读”一下我们指定的资料,那效果会不会炸裂?
答案是肯定的!这就是我们今天要介绍的主角—— RAG(Retrieval-Augmented Generation,检索增强生成) 技术。
简单来说,RAG就像是给大模型配上了一个可以随时查阅的“开卷考试”资料库。当用户提问时,系统会先从这个资料库里检索出最相关的内容,然后把这些内容连同问题一起交给大模型,让它根据这些“参考资料”来生成最终答案。
这样做的好处显而易见:
- 提升准确性:有效减少模型“幻觉”,让回答都有据可循。知识实时更新:只需更新我们的知识库,就能让模型掌握最新信息。降低成本:无需重新训练一个庞大的模型,省时又省钱。
今天我们尝试使用全开源、全本地的工具,搭建一个属于我们自己的RAG问答应用。我们只需要一台能跑得动本地模型的电脑,就能让AI成为我们专属的知识问答专家!
准备工作:我们的“兵器库”
在开始之前,我们需要先配置好开发环境。打开你的终端,运行以下命令来安装所有必需的Python库:
pip install pymupdf openai==1.99.0 numpy chromadb pydantic==2.11.0- pymupdf: 用于从PDF文件中提取文字。openai: 虽然名字是OpenAI,但它是一个通用的API客户端,我们用它来连接本地部署的LM Studio服务。numpy: 用于处理数值计算,是Python数据科学的基石。chromadb: 一款非常轻量、易于上手的开源向量数据库,负责存储和检索我们的知识。
同时,请确保你已经安装并运行了 LM Studio。
什么是LM Studio? LM Studio是一款能让你在个人电脑上(Windows/Mac/Linux)轻松下载、管理和运行各种开源大模型的软件。它还提供了一个与OpenAI API兼容的服务端点,让我们可以像调用官方API一样调用本地模型。 操作步骤:
- 前往 lmstudio.ai 下载并安装。在应用内搜索并下载一个适合中文的开源模型(例如 Qwen、Yi 或 DeepSeek 系列的GGUF格式模型)。切换到 "developer" 标签页,选择你下载好的模型,然后点击 "开发者"标签,将"status"切换为start。
第一部分:RAG之“R” - 构建智能检索系统
这部分是RAG的基石,我们将完成从原始文档到可检索知识库的转化。
第1步:知识注入 - 从PDF中提取文本
万丈高楼平地起,我们的第一步是建立知识库。这里,我们以一个PDF文件(PDF文件需要那些内容都是文字形式的,如果PDF内容都是图片将无法解析)为例,你可以将其换成任何你希望AI学习的中文文档。
import fitz # PyMuPDF库import osdefextract_text_from_pdf(pdf_path): """从PDF文件中提取纯文本内容。""" try: doc = fitz.open(pdf_path) full_text = "" for page in doc: full_text += page.get_text("text") doc.close() return full_text except Exception as e: print(f"读取PDF失败: {e}") returnNone# 为了方便演示,如果PDF不存在,我们直接使用一段示例文本# 你可以创建自己的 "data" 文件夹和 "人工智能简史.pdf" 文件ifnot os.path.exists("data"): os.makedirs("data")pdf_path = "data/LLMBook.pdf"if os.path.exists(pdf_path): extracted_text = extract_text_from_pdf(pdf_path)else: print("PDF文件未找到,将使用内置的示例文本。") extracted_text = """2022 年底,ChatGPT震撼上线,大语言模型技术迅速“席卷”了整个社会,人工智能技术因此迎来了一次重要进展。面对大语言模型的强大性能,我们不禁要问:支撑这些模型的背后技术究竟是什么?这一问题无疑成为了众多科研人员的思考焦点。必须指出的是,大模型技术并不是一蹴而就,其发展历程中先后经历了统计语言模型、神经网络语言模型、预训练语言模型等多个发展阶段,每一步的发展都凝结了众多科研工作者的心血与成果。作为大语言模型技术的重要推动者,OpenAI公司引领了本次技术变革,让我们再次回顾其针对大模型技术的研发历程。2015年,OpenAI 公司正式创立,开始探索通用人工智能的技术路线。早期的OpenAI团队围绕强化学习、多模态、语言模型等几个重要方向进行了深入研究。其中,由Ilya Sutskever 领导的团队主要关注语言模型的研究。当谷歌2017年推出基于注意力机制的Transformer模型后,OpenAI团队迅速洞察到了其潜在的优越性,认为这种模型可能是一种大规模可扩展训练的理想架构。基于此,OpenAI团队开始构建GPT系列模型,并于2018年推出了第一代GPT模型—GPT-1,能够通过“通用文本训练-特定任务微调”的范式去解决下游任务。接下来,GPT-2和GPT-3模型通过扩大预训练数据和模型参数规模,显著提升了模型性能,并且确立了基于自然语言形式的通用任务解决路径。在GPT-3的基础上,OpenAI又通过代码训练、人类对齐、工具使用等技术对于模型性能不断升级,推出了功能强大的GPT-3.5系列模型。2022年11月,ChatGPT正式上线,能够以对话形式解决多种任务,使得用户能够通过网络API体验到语言模型的强大功能。2023年3月,OpenAI推出了标志性的GPT-4模型,将模型能力提升至全新高度,并将其扩展至拥有多模态功能的GPT-4V模型。反观GPT系列模型的发展历程,有两点令人印象深刻。第一点是可拓展的训练架构与学习范式:Transformer架构能够拓展到百亿、千亿甚至万亿参数规模,并且将预训练任务统一为预测下一个词这一通用学习范式;第二点是对于数据质量与数据规模的重视:不同于BERT时代的预训练语言模型,这次大语言模型的成功与数据有着更为紧密的关系,高质量数据、超大规模数据成为大语言模型的关键基础。上述的思路看似简单,但能够从早期众多的技术路线中寻找到这条路线,并且坚定地去执行这条路线,这就是OpenAI成功的关键所在。回顾OpenAI的早期论文,实际上早在GPT-2的论文中,就深入讨论了基于大规模文本预训练的通用任务学习范式,让人不禁感叹OpenAI团队的技术前瞻性。虽然这种研究模式很难复制,但是值得我们去思考、学习。"""# print(extracted_text[:200]) # 打印前200个字符看看第2步:文本分块 - 把“大象”装进“冰箱”
直接把整篇文章丢给模型是不现实的,太长的文本会超出模型的“记忆”上限(上下文窗口)。因此,我们需要把长文本切分成一个个更小、更易于管理和检索的“知识块”(Chunks)。
def chunk_text(text, chunk_size=300, overlap=50): """ 将文本按指定大小和重叠率进行分块。 参数: text (str): 待分块的文本。 chunk_size (int): 每个块的理想字符数。 overlap (int): 相邻块之间的重叠字符数,有助于保持上下文连续。 返回: list[str]: 文本块列表。 """ chunks = [] start = 0 while start < len(text): end = start + chunk_size chunks.append(text[start:end]) start += chunk_size - overlap return chunkstext_chunks = chunk_text(extracted_text)print(f"文本被分成了 {len(text_chunks)} 个块。")print("--- 第一个块示例 ---")print(text_chunks[0])第3步:向量化 - 让文字变成“数学”
计算机不理解文字,只懂数字。为了让计算机能“理解”文本并比较其相似度,我们需要将文本块转换成一串数字,这个过程叫做嵌入(Embedding),生成的结果就是向量(Vector)。
我们将使用本地LM Studio服务来生成这些向量。你需要确保在LM Studio中加载的模型支持生成嵌入向量(大部分模型都支持)。
from openai import OpenAIimport numpy as np# 配置客户端,使其指向你的本地LM Studio服务器client = OpenAI(base_url="http://localhost:1234/v1", api_key="not-needed")defcreate_embeddings(chunks, model="text-embedding-qwen3-embedding-0.6b"): """使用本地模型为文本块列表创建向量嵌入。""" # 注意:LM Studio可能默认使用其加载的聊天模型进行嵌入, # model参数可能不会切换模型,但仍建议填写以保持代码清晰。 response = client.embeddings.create( model=model, input=chunks ) # 从响应中提取嵌入向量 embeddings = [np.array(data.embedding) for data in response.data] return embeddings# 为我们所有的文本块创建向量chunk_embeddings = create_embeddings(text_chunks)print(f"成功为 {len(chunk_embeddings)} 个文本块创建了向量。")print(f"每个向量的维度是: {len(chunk_embeddings[0])}")第4步:向量入库 - 用ChromaDB构建知识索引
现在我们有了文本块和对应的向量,是时候把它们存入专业的向量数据库ChromaDB了。这就像是为我们的知识建立了一个高效的索引,方便后续快速查找。
import chromadb# 1. 初始化ChromaDB客户端# 这会在你的项目目录下创建一个 .chroma 文件夹来持久化存储数据chroma_client = chromadb.Client()# 2. 创建一个集合(Collection),类似于数据库中的一张表# 如果集合已存在,先删除再创建,确保我们从一个干净的状态开始collection_name = "ai_history_db"if collection_name in [c.name for c in chroma_client.list_collections()]: chroma_client.delete_collection(name=collection_name)collection = chroma_client.create_collection(name=collection_name)# 3. 将文本块、向量和唯一ID存入集合# 我们需要为每个块创建一个唯一的IDchunk_ids = [str(i) for i inrange(len(text_chunks))]collection.add( embeddings=chunk_embeddings, documents=text_chunks, ids=chunk_ids)print(f"知识库 '{collection_name}' 创建并填充完毕!")第5步:语义检索 - 智能查询
知识库建好了,现在来试试效果。我们定义一个函数,它接收一个问题,将其向量化,然后在ChromaDB中查询最相似的文本块。
def semantic_search(query, k=3): """ 在ChromaDB中执行语义搜索。 参数: query (str): 用户的查询问题。 k (int): 需要返回的最相关文本块的数量。 返回: list[str]: 包含最相关文本块的列表。 """ # 1. 将查询问题也转换成向量 query_embedding = create_embeddings([query])[0] # 2. 在集合中查询最相似的 k 个结果 results = collection.query( query_embeddings=[query_embedding.tolist()], # 查询向量需要是 list 格式 n_results=k ) return results['documents'][0]# 测试一下!my_query = "大模型有什么特点?"relevant_chunks = semantic_search(my_query)print(f"对于问题: '{my_query}'")print("\n--- 检索到的相关信息如下 ---")for i, chunk inenumerate(relevant_chunks): print(f"【相关片段 {i+1}】\n{chunk}\n")第二部分:RAG之“G” - 结合上下文生成答案
检索(R)部分已经完成!我们成功地根据问题找到了相关的“参考资料”。现在,我们进入生成(G)部分,让本地大模型基于这些资料,给出精准的回答。
第6步:构建提示词(Prompt)
这是RAG的“灵魂”所在。我们不能只把问题丢给模型,而是要创建一个包含“指令”、“上下文(检索到的知识)”和“用户问题”的完整提示词。
def build_prompt(query, context_chunks): """构建最终的提示词。""" context = "\n\n".join(context_chunks) # 一个优秀的提示词模板至关重要 prompt_template = f"""你是一个严谨的AI问答助手。请严格根据下面提供的“参考资料”来回答用户的问题。不要编造任何参考资料中没有的信息。如果资料无法回答问题,请回复:“根据我手头的资料,我无法回答这个问题。”--- 参考资料 ---{context}--- 用户问题 ---{query}--- 你的回答 ---""" return prompt_template# 构建最终的提示词final_prompt = build_prompt(my_query, relevant_chunks)print("--- 发送给大模型的最终提示词 ---")print(final_prompt)第7步:生成最终答案
万事俱备,只欠东风!我们将这个精心构造的提示词发送给在LM Studio中运行的大模型,获取最终答案。
def generate_response(prompt, model="qwen3-14b-128k"): """使用本地大模型生成回答。""" # client 已经在前面初始化过了 response = client.chat.completions.create( model=model, # 'qwen3-14b-128k' 是LM Studio的默认占位符 messages=[ {"role": "user", "content": prompt} ], temperature=0.7, # temperature可以控制回复的创造性,对于问答,低一点好 ) return response.choices[0].message.content# 获取最终答案!final_answer = generate_response(final_prompt)print("\n\n============ 最终回答 ============")print(final_answer)示例输出可能如下:
============ 最终回答 ============
根据提供的资料,大模型(Large Language Model, LLM)具有以下特点:
- 性能提升趋势的多样性
- 大模型的能力可通过两种不同视角分析:扩展法则:以语言建模损失为指标,整体表现呈平滑、可预测的持续改进趋势,但可能存在边际效益递减现象。涌现能力:以任务性能为指标,在规模扩展时可能出现骤然跃升的趋势,且这种提升不可预测,一旦出现会带来显著的能力飞跃。
- 训练过程复杂性与成本高
- 训练大模型通常需要更长时间,并需设计针对性的性能诊断方法(如GPT-4提出的可预测训练方法)。由于从头训练成本高昂,研究者常基于已公开模型(如LLaMA、FLAN-T5)进行继续预训练或微调,但需解决灾难性遗忘、能力均衡和任务特化等问题。
- 数据依赖与潜在局限
训练数据在开始前需完成采集,可能导致信息过时或知识错误。因此,需要有效的微调策略以注入更新后的知识。
- 特定任务的显著优势
- 大模型可能具备小模型无法实现的能力(如GPT-2的涌现能力),并在实际应用中表现出色(例如ChatGPT在对话任务中的突破性表现)。
- 学术关注度高
- 以ChatGPT为代表的大语言模型应用引发了广泛关注,相关研究论文数量迅速增长。
看到这个结果了吗?大模型没有泛泛而谈,有使用了我们提供的《LLMBook.pdf》中的内容来回答问题,但是回答的效果貌似没有最优,但是后续我们可以通过一些列的优化措施进行优化,提升RAG的效果。
总结与展望
我们已经成功地走完了一个最简RAG应用的完整流程,从数据处理到本地部署,全程DIY!
回顾一下我们的旅程:
- 环境配置:安装了必要的库,并启动了LM Studio本地服务。知识检索 (R):我们提取、分块、向量化了PDF文本,并使用ChromaDB构建了一个可随时查询的本地知识库。增强生成 (G):我们将检索到的知识与用户问题整合成一个强大的提示词,并交由本地大模型生成了有理有据的答案。
这只是一个开始。基于这个框架,我们可以继续探索:
- 更复杂的知识库:加载多种格式的文件(Word, TXT, HTML),甚至整个网站。更优化的分块和检索策略:尝试不同的分块大小,或者更先进的检索算法。更强大的模型:在LM Studio中尝试不同的开源大模型,看看哪个效果最好。构建用户界面:使用Streamlit或Gradio为你的RAG应用创建一个漂亮的网页界面。
学习资源推荐
如果你想更深入地学习大模型,以下是一些非常有价值的学习资源,这些资源将帮助你从不同角度学习大模型,提升你的实践能力。
本文较长,建议点赞收藏。更多AI大模型应用开发学习视频及资料,在智泊AI。
