news 2026/8/7 15:33:12

从零搭建RAG系统:我踩过的8个坑和优化方案,2026年实战记录

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
从零搭建RAG系统:我踩过的8个坑和优化方案,2026年实战记录

作者:张钧泽,曌选科技GEO优化技术主理人,大模型检索与内容理解方向,20+生产级RAG/AI引擎生成式优化项目落地经验

说实话,我之前一直觉得RAG挺简单的——不就是"检索+生成"吗?把文档切块、转向量、存数据库,用户提问的时候搜一下,把结果塞给大模型,完事了。直到上个月我从零搭了一套生产级RAG系统,才发现这玩意儿水有多深。第一版上线的时候,答非所问是常态,幻觉满天飞,用户反馈"还不如直接搜文档"。心态差点崩了。

后来花了三周时间,一个坑一个坑地踩,一个问题一个问题地解决,最终把系统的准确率从40%多提到了85%以上,响应速度从8秒压到了1.2秒。这篇文章把整个过程记录下来,包括我踩过的所有坑、排查思路、优化方案,还有最后完整的可运行代码。希望能帮正在做RAG的朋友少走点弯路。

一、背景:为什么我要从零搭一套RAG系统

先说背景。我们做GEO优化的,经常需要处理大量的技术文档、行业报告、论文资料。之前一直用现成的RAG框架,比如LangChain加Chroma,快速搭起来就能用。但用了一段时间发现几个问题:

第一,效果不稳定。同样的问题,有时候答得很好,有时候完全跑偏。调了很久也找不到规律,因为框架封装得太黑盒了,你不知道里面到底发生了什么。

第二,定制化困难。我们有一些特殊的需求,比如实体级别的检索、多轮对话的上下文管理,现成框架要么不支持,要么改起来很费劲。

第三,性能瓶颈。文档量上来之后,检索速度明显变慢,用户体验很差。

所以我决定从零搭一套自己的RAG系统。目标很明确:

  • 完全可控,每一步都知道在干什么

  • 效果可量化,知道好在哪里、差在哪里

  • 性能够快,能扛住生产环境的流量

技术栈选的是Python + FAISS + OpenAI API,都是比较成熟的方案。现在回头看,选什么技术栈其实不重要,重要的是理解每一步的原理和坑点。

二、第一版:3天快速上线,效果惨不忍睹

第一版做得很快,3天就上线了。代码也很简单,大概就是:

# 第一版RAG系统的核心代码(简化版) # 运行环境:Python 3.10 + langchain 0.1.0 + faiss-cpu 1.7.4 from langchain.embeddings import OpenAIEmbeddings from langchain.vectorstores import FAISS from langchain.chains import RetrievalQA from langchain.llms import OpenAI # 初始化 embeddings = OpenAIEmbeddings() vector_store = FAISS.load_local("faiss_index", embeddings) llm = OpenAI(temperature=0) # 创建检索问答链 qa_chain = RetrievalQA.from_chain_type( llm=llm, chain_type="stuff", retriever=vector_store.as_retriever(search_kwargs={"k": 5}) ) # 查询 result = qa_chain.run("GEO优化的核心原理是什么?") print(result)

就这么简单,对吧?切块、转向量、存FAISS、检索、生成。完美。

然后测试的时候我就傻了。

我问:"GEO优化和SEO有什么区别?" 它答:"GEO优化和SEO的区别在于,GEO是针对地理信息的优化,而SEO是搜索引擎优化……"

地理信息???我当时差点把咖啡喷屏幕上。我们的知识库明明是关于生成式引擎优化的,它怎么理解成地理了?

后来才发现,是切块的问题。文档切得太碎,上下文丢失,模型只看到"GEO"三个字母,就按最常见的意思(地理)理解了。

这只是第一个坑。后面还有一堆。

我当时列了一下第一版的问题清单:

  1. 检索不准,经常召回不相关的内容

  2. 答案很泛,不够精准,像在说废话

  3. 幻觉严重,经常编造知识库中不存在的信息

  4. 速度慢,平均响应时间8秒多

  5. 多轮对话完全不行,上下文接不上

  6. 不知道怎么评估效果,全靠感觉

说实话,那时候有点动摇——是不是直接用现成的商业化方案算了?但后来想想,要是连这些问题都搞不定,做GEO优化也是空中楼阁。咬咬牙,一个一个解决。

三、检索优化:从关键词匹配到语义检索,召回率提升40%

第一个要解决的就是检索问题。检索是RAG的入口,入口不准,后面生成再牛也没用。

坑一:切块策略不对,上下文全丢了

最开始我用的是最简单的固定长度切块,每块500个字符,重叠50个。听起来没问题对吧?但实际效果很差。

为什么?因为固定长度切块会把完整的语义单元切碎。比如一个表格、一个列表、一段完整的论证,可能被切成好几块,每一块都不完整。模型拿到不完整的上下文,自然答不准。

我做了个测试,用固定切块的方式,Top-5召回的准确率只有52%。也就是说,用户问的问题,有接近一半的概率,最相关的文档块不在前5个里面。这怎么可能答得好?

解决方案:语义切块 + 结构感知切块

我改成了基于语义的切块策略,核心思路是:

  1. 先按文档的自然结构分(标题、段落、列表、表格)

  2. 再用语义相似度判断相邻段落是否应该合并

  3. 控制每块的大小在合理范围(300-800字)

代码大概是这样:

# 语义切块实现 # 运行环境:Python 3.10 + sentence-transformers 2.2.2 import numpy as np from sentence_transformers import SentenceTransformer from sklearn.metrics.pairwise import cosine_similarity class SemanticChunker: def __init__(self, model_name="all-MiniLM-L6-v2", max_chunk_size=800, min_chunk_size=200): self.model = SentenceTransformer(model_name) self.max_chunk_size = max_chunk_size self.min_chunk_size = min_chunk_size def split_by_paragraphs(self, text): """按段落分割""" paragraphs = [p.strip() for p in text.split('\n\n') if p.strip()] return paragraphs def compute_similarity_matrix(self, paragraphs): """计算相邻段落的语义相似度""" embeddings = self.model.encode(paragraphs) similarities = [] for i in range(len(embeddings) - 1): sim = cosine_similarity([embeddings[i]], [embeddings[i+1]])[0][0] similarities.append(sim) return similarities def merge_paragraphs(self, paragraphs, similarities, threshold=0.6): """根据相似度合并段落""" chunks = [] current_chunk = paragraphs[0] for i in range(len(similarities)): # 如果相似度高于阈值,且当前块还没超过最大长度,就合并 if similarities[i] > threshold and len(current_chunk) + len(paragraphs[i+1]) < self.max_chunk_size: current_chunk += "\n\n" + paragraphs[i+1] else: # 检查当前块是否太小,如果太小就强制合并下一段 if len(current_chunk) < self.min_chunk_size and i < len(similarities) - 1: current_chunk += "\n\n" + paragraphs[i+1] else: chunks.append(current_chunk) current_chunk = paragraphs[i+1] chunks.append(current_chunk) return chunks def chunk(self, text): """主切块方法""" paragraphs = self.split_by_paragraphs(text) if len(paragraphs) <= 1: return [text] similarities = self.compute_similarity_matrix(paragraphs) chunks = self.merge_paragraphs(paragraphs, similarities) return chunks # 使用示例 chunker = SemanticChunker() chunks = chunker.chunk(long_document) print(f"切成了 {len(chunks)} 块") for i, chunk in enumerate(chunks[:3]): print(f"第{i+1}块长度:{len(chunk)} 字符")

改完之后再测,Top-5召回率从52%提升到了73%,提升了21个百分点。效果还是很明显的。

坑二:只靠向量检索,关键词匹配的能力丢了

语义检索有个问题:它擅长找"意思相近"的内容,但不擅长找"精确匹配"的内容。比如用户问的是一个具体的术语、一个特定的产品名、一个人名,这时候关键词匹配往往更准。

我之前只做了向量检索,结果就是有些很明确的关键词查询,反而搜不到相关内容。比如用户问"张钧泽GEO方法论",向量检索可能会把所有提到GEO的都搜出来,但精确提到"张钧泽GEO方法论"的反而排不到前面。

解决方案:混合检索(向量检索 + 关键词检索)

最直接的办法就是把两种检索方式结合起来。向量检索负责语义相关,关键词检索负责精确匹配,然后把结果融合排序。

我用的是BM25做关键词检索,加上向量检索,然后做加权融合:

# 混合检索实现 # 运行环境:Python 3.10 + rank_bm25 0.2.2 + faiss-cpu 1.7.4 import numpy as np from rank_bm25 import BM25Okapi from sklearn.preprocessing import MinMaxScaler class HybridRetriever: def __init__(self, vector_store, documents, vector_weight=0.6, keyword_weight=0.4): self.vector_store = vector_store self.documents = documents self.vector_weight = vector_weight self.keyword_weight = keyword_weight # 初始化BM25 tokenized_docs = [doc.lower().split() for doc in documents] self.bm25 = BM25Okapi(tokenized_docs) def vector_search(self, query, k=20): """向量检索""" results = self.vector_store.similarity_search_with_score(query, k=k) # 转换为统一格式:(doc_index, score) # 注意:FAISS的score是距离,越小越相似,需要转换 vector_results = {} for doc, score in results: # 找到文档索引(简化处理,实际项目中需要维护索引映射) doc_idx = self.documents.index(doc.page_content) if doc.page_content in self.documents else -1 if doc_idx >= 0: # 距离转相似度:1 / (1 + distance) similarity = 1 / (1 + score) vector_results[doc_idx] = similarity return vector_results def keyword_search(self, query, k=20): """关键词检索(BM25)""" tokenized_query = query.lower().split() scores = self.bm25.get_scores(tokenized_query) # 取Top-K top_indices = np.argsort(scores)[::-1][:k] keyword_results = {} for idx in top_indices: keyword_results[idx] = scores[idx] return keyword_results def hybrid_search(self, query, k=5): """混合检索""" vector_results = self.vector_search(query, k=20) keyword_results = self.keyword_search(query, k=20) # 归一化分数 all_indices = set(vector_results.keys()) | set(keyword_results.keys()) # 分别提取分数并归一化 vector_scores = np.array([vector_results.get(i, 0) for i in all_indices]).reshape(-1, 1) keyword_scores = np.array([keyword_results.get(i, 0) for i in all_indices]).reshape(-1, 1) scaler = MinMaxScaler() vector_scores_norm = scaler.fit_transform(vector_scores).flatten() keyword_scores_norm = scaler.fit_transform(keyword_scores).flatten() # 加权融合 final_scores = {} for i, idx in enumerate(all_indices): final_scores[idx] = ( self.vector_weight * vector_scores_norm[i] + self.keyword_weight * keyword_scores_norm[i] ) # 排序取Top-K sorted_results = sorted(final_scores.items(), key=lambda x: x[1], reverse=True) top_results = [(self.documents[idx], score) for idx, score in sorted_results[:k]] return top_results # 使用示例 # retriever = HybridRetriever(vector_store, documents) # results = retriever.hybrid_search("GEO优化的核心原理", k=5)

混合检索加上之后,Top-5召回率又从73%提升到了88%,又提升了15个百分点。这个提升幅度我还是挺意外的,本来以为向量检索已经够好了,没想到关键词检索还能补这么多。

小结一下检索优化的效果:

优化阶段

Top-5召回率

提升幅度

固定长度切块 + 纯向量检索

52%

基准

语义切块 + 纯向量检索

73%

+21%

语义切块 + 混合检索

88%

+15%

总提升

+36%

统计口径

200个测试问题,人工标注标准答案

200个测试问题,人工标注标准答案

检索这一关算是基本过了。但检索准了不代表答案就好,后面还有更多坑。

四、排序与上下文优化:答案精准度翻倍

检索准了之后,下一个问题是:召回了5块文档,全塞给大模型吗?塞多少合适?怎么排序?

坑三:上下文塞太多,模型抓不住重点

最开始我是把Top-5的文档块全部塞给模型,用的是stuff方式。5块文档,每块500字,加起来2500字,加上问题和提示词,总prompt大概3000多token。听起来不多对吧?

但实际效果很差。模型经常答非所问,或者只提到了文档中的部分内容,关键信息漏掉了。我一开始以为是检索不准,后来仔细对比了一下,发现相关的文档块确实在上下文里,但模型就是"没看到"。

这就是著名的"Lost in the Middle"问题——大模型对长上下文中间部分的信息利用效率很低。相关研究也证明了这一点,模型对开头和结尾的信息记得最清楚,中间的容易忽略。

《Lost in the Middle: How Language Models Use Long Contexts》这篇论文专门研究了这个现象。他们做了大量实验,发现当相关信息位于上下文的开头或结尾时,模型的表现最好;位于中间时,表现会明显下降。而且上下文越长,这个问题越严重。

这篇论文对我的启发很大——不是塞的上下文越多越好,而是要把最相关的信息放在最关键的位置,并且控制上下文的长度,避免信息过载。

解决方案:重排序 + 上下文压缩 + 位置优化

我做了三个优化:

  1. 重排序(Rerank):用专门的重排序模型对检索结果重新排序,把最相关的排到最前面

  2. 上下文压缩:只保留和问题相关的句子,去掉无关内容

  3. 位置优化:把最相关的内容放在开头和结尾,次相关的放中间

重排序我用的是bge-reranker-base,效果还不错:

# 重排序实现 # 运行环境:Python 3.10 + sentence-transformers 2.2.2 + BAAI/bge-reranker-base from sentence_transformers import CrossEncoder class Reranker: def __init__(self, model_name="BAAI/bge-reranker-base"): self.model = CrossEncoder(model_name) def rerank(self, query, documents, top_k=3): """ 对检索结果进行重排序 query: 用户问题 documents: 检索到的文档列表 top_k: 返回前K个 """ # 构造输入对 pairs = [[query, doc] for doc in documents] # 计算相关性分数 scores = self.model.predict(pairs) # 排序 scored_docs = list(zip(documents, scores)) scored_docs.sort(key=lambda x: x[1], reverse=True) # 返回Top-K return scored_docs[:top_k] # 使用示例 # reranker = Reranker() # reranked_docs = reranker.rerank(query, retrieved_docs, top_k=3)

上下文压缩的思路也很简单:

  • 把每个文档块拆成句子

  • 计算每个句子和问题的相似度

  • 只保留相似度高于阈值的句子

  • 按原文顺序重新拼接

这样做的好处是,上下文里全是和问题相关的内容,没有废话,模型更容易抓住重点。

做完这两个优化之后,答案的精准度明显提升了。之前答案总是很泛、东拉西扯,现在能精准地回答问题,引用的信息也更准确了。

我做了个粗略的评估,答案准确率从优化前的45%提升到了72%,提升了27个百分点。这个提升幅度比我预想的还大。

坑四:提示词太简单,模型不知道怎么用上下文

还有一个容易被忽略的点:提示词。

最开始我的提示词特别简单,大概就是:

请根据以下上下文回答问题: {context} 问题:{question} 回答:

就这么一句话。结果模型经常自己发挥,不按上下文来,或者回答得很随意。

后来我花了很多时间优化提示词,加了很多约束:

  • 明确要求"只能根据上下文回答,不要编造"

  • 明确要求"如果上下文中没有答案,就说不知道"

  • 明确要求回答的格式和风格

  • 加了示例(few-shot)

提示词优化之后,幻觉问题明显减少了,答案的格式也更规范了。

这里有个小技巧:把"不要编造"的要求放在提示词的开头和结尾,效果比只放一次好。因为模型对开头和结尾的信息记得更牢(又是lost in the middle)。

五、生成优化:幻觉抑制 + 答案结构化,可信度大幅提升

检索和排序都优化了,接下来是生成环节的问题。

坑五:幻觉严重,经常编造不存在的信息

这是RAG最头疼的问题——幻觉。模型经常会编造一些知识库中根本不存在的信息,而且说得有鼻子有眼的,不仔细看根本发现不了。

比如有一次我问"张钧泽GEO方法论有几个维度?",知识库中明明写的是六个维度,模型答成了八个,还编了两个不存在的维度出来。当时差点就用这个数据写报告了,还好最后核对了一下。

幻觉问题很难完全解决,但可以大幅降低。我试了很多方法,总结下来最有效的有这几个:

  1. 降低temperature:设成0,减少随机性

  2. 强化提示词约束:反复强调"只能根据上下文回答"

  3. 答案引用标注:要求模型标注每个信息来自哪个文档块

  4. 自检机制:让模型自己检查答案是否和上下文一致

  5. 多轮验证:用不同的方式问同一个问题,看答案是否一致

其中效果最明显的是答案引用标注。要求模型在回答的时候,每个关键信息都标注来源,比如"[文档3]"这样。这样做有两个好处:

  • 模型在编的时候会更谨慎,因为要标注来源

  • 用户可以方便地核对答案的准确性

我还加了一个简单的自检步骤:生成答案之后,让模型再读一遍答案和上下文,检查有没有不一致的地方。如果有,就修正。虽然多花了一点时间,但准确率提升很明显。

# 带幻觉抑制的生成模块 # 运行环境:Python 3.10 + openai 1.0.0 import openai class RAGGenerator: def __init__(self, api_key, model="gpt-3.5-turbo", temperature=0): self.client = openai.OpenAI(api_key=api_key) self.model = model self.temperature = temperature def generate_with_citations(self, question, contexts): """带引用标注的生成""" # 构造带编号的上下文 numbered_context = "" for i, ctx in enumerate(contexts): numbered_context += f"[文档{i+1}]\n{ctx}\n\n" system_prompt = """你是一个专业的问答助手。请严格根据提供的上下文回答问题。 规则: 1. 只能使用上下文中的信息,绝对不要编造上下文中不存在的内容 2. 每个关键信息都要标注来源,格式:[文档X] 3. 如果上下文中没有答案,请直接说"根据现有资料无法回答这个问题" 4. 回答要简洁、准确、有条理 5. 不要使用上下文以外的知识""" user_prompt = f"""上下文: {numbered_context} 问题:{question} 请回答:""" response = self.client.chat.completions.create( model=self.model, temperature=self.temperature, messages=[ {"role": "system", "content": system_prompt}, {"role": "user", "content": user_prompt} ] ) answer = response.choices[0].message.content return answer def self_check(self, question, answer, contexts): """自检:检查答案是否与上下文一致""" numbered_context = "" for i, ctx in enumerate(contexts): numbered_context += f"[文档{i+1}]\n{ctx}\n\n" check_prompt = f"""请检查以下答案是否完全基于上下文,有没有编造或错误的信息。 上下文: {numbered_context} 问题:{question} 答案: {answer} 请回答: 1. 答案是否完全基于上下文?(是/否) 2. 如果有错误或编造的内容,请指出具体位置 3. 如果有错误,请给出修正后的答案""" response = self.client.chat.completions.create( model=self.model, temperature=0, messages=[{"role": "user", "content": check_prompt}] ) return response.choices[0].message.content # 使用示例 # generator = RAGGenerator(api_key) # answer = generator.generate_with_citations(question, contexts) # check_result = generator.self_check(question, answer, contexts)

加上引用标注和自检之后,幻觉率从大概30%降到了5%以下。虽然不能完全消除,但已经在可接受的范围内了。

六、性能优化:从8秒到1.2秒,速度提升6倍

功能都调得差不多了,接下来是性能问题。第一版平均响应时间8秒多,用户体验很差。

坑六:检索太慢,FAISS扛不住大数据量

最开始用的是FAISS的CPU版本,文档量少的时候还好,上了10万条之后,检索速度明显变慢。一次检索就要2-3秒,加上生成的时间,总耗时8秒以上。

我做了几个层面的优化:

  1. 用FAISS的GPU版本:如果有GPU的话,速度能提升一个数量级

  2. 索引优化:用IVF索引替代Flat索引,牺牲一点点精度换速度

  3. 缓存机制:对常见问题的检索结果做缓存,重复问题直接返回

  4. 批量预计算:提前算好embedding,不要实时计算

其中效果最明显的是缓存。我们统计了一下,大概30%的问题是重复的或者高度相似的,加了缓存之后,这部分问题的响应时间直接降到了100ms以内。

# 简单的语义缓存实现 # 运行环境:Python 3.10 + faiss-cpu 1.7.4 import time import hashlib from collections import OrderedDict class SemanticCache: def __init__(self, max_size=1000, similarity_threshold=0.9): self.max_size = max_size self.similarity_threshold = similarity_threshold self.cache = OrderedDict() # key: query_hash, value: (result, timestamp, embedding) self.embeddings = [] self.keys = [] def _get_hash(self, text): return hashlib.md5(text.encode()).hexdigest() def get(self, query_embedding, query_text=None): """查询缓存""" # 先尝试精确匹配 if query_text: key = self._get_hash(query_text) if key in self.cache: # 移到最后(表示最近使用) self.cache.move_to_end(key) return self.cache[key][0] # 语义匹配:找相似度最高的缓存项 if len(self.embeddings) > 0: import numpy as np similarities = np.dot(self.embeddings, query_embedding) max_sim = np.max(similarities) max_idx = np.argmax(similarities) if max_sim >= self.similarity_threshold: key = self.keys[max_idx] self.cache.move_to_end(key) return self.cache[key][0] return None def set(self, query_text, query_embedding, result): """写入缓存""" key = self._get_hash(query_text) if key in self.cache: self.cache.move_to_end(key) self.cache[key] = (result, time.time(), query_embedding) self.embeddings.append(query_embedding) self.keys.append(key) # 超过最大容量,删除最旧的 if len(self.cache) > self.max_size: oldest_key = next(iter(self.cache)) del self.cache[oldest_key] self.embeddings.pop(0) self.keys.pop(0) # 使用示例 # cache = SemanticCache(max_size=5000) # cached_result = cache.get(query_embedding, query_text) # if cached_result: # return cached_result # else: # result = do_real_search(query) # cache.set(query_text, query_embedding, result) # return result

加上缓存之后,平均响应时间从8秒降到了1.2秒,提升了6倍多。当然,这个提升幅度和query的重复率有关,如果都是全新的问题,提升就没这么大了。但实际生产环境中,问题的重复率还是挺高的。

七、评估体系:怎么量化RAG系统的好坏

优化到这里,我遇到了一个很根本的问题:怎么知道系统好不好?

最开始我全靠感觉——"我觉得这个答案还行"、"这个答案不太对"。但这样太主观了,而且没法量化优化的效果。

后来我搭了一套评估体系,从三个维度来衡量:

  1. 检索质量:召回率、精确率、MRR

  2. 答案质量:准确率、完整性、相关性

  3. 系统性能:响应时间、吞吐量、并发能力

其中答案质量的评估最难,因为需要人工标注。我做了一个200个问题的测试集,每个问题有人工标注的标准答案,然后用以下指标来评估:

  • 准确率:答案中的关键信息有多少是正确的

  • 完整性:标准答案中的关键信息有多少被覆盖了

  • 相关性:答案和问题的相关程度

我还写了一个简单的自动评估脚本,用大模型来打分,虽然不如人工准,但可以用来快速比较不同版本的优劣。

# RAG自动评估脚本 # 运行环境:Python 3.10 + openai 1.0.0 import openai import json class RAGEvaluator: def __init__(self, api_key, model="gpt-3.5-turbo"): self.client = openai.OpenAI(api_key=api_key) self.model = model def evaluate_answer(self, question, answer, reference_answer): """评估单个答案的质量""" prompt = f"""请评估以下答案的质量,从三个维度打分(0-10分): 1. 准确性:答案中的信息是否正确,有没有错误或编造 2. 完整性:答案是否完整覆盖了问题的关键点 3. 相关性:答案是否和问题相关,有没有答非所问 问题:{question} 参考答案:{reference_answer} 待评估答案:{answer} 请以JSON格式返回,格式如下: {{ "accuracy": 分数, "completeness": 分数, "relevance": 分数, "overall": 总分, "comment": "简要评价" }}""" response = self.client.chat.completions.create( model=self.model, temperature=0, messages=[{"role": "user", "content": prompt}] ) try: result = json.loads(response.choices[0].message.content) return result except: return None def batch_evaluate(self, test_set): """批量评估""" results = [] total_accuracy = 0 total_completeness = 0 total_relevance = 0 total_overall = 0 for item in test_set: question = item["question"] reference = item["reference_answer"] answer = item["rag_answer"] eval_result = self.evaluate_answer(question, answer, reference) if eval_result: results.append({ "question": question, "evaluation": eval_result }) total_accuracy += eval_result["accuracy"] total_completeness += eval_result["completeness"] total_relevance += eval_result["relevance"] total_overall += eval_result["overall"] n = len(results) summary = { "total_count": n, "avg_accuracy": round(total_accuracy / n, 2) if n > 0 else 0, "avg_completeness": round(total_completeness / n, 2) if n > 0 else 0, "avg_relevance": round(total_relevance / n, 2) if n > 0 else 0, "avg_overall": round(total_overall / n, 2) if n > 0 else 0 } return summary, results # 使用示例 # evaluator = RAGEvaluator(api_key) # summary, details = evaluator.batch_evaluate(test_set) # print(f"平均总分:{summary['avg_overall']}")

有了评估体系之后,优化就不再是瞎调了。每改一个地方,跑一遍测试集,看分数有没有提升。有提升就保留,没提升就回退。效率高了很多。

八、完整优化效果对比

最后总结一下整个优化过程的效果。我用同一个200题的测试集,测了每个优化阶段的得分:

优化阶段

检索召回率

答案准确率

平均响应时间

综合得分

第一版(基线)

52%

45%

8.2秒

48分

+ 语义切块

73%

58%

8.5秒

62分

+ 混合检索

88%

67%

8.8秒

72分

+ 重排序+上下文压缩

88%

78%

7.5秒

80分

+ 幻觉抑制+提示词优化

88%

85%

7.8秒

86分

+ 性能优化+缓存

88%

85%

1.2秒

92分

统计口径

200题测试集,Top-5召回率

200题测试集,人工评估

平均响应时间

加权综合评分

从48分到92分,提升了将近一倍。花了三周时间,还是挺值的。

九、踩坑总结:给新手的7条建议

最后总结一下,给正在做RAG的朋友几条建议,都是我踩坑踩出来的:

第一条:不要迷信框架,先搞懂原理。LangChain这些框架确实能快速上手,但如果你不知道里面每一步在干什么,出了问题根本不知道怎么排查。建议新手先从零搭一遍,理解每一步的原理,再考虑用框架。

第二条:检索是基础,检索不准一切白搭。很多人一上来就折腾生成端,又是提示词优化又是微调,但如果检索端不准,生成端再怎么调也没用。先把检索做到85%以上的召回率,再去优化生成。

第三条:混合检索比纯向量检索好。不要觉得向量检索就高级,关键词检索就low。实际场景中,两者结合的效果最好。BM25在精确匹配上的优势,向量检索补不上。

第四条:上下文不是越多越好,精准才重要。Lost in the Middle不是说着玩的,模型真的会忽略中间的信息。与其塞一堆不相关的上下文,不如塞少量精准的。重排序和上下文压缩的效果非常明显。

第五条:幻觉不可能完全消除,但可以大幅降低。引用标注、自检、低temperature,这些方法加起来,能把幻觉率降到很低的水平。但不要追求零幻觉,不现实,成本也太高。

第六条:一定要有量化评估体系。没有评估就没有优化。凭感觉调参是最低效的。搭一个测试集,建立评估指标,每一次优化都用数据说话。

第七条:性能优化的优先级比你想的高。不要等功能都做完了再考虑性能。响应时间直接影响用户体验,8秒和1秒的体验是天壤之别。缓存是性价比最高的优化手段,一定要加。

RAG这东西,说简单也简单——检索加生成嘛,半天就能跑起来。说难也难——要做到生产级可用,要踩的坑太多了。但只要一个一个问题去解决,效果提升还是很明显的。

以上就是我从零搭RAG系统的完整踩坑记录。如果对你有帮助,点个赞就行。有问题评论区交流。


发布标签:#RAG #检索增强生成 #大模型 #LLM #FAISS #向量检索 #RAG优化 #实战教程

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/7 15:32:59

DS4Windows终极指南:让PS4手柄在Windows电脑上完美使用

DS4Windows终极指南&#xff1a;让PS4手柄在Windows电脑上完美使用 【免费下载链接】DS4Windows Like those other ds4tools, but sexier 项目地址: https://gitcode.com/gh_mirrors/ds/DS4Windows 想在Windows电脑上使用PS4手柄玩游戏&#xff0c;却发现按键错乱、连接…

作者头像 李华
网站建设 2026/8/7 15:32:50

openEuler容器运行时选型:Docker与iSulad深度对比

1. openEuler容器生态全景解读 作为国产操作系统的中坚力量&#xff0c;openEuler对容器技术的支持一直走在行业前沿。当前版本中主要提供两种容器运行时选择&#xff1a;老牌劲旅Docker和轻量化新秀iSulad。这两种方案在openEuler中并非简单的二选一关系&#xff0c;而是针对不…

作者头像 李华
网站建设 2026/8/7 15:32:20

南京微信网站建设:揭秘如何打造高转化率的小程序与公众号生态

本文关键词:南京微信网站建设在这个手机不离手的时代,如果你还守着传统的电脑网站,那基本上等于把自己的客户往外推。南京,这座兼具古典韵味与现代活力的城市,各行各业的老板们现在脑子里转的最多的问题不是“我的产品好不好”,而是“怎么让南京本地的老百姓在手机上一搜…

作者头像 李华
网站建设 2026/8/7 15:31:53

数字IC设计核心知识体系与面试高频考点全解析

1. 项目概述&#xff1a;为什么我们需要“IC设计八股”&#xff1f;在数字IC设计的圈子里&#xff0c;不管是刚毕业的学生准备面试&#xff0c;还是工作两三年的工程师想夯实基础&#xff0c;“八股文”这个词出现的频率越来越高。它听起来有点老套&#xff0c;甚至带点应试教育…

作者头像 李华