RAG技术详解:从原理到实践,构建基于大模型的智能问答系统

RAG技术详解:从原理到实践,构建基于大模型的智能问答系统 1. 从“幻觉”到“有据可查”为什么我们需要RAG如果你最近在折腾大语言模型LLM不管是ChatGPT的API还是开源的Llama、Qwen大概率都遇到过这样的场景你问它一个非常具体、非常新的问题比如“我们公司最新的产品定价策略是什么”或者“帮我总结一下昨天行业峰会上某位专家的核心观点”它要么开始一本正经地胡说八道业内称为“幻觉”要么直接告诉你它的知识截止到某个日期对此无能为力。这种无力感正是驱动我们探索RAG检索增强生成技术的核心痛点。简单来说你可以把一个大语言模型想象成一个博闻强识、但记忆永远停留在学生时代的“老学究”。它脑子里装满了教科书和经典文献训练数据但对于毕业之后世界发生的新变化、你公司内部的规章制度、或者你个人电脑里的那些文档它一无所知。RAG要做的就是给这位“老学究”配一个超级高效的“个人数字助理”。每当“老学究”需要回答问题时这个助理会立刻去翻阅最新的报告、公司文档、数据库或者网络资料外部知识库把最相关的几页内容找出来放在“老学究”面前让它基于这些最新的、权威的资料来组织答案。这样生成的回答不仅准确性大幅提升还能告诉你答案的依据是哪份文件的哪一段做到了“有据可查”。这不仅仅是让回答更准确。从工程和商业角度看RAG解决了几大关键问题第一是成本从头训练或微调Fine-tuning一个模型来学习新知识耗费的算力和资金是巨大的而RAG只需要处理文档和检索成本低得多。第二是时效性世界在变知识在更新RAG允许我们随时更新后端的知识库模型就能立刻获取最新信息无需重新训练。第三是可控性与安全性你可以精确控制模型能“看到”哪些资料比如只开放公开手册屏蔽机密设计文档从而避免模型泄露敏感信息或依据不可靠来源作答。所以无论你是想构建一个能回答产品问题的智能客服一个能分析内部财报的决策助手还是一个基于个人笔记的写作伴侣RAG都是将通用大模型“专业化”、“私有化”、“实时化”的首选技术路径。它不改变模型本身而是改变了模型获取信息的渠道这是一种巧妙且高效的“增强”。2. RAG系统核心架构与工作流程拆解一个完整的RAG系统远不止是“搜索生成”那么简单。它是一个精心设计的流水线每个环节的选择和优化都直接影响最终效果。我们可以将其核心工作流程拆解为四个关键阶段文档处理、检索、增强与生成。下面我们来逐一拆解看看每个环节到底在做什么以及为什么这么做。2.1 文档处理从原始资料到模型能理解的“语言”这是所有工作的基石也是最容易埋下隐患的环节。你的原始数据可能是PDF、Word、PPT、HTML网页甚至是数据库里的表格。模型无法直接理解这些格式我们需要将其转化为一种它擅长处理的格式——文本并进一步转化为它能够进行数学比对的格式——向量Embedding。第一步加载与解析你需要根据文档类型选择合适的加载器Loader。比如用PyPDF2或pdfplumber处理PDF用python-docx处理Word用BeautifulSoup处理HTML。这一步的关键在于尽可能干净地提取出纯文本剔除页眉、页脚、页码、无关图片标注等噪音。一个常见的坑是扫描版PDF如果没做OCR光学字符识别提取出来的就是乱码或空白必须用pytesseract或商业OCR服务先处理。第二步文本分块Chunking这是决定检索精度的核心步骤之一。你不能把一整本100页的产品手册作为一个整体扔给模型那样检索时会失去焦点。也不能切得太碎导致语义不完整。分块策略需要权衡固定大小分块比如每块512个字符。简单粗暴但可能把一个完整的句子或段落从中间切断。基于分隔符分块按照段落\n\n、标题、句号等自然边界来切分。更符合语义但块的大小可能不均匀。语义分块使用更复杂的算法在保证句子完整性的前提下尽可能让每个块拥有独立的主题。这通常效果更好但计算更复杂。我的经验是对于一般文档可以结合后两者先按大标题如##分割成章节再在每个章节内按段落或固定大小进行二次分块。同时设置一定的重叠度Overlap比如相邻块之间重叠50-100个字符。这非常重要可以防止关键信息恰好落在两个块的边界上而被遗漏。例如一个问题的答案可能前半句在A块末尾后半句在B块开头没有重叠就会丢失一半信息。第三步向量化Embedding这是将文本转化为计算机和模型能理解的“数学语言”的过程。我们使用一个嵌入模型Embedding Model将每一块文本转换成一个高维向量比如768或1024维。这个向量就像是这段文本在高维空间中的一个“坐标点”语义相近的文本其向量在空间中的距离通常用余弦相似度衡量也会很近。模型选型是关键。对于中文场景你可能会在text2vec、BGEBAAI/bge-large-zh、M3E等开源模型中选择。对于英文OpenAI的text-embedding-ada-002或开源的all-MiniLM-L6-v2都是常见选择。选择时需要考虑模型维度影响存储和计算成本、语义表征能力尤其在你的专业领域、推理速度以及是否支持长文本。务必注意生成答案的大模型LLM和做向量化的嵌入模型Embedding Model通常是两个独立的模型别搞混了。2.2 检索在知识海洋中精准定位当用户提出一个问题Query时系统需要从成千上万的文本块中找到最相关的几个。这个过程就是检索。检索流程查询向量化使用和文档处理时同一个嵌入模型将用户的问题也转化为一个向量。相似度计算在向量数据库中计算查询向量与所有文档块向量之间的相似度如余弦相似度。返回Top-K结果按照相似度从高到低排序返回最相关的K个文档块。K值是个超参数通常取3-5既要提供足够上下文又要避免给LLM输入过多无关信息造成干扰或超长。检索技术进阶简单向量检索Dense Retrieval如上所述直接做相似度计算。优点是简单直接能捕捉语义相似性。混合检索Hybrid Search结合稠密检索向量相似度和稀疏检索如BM25一种传统的关键词匹配算法。有些问题字面匹配很重要如产品型号“ABC-123”BM25更擅长有些问题需要语义理解如“性价比高的方案”向量检索更优。两者结果按分数融合能显著提升召回率。重排序Re-ranking在初步检索出Top-NNK个结果后用一个更精细但更耗时的重排序模型如BGE-reranker对它们进行再次打分和排序选出最终的Top-K。这相当于用“精筛”来优化“粗选”的结果能有效提升精度尤其当K值较小时。元数据过滤如果你的文档块携带了元数据如所属部门、文档类型、创建日期可以在检索时加入过滤条件。例如当财务部员工提问时只检索标记为“财务制度”的文档块这能大幅提升准确性和安全性。2.3 增强与生成为LLM装配“上下文眼镜”检索到的文档块不会直接作为答案。它们是“原料”需要巧妙地“喂”给大语言模型引导它基于这些原料生成最终答案。这就是提示工程Prompt Engineering发挥核心作用的地方。一个典型且有效的提示词模板如下请基于以下提供的上下文信息来回答问题。如果上下文信息中包含答案请严格依据上下文进行总结和回答。如果上下文信息中不包含答案请直接回答“根据已知信息无法回答该问题”不要编造信息。 上下文信息 {context} 问题 {question} 请用中文回答这个模板做了几件关键事明确指令告诉LLM必须基于给定上下文抑制其内部知识的随意发挥。设定边界明确告知如果上下文没有答案该如何处理这是减少“幻觉”的关键约束。结构化输入清晰分隔“上下文”和“问题”帮助模型理解任务结构。这里的{context}就是上一步检索到的Top-K个文档块拼接而成的文本。拼接时通常会在每个块前加上来源标识如[来自《产品手册》第3章]这样在最终答案中LLM有可能连引用来源一起输出。然后我们将这个组装好的提示词发送给LLM如GPT-4、Claude或本地部署的Llama它就会扮演那个看了参考资料的“老学究”生成一个基于上下文的、连贯的答案。2.4 向量数据库RAG系统的记忆中枢在整个流程中向量数据库扮演了“记忆中枢”的角色。它专门为高效存储和检索向量数据而设计。为什么不用传统数据库传统关系型数据库如MySQL或键值数据库如Redis并非为高维向量的相似度搜索而优化。进行全表扫描计算余弦相似度的代价是O(N)当文档块达到百万、千万级别时速度会慢到无法接受。向量数据库如Pinecone、Weaviate、Qdrant、Milvus或云服务的向量检索能力底层使用近似最近邻ANN算法能在牺牲极小精度的情况下将检索复杂度降至亚线性实现毫秒级的海量向量检索。选型考量和操作流程选型考虑因素包括性能QPS、延迟、可扩展性、是否支持混合检索/过滤、成本开源自托管 vs 云服务、社区生态等。对于快速验证ChromaDB轻量易用对于生产级应用Milvus、Qdrant或云厂商的托管服务更可靠。写入流程文档处理完成后你会得到两个核心数据文本块列表和对应的向量列表。你需要将它们连同元数据如来源文件、分块索引、创建时间等作为一个整体写入向量数据库。通常每个条目包含id主键、vector嵌入向量、text原始文本、metadataJSON格式的元数据。检索流程如前所述将查询向量化后调用向量数据库的search接口传入查询向量、返回数量K以及可选的元数据过滤条件数据库会返回最相似的K个条目包括其文本和元数据。一个关键实践务必建立文本块与原始源文件的映射关系并将源文件路径或标识存储在元数据中。这样当用户对答案存疑或想深入查看时系统可以快速定位到原文出处这是构建可信AI系统的重要一环。3. 从零搭建一个可运行的RAG系统以智能文档问答为例理论讲得再多不如动手做一遍。下面我将带你使用LangChain一个流行的LLM应用框架和Chroma轻量级向量数据库搭建一个最简单的本地智能文档问答系统。我们会一步步走过数据准备、索引构建、查询检索和答案生成的完整流程。3.1 环境准备与工具选型首先我们需要一个Python环境建议3.8以上。核心库包括langchainlangchain-community: 提供LLM应用开发的各类组件和集成。chromadb: 轻量级向量数据库易于本地启动。sentence-transformers: 用于运行开源的嵌入模型。pypdf或pdfplumber: 用于解析PDF文档。openai(可选): 如果你打算使用OpenAI的API作为LLM或嵌入模型。你可以通过pip一键安装pip install langchain langchain-community chromadb sentence-transformers pypdf关于模型选型为了完全本地运行且免费我们做如下选择嵌入模型使用sentence-transformers库中的all-MiniLM-L6-v2模型。这是一个轻量级但效果不错的英文通用模型。对于中文可以替换为BAAI/bge-small-zh。大语言模型LLM使用Ollama本地运行的Llama 3.27B版本。Ollama让你能在自己的电脑上运行开源大模型。你需要先安装Ollama然后在终端运行ollama pull llama3.2来拉取模型。我们将通过langchain的ChatOllama来调用它。3.2 构建知识库索引假设我们有一个名为product_manual.pdf的产品手册现在要把它“喂”给系统。# 导入必要的库 from langchain_community.document_loaders import PyPDFLoader from langchain.text_splitter import RecursiveCharacterTextSplitter from langchain_community.embeddings import HuggingFaceEmbeddings from langchain_community.vectorstores import Chroma import os # 1. 加载文档 loader PyPDFLoader(path/to/your/product_manual.pdf) documents loader.load() print(f加载了 {len(documents)} 页文档) # 2. 文本分块 text_splitter RecursiveCharacterTextSplitter( chunk_size500, # 每个块大约500字符 chunk_overlap50, # 块之间重叠50字符防止信息割裂 length_functionlen, separators[\n\n, \n, 。, , , , , , ] # 按语义分割的优先级 ) chunks text_splitter.split_documents(documents) print(f将文档切分为 {len(chunks)} 个文本块) # 3. 初始化嵌入模型 # 使用本地嵌入模型首次运行会下载模型文件 embeddings HuggingFaceEmbeddings( model_namesentence-transformers/all-MiniLM-L6-v2, model_kwargs{device: cpu}, # 使用CPU有GPU可改为cuda encode_kwargs{normalize_embeddings: True} # 归一化方便计算余弦相似度 ) # 4. 创建并持久化向量数据库 # persist_directory 指定索引保存的本地目录 vectorstore Chroma.from_documents( documentschunks, embeddingembeddings, persist_directory./chroma_db # 索引将保存在这个文件夹 ) vectorstore.persist() # 将索引写入磁盘 print(向量数据库索引构建完成已保存至 ./chroma_db)这段代码完成了知识库的构建。RecursiveCharacterTextSplitter会尝试按你定义的separators顺序去分割文本尽量保证块的语义完整性。Chroma.from_documents方法内部做了三件事调用嵌入模型将每个文本块转化为向量将这些向量和原始文本、元数据一起存储到Chroma数据库中最后将数据库持久化到本地目录。以后重启程序可以直接加载这个目录无需重新处理文档。3.3 实现问答检索链索引建好了现在来实现问答功能。我们将创建一个检索链Retrieval Chain它封装了检索和生成两个步骤。from langchain.chains import RetrievalQA from langchain_community.llms import Ollama from langchain.prompts import PromptTemplate # 1. 加载已有的向量数据库 embeddings HuggingFaceEmbeddings(model_namesentence-transformers/all-MiniLM-L6-v2) vectorstore Chroma(persist_directory./chroma_db, embedding_functionembeddings) # 2. 将向量数据库转换为检索器Retriever # search_kwargs 可以控制返回结果的数量 retriever vectorstore.as_retriever(search_kwargs{k: 4}) # 3. 初始化本地LLM (通过Ollama) llm Ollama(modelllama3.2, temperature0.1) # temperature调低让输出更确定 # 4. 定义提示词模板 prompt_template 请严格根据以下上下文信息来回答问题。如果上下文中有答案请基于上下文进行回答并尽量引用原文措辞。如果上下文中没有明确答案请直接说“根据提供的资料我无法回答这个问题”不要编造信息。 上下文 {context} 问题 {question} 请用中文给出专业、清晰的回答 PROMPT PromptTemplate( templateprompt_template, input_variables[context, question] ) # 5. 创建检索问答链 qa_chain RetrievalQA.from_chain_type( llmllm, chain_typestuff, # “stuff”模式将检索到的所有文档拼接到提示词中 retrieverretriever, chain_type_kwargs{prompt: PROMPT}, return_source_documentsTrue # 非常重要返回检索到的源文档 ) # 6. 进行问答 query 这款产品的主要特性有哪些 result qa_chain.invoke({query: query}) print(问题, query) print(\n答案, result[result]) print(\n--- 参考来源 ---) for i, doc in enumerate(result[source_documents]): print(f[来源{i1}] {doc.metadata.get(source, 未知)} - 页码: {doc.metadata.get(page, N/A)}) print(f片段预览: {doc.page_content[:200]}...\n)这个链的工作流程是当你提出query时retriever会从向量库中找出最相关的4个文档块。RetrievalQA链会将这些文档块填入prompt_template中的{context}占位符将你的问题填入{question}形成完整的提示词发送给LLM。LLM生成的答案连同检索到的源文档方便你溯源一起返回。3.4 效果优化与进阶配置上面的基础版本可以运行但效果可能不尽如人意。以下是几个立竿见影的优化点1. 优化检索器# 使用“最大边际相关性”MMR进行检索兼顾相关性与多样性 retriever vectorstore.as_retriever( search_typemmr, # 使用MMR算法 search_kwargs{k: 6, fetch_k: 20, lambda_mult: 0.7} ) # fetch_k: 初步检索的数量lambda_mult: 多样性权重0偏向相关1偏向多样MMR算法在保证相关性的同时会尽量选择内容差异大的文档块避免给LLM输入大量重复信息。2. 使用更强大的LLM和嵌入模型LLM本地可以尝试更大的模型如Qwen2.5-14B或Llama 3.1 70B如果显存足够。或者直接使用API服务如OpenAI的gpt-4o-mini在效果和成本间取得平衡。只需将Ollama替换为ChatOpenAI。嵌入模型对于中文强烈推荐BAAI/bge-large-zh-v1.5它在中文语义表征上表现优异。只需在HuggingFaceEmbeddings中更改model_name即可。3. 实现对话历史多轮对话基础链是无状态的。要实现像ChatGPT那样的多轮对话需要引入记忆机制。from langchain.memory import ConversationBufferMemory from langchain.chains import ConversationalRetrievalChain memory ConversationBufferMemory(memory_keychat_history, return_messagesTrue, output_keyanswer) qa_chain ConversationalRetrievalChain.from_llm( llmllm, retrieverretriever, memorymemory, combine_docs_chain_kwargs{prompt: PROMPT} ) # 后续调用时链会自动管理对话历史 result qa_chain.invoke({question: query})这样你问“产品特性有哪些”再接着问“其中哪个特性最受市场欢迎”模型能在记住前文的基础上在上下文中寻找与“市场欢迎”相关的信息。4. 生产级RAG的挑战与优化实战当你把Demo推向真实的生产环境面对海量、多格式、动态更新的文档以及高并发的用户请求时会遇到一系列新的挑战。下面分享一些实战中积累的经验和优化策略。4.1 检索质量提升解决“找不到”和“找不准”问题一语义不匹配导致的“找不到”用户问“怎么报销差旅费”但知识库里只有标题为《员工费用管理办法》的文档。简单的向量相似度可能无法将它们关联起来。优化方案查询重写/扩展在检索前先用一个轻量级模型或规则对原始查询进行扩展。例如将“报销差旅费”扩展为“报销差旅费 员工费用 管理办法 出差 费用申请”。这增加了与知识库文档的语义重叠面。LangChain中的Query expansion工具可以帮你做这件事。问题二关键词与语义的权衡用户问“iPhone 15的电池容量”文档中明确写着“电池容量为3349mAh”。此时精确的关键词匹配BM25可能比语义向量检索更直接有效。优化方案混合检索Hybrid Search如前所述结合向量检索和关键词检索如BM25并将两者的分数进行加权融合。Chroma和Weaviate等数据库已原生支持。你可以赋予BM25更高的权重来应对这种精确术语查询。问题三多跳推理Multi-hop Reasoning用户问“负责A项目的团队经理是谁”。答案可能需要两步先从文档中找到“A项目由B部门负责”再从另一文档中找到“B部门的经理是张三”。优化方案迭代检索或图检索迭代检索先检索与“A项目”相关的文档从中提取出“B部门”这个实体。然后以“B部门 经理”作为新的查询进行第二次检索。图检索Graph RAG如果你能将知识库构建成图结构实体为节点关系为边那么这类多跳查询会非常高效。例如建立项目-属于-部门-经理是-人这样的关系链。Neo4j等图数据库可以与RAG结合实现这一点。4.2 生成质量优化让答案更精准、更可控问题一答案脱离上下文幻觉即使给了上下文LLM有时还是会“自由发挥”。优化方案强化提示词约束与后处理在提示词中强调使用更强烈的措辞如“你必须且只能使用以下上下文中的信息。上下文未提及的内容一律回答‘不知道’。”引用溯源Citation要求LLM在答案中标注引用如【1】。这不仅能提高可信度你还可以在后处理阶段检查这些引用是否真实存在于上下文中如果不存在则触发修正或警告。自我验证Self-Consistency让LLM先生成答案再基于同一个上下文问它“你刚才的答案在上下文中是否有直接支持请指出原文。”通过这种自我审视来发现矛盾。问题二上下文过长导致信息丢失或成本过高当检索到的文档块很多或很长时可能超出LLM的上下文窗口或者即使没超出LLM也可能无法有效处理如此多的信息。优化方案上下文压缩与选择性注入Map-Reduce将每个检索到的文档块先分别进行总结Map然后将所有总结再次汇总成最终答案Reduce。这适用于文档块很多的情况。Refine先基于第一个文档块生成一个初始答案然后依次将后续文档块和当前答案给LLM让它去修正和补充。这种方式生成的答案质量通常更高但速度慢。选择性上下文不是把所有检索到的文本都扔给LLM。可以先用一个更小的模型或规则对每个检索块进行相关性打分只选择分数最高的前2-3个块送入最终生成阶段。4.3 系统性能与工程化考量1. 索引更新策略知识库不是一成不变的。文档新增、修改、删除后索引如何更新全量重建最简单但成本高。适合更新不频繁的场景如每周一次。增量更新识别变更的文件只重新处理这些文件并更新向量库。这需要你记录每个文件及其分块的哈希值以判断是否变更。注意一个文件的微小修改可能导致其所有块的向量都变化需要删除旧块插入新块。实时更新对于流式数据如客服对话日志可以建立近实时如每分钟的索引管道。但要注意频繁的插入删除可能影响向量索引的性能和结构需要数据库支持动态索引。2. 缓存策略对于高频的、重复的问题如“公司官网是什么”每次都要走一遍检索和生成流程是浪费。答案缓存在应用层对完全相同的查询直接返回缓存的历史答案。设置合理的TTL生存时间。语义缓存更高级的做法是对语义相似的问题也返回缓存答案。例如“怎么请假”和“请假流程是什么”应该命中同一个缓存。这需要计算查询之间的语义相似度并设定一个阈值。3. 评估与监控没有度量就无法改进。你需要建立一套评估体系人工评估抽样一批问题从“相关性”、“准确性”、“完整性”、“流畅性”等维度打分。这是黄金标准但成本高。自动评估代理评估检索阶段计算“检索召回率”——人工标注的相关文档有多少被系统检索出来了。生成阶段使用G-Eval、BERTScore等基于LLM或模型的评估方法对比生成答案与标准答案的相似度。也可以让一个更强的LLM如GPT-4扮演裁判评价生成答案的质量。业务指标监控在线上系统监控用户反馈如点赞/点踩、问题拒答率、平均响应时间等这些是最终效果的体现。5. 避坑指南与常见问题排查在实际开发和运维中你会遇到各种各样奇怪的问题。这里记录了一些典型的“坑”和排查思路。5.1 检索相关为什么总是搜不到想要的症状用户的问题明明在知识库里有但系统返回的答案却是“无法回答”或胡编乱造。检查1分块策略是否合理问题答案可能被切分到了两个块的交界处。排查查看检索到的源文档内容。如果答案的关键部分确实在块的开头或结尾考虑增大chunk_overlap重叠度。问题块太大包含太多无关信息稀释了关键信息的向量表征。排查尝试减小chunk_size比如从1000降到500或300。对于问答型任务较小的块通常效果更好。检查2嵌入模型是否匹配问题使用了不适合领域或语言的嵌入模型。例如用纯英文模型处理中文法律文档。排查在HuggingFace上寻找在特定领域如医学、法律、代码或语言上经过微调的嵌入模型进行替换测试。检查3查询本身是否太模糊或太简短问题用户输入“它怎么样”这种指代不明的查询任何检索系统都无能为力。排查在前端引导用户输入更具体的问题或者在检索前对查询进行智能补全利用对话历史或用户画像。5.2 生成相关为什么答案质量差或胡言乱语症状检索到了正确的文档但LLM生成的答案还是不对或者包含了上下文没有的信息。检查1提示词Prompt是否足够清晰有力问题提示词约束力太弱如“请参考以下上下文”LLM可能还是会优先调用自己的内部知识。解决使用更强硬的指令如“你必须”、“禁止”、“只能”。在提示词中明确给出“好答案”和“坏答案”的示例Few-shot Learning引导LLM遵循格式。检查2上下文是否过于冗长或混乱问题检索到的多个文档块内容重复或矛盾干扰了LLM。解决在将上下文拼接给LLM前先做去重。或者使用MMR检索来保证结果多样性。也可以尝试Refine或Map-Reduce链式类型让LLM分步处理信息。检查3LLM本身的能力或温度Temperature设置问题使用的开源小模型如7B参数的指令遵循和推理能力有限。解决尝试换用更大参数或指令微调更好的模型。同时将temperature参数调低如0.1减少随机性让输出更确定、更贴近上下文。5.3 工程与部署相关为什么服务变慢或内存溢出症状本地测试正常一上线就响应慢甚至崩溃。检查1向量检索是否成为瓶颈问题文档数量达到百万级后即使使用ANN算法单次检索也可能需要几百毫秒。解决索引优化确保向量数据库使用了合适的索引类型如HNSW、IVF。调整索引参数如ef_construction,M在构建速度和检索精度间权衡。硬件加速使用支持GPU加速的向量数据库如Milvus或嵌入模型推理。缓存实施查询/答案缓存。检查2LLM调用是否超时或限流问题使用云API时网络延迟或对方限流导致响应慢。解决实现请求队列、重试机制和退避策略。对于关键应用考虑部署备份的本地模型。检查3内存泄漏问题长时间运行后内存占用持续增长。排查检查代码中是否有全局变量不断累积如缓存未设上限、日志未清理。使用内存分析工具如memory_profiler定位问题。确保向量数据库客户端和LLM客户端被正确关闭和复用。最后RAG不是一个“设置好就一劳永逸”的系统。它更像一个需要持续调优的引擎。从分块大小、重叠度、嵌入模型、检索算法、提示词模板到LLM本身每一个环节都有大量的参数和选择可以调整。最好的方法是从一个简单可用的版本开始构建一个评估数据集然后像做实验一样每次只改变一个变量A/B测试客观地评估效果变化逐步迭代最终打磨出适合你特定场景和需求的高性能RAG系统。这个过程本身就是对大模型应用深入理解的最佳路径。