1. 大模型应用开发的三种模式
Prompt 模式
适用场景:轻量需求、快速验证,无需额外数据和训练。比如通用问答、文案生成、简单指令执行(如文本摘要、格式转换),适合对结果个性化要求低、调用频率不高的场景,直接通过精准提示词驱动基础大模型输出结果。
RAG 模式
适用场景:需要外部知识库支撑的场景,比如企业内部文档问答、产品手册咨询、实时信息查询(如新闻、财报解读)。核心解决大模型 “知识过时”“信息不准确” 的问题,通过检索外部文档并拼接上下文给模型,无需训练,兼顾灵活性和准确性,适合数据频繁更新的场景。
Fine-tuning 模式
适用场景:高个性化、高适配性需求,比如垂直领域专属模型(如医疗诊断辅助、法律文书生成)、特定话术风格定制(如品牌客服话术)、复杂任务逻辑对齐。需要标注高质量数据集进行模型微调,开发成本和门槛较高,适合对模型输出稳定性、专业性要求极高的规模化场景。
2. 什么是 RAG ?
RAG(Retrieval-Augmented Generation)
- 检索增强生成,是一种结合信息检索(Retrieval)和文本生成(Generation)的技术
- RAG技术通过实时检索相关文档或信息,并将其作为上下文输入到生成模型中,从而提高生成结果的时效性和准确性。
2.1. RAG 的优势是什么?
解决知识时效性问题:大模型的训练数据通常是静态的,无法涵盖最新信息,而RAG可以检索外部知识库实时更新信息
减少模型幻觉:通过引入外部知识,RAG能够减少模型生成虚假或不准确内容的可能性
提升专业领域回答质量:RAG能够结合垂直领域的专业知识库,生成更具专业深度的回答
生成内容的溯源(可解释性)
2.2. RAG 的核心原理与流程
RAG(检索增强生成)的核心原理是 “先检索外部知识库的相关信息,再将检索结果与用户问题拼接成提示词,交给大模型生成回答”,以此解决大模型 “知识过时、信息不准确、幻觉” 的核心痛点,同时避免了 Fine-tuning 的高成本。
其完整流程分为 4 个核心步骤,可分为离线构建阶段和在线查询阶段:
离线构建知识库(一次构建,重复使用)
文档拆分:将原始文档(如 PDF、Word、网页)切割成大小合适的文本块(Chunk),避免因文本过长超出模型上下文窗口。
嵌入向量化:用嵌入模型(如 text-embedding-v4)将每个文本块转换成向量(Embedding),向量能表征文本的语义信息。
向量入库:将所有文本块的向量和对应的原始内容、元数据(如页码、来源)存入向量数据库(如 FAISS、Milvus)。
在线查询(用户交互阶段)
用户问题向量化:将用户的查询问题用同一嵌入模型转换成向量。
相似度检索:在向量数据库中检索与问题向量最相似的 Top-N 个文本块,这些文本块就是回答问题的核心依据。
Prompt 拼接:把检索到的文本块、用户问题、系统指令(如 “仅基于给定文档回答”)拼接成完整的提示词。
模型生成回答:将拼接好的提示词输入大模型,模型基于提示词中的文档信息生成准确、无幻觉的回答。
划重点:RAG 本质上就是重构了一个新的 Prompt!
4. LangChain快速搭建本地知识库检索
4.1.先安装指定版本的依赖库
这里必须指定版本,不然会报各种错误
pip install pypdf2pip install dashscopepip install langchain==0.0.286pip install langchain-community==0.0.9pip install openai==0.28.1pip install faiss-cpu
我这里用的是比较旧的版本,但是这个版本是可以跑的
4.2.代码如下
import osimport loggingimport picklefrom PyPDF2 import PdfReaderfrom langchain.chains.question_answering import load_qa_chainfrom langchain.chat_models import ChatOpenAI as OldChatOpenAIfrom langchain_community.embeddings import DashScopeEmbeddingsfrom langchain_community.callbacks.manager import get_openai_callbackfrom langchain.text_splitter import RecursiveCharacterTextSplitterfrom langchain_community.vectorstores import FAISSfrom typing import List, Tupledef read_and_split_pdf(pdf_path):# 初始化文本分割器(保持你的原有配置)text_splitter = RecursiveCharacterTextSplitter(separators=["\n\n", "\n", ".", " ", ""],chunk_size=512,chunk_overlap=128,length_function=len,)"""读取PDF并分割文本,返回:- chunks:分割后的文本块列表- chunk_page_numbers:每个文本块对应的页码列表"""# 初始化PDF阅读器pdf = PdfReader(pdf_path)chunks = [] # 存储分割后的所有文本块chunk_page_numbers = [] # 存储每个文本块对应的页码# 遍历每一页PDF(保留页码)for page_number, page in enumerate(pdf.pages, start=1):extracted_text = page.extract_text()if not extracted_text:logging.warning(f"No text found on page {page_number}.")continue# 关键:对当前页的文本单独分割(而非拼接后整体分割)page_chunks = text_splitter.split_text(extracted_text)# 为当前页的每个分割块绑定页码chunks.extend(page_chunks)chunk_page_numbers.extend([page_number] * len(page_chunks))return chunks, chunk_page_numbersdef create_knowledge_base(chunks, chunk_page_numbers, save_path) -> FAISS:"""处理文本并创建向量存储参数:chunks:分割后的文本块列表chunk_page_numbers:每个文本块对应的页码列表save_path: 可选,保存向量数据库的路径"""# 过滤空文本块(避免嵌入接口报错)valid_data = [(chunk, num) for chunk, num in zip(chunks, chunk_page_numbers) if chunk.strip()]valid_chunks = [item[0] for item in valid_data]valid_page_nums = [item[1] for item in valid_data]if not valid_chunks:raise ValueError("没有有效文本块可创建向量库,请检查PDF文本提取结果")# 打印有效文本块数量print(f"过滤空文本后,有效文本块数量: {len(valid_chunks)}")# 创建嵌入模型# 调用阿里百炼平台文本嵌入模型,配置环境变量 DASHSCOPE_API_KEYembeddings = DashScopeEmbeddings(model = "text-embedding-v4")print("从文本块创建知识库(分批处理)...")# 关键修复:分批处理文本块(DashScope v4 批量限制≤10)batch_size = 10knowledgeBase = None# 按批次遍历文本块for i in range(0, len(valid_chunks), batch_size):# 截取当前批次的文本和页码batch_chunks = valid_chunks[i:i+batch_size]batch_page_nums = valid_page_nums[i:i+batch_size]# 为当前批次创建向量库batch_db = FAISS.from_texts(batch_chunks, embeddings)# 合并批次到总向量库if knowledgeBase is None:knowledgeBase = batch_dbelse:knowledgeBase.merge_from(batch_db)print(f"已处理第 {i//batch_size + 1} 批,共处理 {min(i+batch_size, len(valid_chunks))}/{len(valid_chunks)} 个文本块")print("已从文本块创建完整知识库...")# 存储每个文本块对应的页码信息(仅保留有效文本块)page_info = {chunk: valid_page_nums[i] for i, chunk in enumerate(valid_chunks)}knowledgeBase.page_info = page_info# 如果提供了保存路径,则保存向量数据库和页码信息if save_path:# 确保目录存在os.makedirs(save_path, exist_ok=True)# 保存FAISS向量数据库knowledgeBase.save_local(save_path)print(f"向量数据库已保存到: {save_path}")# 保存页码信息到同一目录with open(os.path.join(save_path, "page_info.pkl"), "wb") as f:pickle.dump(page_info, f)print(f"页码信息已保存到: {os.path.join(save_path, 'page_info.pkl')}")return knowledgeBasedef load_knowledge_base(load_path: str, embeddings = None) -> FAISS:"""从磁盘加载向量数据库和页码信息参数:load_path: 向量数据库的保存路径embeddings: 可选,嵌入模型。如果为None,将创建一个新的DashScopeEmbeddings实例返回:knowledgeBase: 加载的FAISS向量数据库对象"""# 如果没有提供嵌入模型,则创建一个新的if embeddings is None:embeddings = DashScopeEmbeddings(model="text-embedding-v4")# 加载FAISS向量数据库,添加allow_dangerous_deserialization=True参数以允许反序列化#langchain==0.0.286 版本不支持 allow_dangerous_deserialization 参数knowledgeBase = FAISS.load_local(load_path, embeddings)print(f"向量数据库已从 {load_path} 加载。")# 加载页码信息page_info_path = os.path.join(load_path, "page_info.pkl")if os.path.exists(page_info_path):with open(page_info_path, "rb") as f:page_info = pickle.load(f)knowledgeBase.page_info = page_infoprint("页码信息已加载。")else:print("警告: 未找到页码信息文件。")return knowledgeBasedef queryInfo(query:str,knowledgeBase):# 设置查询问题# 执行相似度搜索,找到与查询相关的文档docs = knowledgeBase.similarity_search(query)# 2. 初始化 ChatOpenAI 客户端(兼容百炼)# 初始化对话大模型(适配 langchain==0.0.286 的旧版参数)chatLLM = OldChatOpenAI(openai_api_key=os.getenv("DASHSCOPE_API_KEY"), # 旧版参数名openai_api_base="https://dashscope.aliyuncs.com/compatible-mode/v1", # 旧版参数名model_name="deepseek-v3", # 旧版参数名(关键!不能写 model)temperature=0,#建议设为0,保证回答稳定max_tokens=2048,# 补充旧版必要参数:确保实例完整request_timeout=60,n=1)# 加载问答链chain = load_qa_chain(chatLLM, chain_type="stuff")# 准备输入数据input_data = {"input_documents": docs, "question": query}# 执行问答链并捕获原始响应response = chain.run(**input_data)print("回答:", response)print("来源:")# 记录唯一的页码unique_pages = set()# 显示每个文档块的来源页码for doc in docs:text_content = getattr(doc, "page_content", "")source_page = knowledgeBase.page_info.get(text_content.strip(), "未知")if source_page not in unique_pages:unique_pages.add(source_page)print(f"文本块页码: {source_page}")# 程序入口:只有直接运行这个文件时,才执行游戏if __name__ == "__main__":"""#先创建知识库chunks, chunk_page_numbers = read_and_split_pdf("G:\\AI\\workspace\\vscode\\202601\\123.pdf")print(chunks)print()print(chunk_page_numbers)create_knowledge_base(chunks,chunk_page_numbers,"./vector_db");"""#查询知识库knowledgeBase = load_knowledge_base("./vector_db")query = "客户经理被投诉了,投诉一次扣多少分"queryInfo(query,knowledgeBase);print()query = "总结下可越级聘用的情况"queryInfo(query,knowledgeBase);
上面的代码刚好对应了RAG 的流程
read_and_split_pdf:拆分文档create_knowledge_base:向量化和入库load_knowledge_base:查询知识库docs = knowledgeBase.similarity_search(query):用户问题向量化和相似度检索chain = load_qa_chain(chatLLM, chain_type="stuff"):prompt拼接response = chain.run(**input_data) :模型生成回答最后的页面还溯源了
