准确率从60%到94%:构建RAG系统的核心策略(2026最新版) - 今日头条 摘要
文档概览
title: "准确率从60%到94%:构建RAG系统的核心策略(2026最新版) - 今日头条" source_url: "https://www.toutiao.com/article/7627674278067601935/?wid=1782275799998" source_site: "www.toutiao.com" clipped_at...
关键事实
- from pydantic_ai import Agentagent = Agent('openai:gpt-4o', system_prompt='你是具有多个检索工具的RAG助手。为每个查询选择合适的工具。')@agent.toolasyncdefsearch_knowledge_base(query: str, limit: int = 5) -> str:"""文档分块的语义搜索""" query_embedding = await embedder.embed_query(query) results = await db.match_chunks(query_embedding, limit)return format_results(results)@agent.toolasyncdefretrieve_full_document(document_title: str) -> str:"""当分块缺乏上下文时检索完整文档""" result = await db.query("SELECT title, content FROM documents WHERE title ILIKE %s",f"%{document_title}%" )returnf"{result['title']}\n\n{result['content']}"@agent.toolasyncdefsql_query(question: str) -> str:"""查询结构化数据库获取特定数据"""# 智能体可以为结构化数据编写SQL查询# (在生产环境中,使用具有安全检查的适当SQL生成)return execute_safe_sql(question)
-
初始化Graphiti(连接到Neo4j)graphiti = Graphiti("neo4j://localhost:7687", "neo4j", "password")asyncdefingest_document(text: str, source: str):"""摄取到知识图谱"""# Graphiti自动提取实体和关系await graphiti.add_episode( name=source, episode_body=text, source=EpisodeType.text, source_description=f"文档:{source}" )asyncdefsearch_knowledge_graph(query: str) -> str:"""混合搜索:语义 + 关键词 + 图"""# Graphiti结合:# - 语义相似性(嵌入)# - BM25关键词搜索# - 图结构遍历# - 时间上下文 results = await graphiti.search(query=query, num_results=5)# 格式化图结果 formatted = for result in results: formatted.append(f"实体:{result.node.name}\n"f"类型:{result.node.type}\n"f"关系:{result.relationships}" )return"\n---\n".join(formatted)
- defingest_hierarchical(document: str, title: str):"""创建父-子结构"""# 父级:大部分(2000字符) parent_chunks = [document[i:i+2000] for i inrange(0, len(document), 2000)]for parent_id, parent inenumerate(parent_chunks):# 存储父级 metadata = {"heading": f"{title} - 部分 {parent_id}"} db.execute("INSERT INTO parent_chunks (id, content, metadata) VALUES (%s, %s, %s)", (parent_id, parent, json.dumps(metadata)) )# 子级:小子分块(500字符) child_chunks = [parent[j:j+500] for j inrange(0, len(parent), 500)]for child in child_chunks: embedding = get_embedding(child) db.execute("INSERT INTO child_chunks (content, embedding, parent_id) VALUES (%s, %s, %s)", (child, embedding, parent_id) )
- queries = [query] + response.choices[0].message.content.strip.split('\n')# 并行执行所有搜索 search_tasks = for q in queries: query_embedding = await embedder.embed_query(q) task = db.fetch("SELECT * FROM match_chunks($1::vector, $2)", query_embedding, limit ) search_tasks.append(task) results_lists = await asyncio.gather(*search_tasks)# 按分块ID去重,保留最高相似度 seen = {}for results in results_lists:for row in results: chunk_id = row['chunk_id']if chunk_id notin seen or row['similarity'] > seen[chunk_id]['similarity']: seen[chunk_id] = row# 返回前N个唯一结果returnsorted( seen.values, key=lambda x: x['similarity'], reverse=True )[:limit]
- deflate_chunk(text: str, chunk_size=512) -> list:"""分块前嵌入完整文档"""# 步骤1:嵌入整个文档(最多8192个标记) full_doc_token_embeddings = transformer_embed(text) # 标记级别# 步骤2:定义分块边界 tokens = tokenize(text) chunk_boundaries = range(0, len(tokens), chunk_size)# 步骤3:为每个分块池化标记嵌入 chunks_with_embeddings = for start in chunk_boundaries: end = start + chunk_size chunk_text = detokenize(tokens[start:end])# 平均池化标记嵌入(保留完整文档上下文!) chunk_embedding = mean_pool(full_doc_token_embeddings[start:end]) chunks_with_embeddings.append((chunk_text, chunk_embedding))return chunks_with_embeddings
- 检索到的文档:{format_docs_for_grading(results)}按1-5分评估这些文档与查询的相关性。仅用数字回答。""" grade_response = await client.chat.completions.create( model="gpt-4o-mini", messages=[{"role": "user", "content": grade_prompt}], temperature=0 ) grade = int(grade_response.choices[0].message.content.strip.split[0])# 如果结果好,返回它们if grade >= 3:return {"results": results,"iterations": iteration + 1,"final_query": query }# 如果结果差且不是最后一次迭代,优化查询if iteration < max_iterations - 1: refine_prompt = f"""查询"{query}"返回了低相关性结果。
-
初始化一次reranker = CrossEncoder('cross-encoder/ms-marco-MiniLM-L-6-v2')asyncdefsearch_with_reranking(query: str, limit: int = 5) -> list:# 阶段1:快速向量检索(获取4倍候选) candidate_limit = min(limit * 4, 20) query_embedding = await embedder.embed_query(query) candidates = await db.query("SELECT content, metadata FROM chunks ORDER BY embedding $1 LIMIT $2", query_embedding, candidate_limit )# 阶段2:使用交叉编码器重新排序 pairs = query, row['content' for row in candidates] scores = reranker.predict(pairs)
- asyncdefhierarchical_search(query: str) -> str:"""搜索子级,返回父级""" query_emb = get_embedding(query)# 搜索小子级以提高精度 results = await db.query("""SELECT p.content, p.metadata FROM child_chunks c JOIN parent_chunks p ON c.parent_id = p.id ORDER BY c.embedding %s LIMIT 3""", query_emb )# 返回大父级以获取上下文 formatted = for content, metadata in results: meta = json.loads(metadata) formatted.append(f"[{meta['heading']}]\n{content}")return"\n\n".join(formatted)
重要细节
准确率从60%到94%:构建RAG系统的核心策略(2026最新版) - 今日头条 / 朴素RAG方法defnaive_rag(query: str) -> str:# 1. 对查询进行向量化 query_embedding = embed(query) / 2. 查找相似片段 chunks = vector_db.search(query_embedding, top_k=5) / 3. 生成答案 context = "\n".join(chunks) answer = llm.generate(f"Context: {context}\n\nQuestion: {query}")return answer
title: "准确率从60%到94%:构建RAG系统的核心策略(2026最新版) - 今日头条" source_url: "https://www.toutiao.com/article/7627674278067601935/?wid=1782275799998" source_site: "www.toutiao.com" clipped_at: "2026-06-24T04:36:56.516383+00:00" clipper: "aiwiki-url-ingest" extractor: "toutiao_rendered"
准确率从60%到94%:构建RAG系统的核心策略(2026最新版) - 今日头条
我第一次构建RAG系统时,以为一切都很简单:把文档切块、创建向量、检索相似内容,然后喂给大模型。
结果准确率只有60%左右。
用户得到的是完全不相关的答案。系统会“自信满满”地返回毫无关联的信息,有时甚至错过文档间显而易见的联系。
我花了数周时间排查问题。
后来发现,我使用的正是研究人员所称的“朴素RAG”——这种最基础的实现方案,几乎从不在生产环境中奏效。
本文将带你了解11个先进的RAG策略,它们将我的系统准确率从60%提升到了94%,并详细展示如何组合这些策略以实现最大效果。
一、朴素RAG的根本问题
我们先看看为什么基础RAG经常失败。
传统RAG遵循这个简单流程:
# 朴素RAG方法defnaive_rag(query: str) -> str:# 1. 对查询进行向量化 query_embedding = embed(query)
# 2. 查找相似片段 chunks = vector_db.search(query_embedding, top_k=5)
# 3. 生成答案 context = "\n".join(chunks) answer = llm.generate(f"Context: {context}\n\nQuestion: {query}")return answer
看起来合理,对吧?
但问题出在哪里:
- 固定大小的分 块会在思路中间切断句子,丢失上下文
- 单一查询视角 会错过表述不同的文档
- 没有相关性过滤 ——你得到的是“最接近”的匹配,而不是最相关的
- 有限的上下文 ——小片段缺乏完整画面
结果就是?你的RAG系统变成了一个高级猜谜游戏。
下面展示如何修复这些问题。
二、真正有效的11个策略
我将这些策略分为三类: 摄取策略 (如何准备文档)、 查询策略( 如何搜索)和 混合方法 (组合策略以放大效果)。
**策略1:
按重排序分数排序并返回前N个 reranked = sorted(zip(candidates, scores), key=lambda x: x[1], reverse=True )[:limit]return [doc for doc, score in reranked]
- -> list:# 阶段1:快速向量检索(获取4倍候选) candidate_limit = min(limit * 4, 20) query_embedding = await embedder.embed_query(query) candidates = await db.query("SELECT content, metadata FROM chunks ORDER BY embedding $1 LIMIT $2", query_embedding, candidate_limit )# 阶段2:使用交叉编码器重新排序 pairs = query, row['content' for row in candidates] scores = reranker.predict(pairs)
按重排序分数排序并返回前N个 reranked = sorted(zip(candidates, scores), key=lambda x: x[1], reverse=True )[:limit]return [doc for doc, score in reranked]
**性能比较:**
查询:"第二季度收入增长因素是什么?" 纯向量(相似度分数):1."第二季度收入为3.14亿美元" (0.82)2."增长因素包括..." (0.78)3."第一季度,收入为..." (0.76) 重排序后(相关性分数):1."增长因素包括..." (0.94)2."第二季度收入为3.14亿美元" (0.89)3."第二季度表现的关键驱动因素..." (0.85)
**优点:**
- 显著提高精度
- 考虑更多候选而不压倒LLM
- 可以修复向量搜索错误
**缺点:**
- 比纯向量搜索慢
- 需要更多计算资源
- 成本稍高
使用时机: 当精度比速度更重要时。非常适合错误答案成本高的问题回答系统。
**策略4:查询扩展**
作用: 使用LLM将简短查询扩展为更详细、全面的版本。
解决的问题: 用户查询通常很模糊。“什么是RAG?”并不能捕捉他们想要的是架构细节、使用案例还是实施指南。
**代码示例:**
asyncdefexpand_query(query: str) -> str:"""将简短查询扩展为详细版本""" system_prompt = """你是查询扩展助手。 接受简短的用户查询并将其扩展为更详细的版本:1. 添加相关上下文和澄清2. 包含相关术语和概念3. 指定应涵盖的方面4. 保持原始意图5. 保持为单个连贯问题将查询扩展为详细2-3倍,同时保持专注。""" response = await client.chat.completions.create( model="gpt-4o-mini", messages=[ {"role": "system", "content": system_prompt}, {"role": "user", "content": f"扩展此查询:{query}"} ], temperature=0.3 )return response.choices[0].message.content.strip
**转换示例:**
``
### 片段 3
_base("退款政策") → 找到提及"refund_policy.pdf"的分块
2. 意识到分块没有完整政策
3. 调用retrieve_full_document("退款政策") → 返回完整文档4. 从完整文档生成全面答案
优点:
- 高度灵活和自适应
- 处理多样化的数据源
- 可以组合多种检索策略
缺点:
- 实现更复杂
- 行为不太可预测
- 由于多步推理,延迟更高
使用时机: 当你有异构数据源(文档、数据库、API)且查询复杂度差异很大时。
策略7:自反思RAG
作用: 检索文档后,系统评估其相关性,如果需要则优化查询,并重新搜索直到满意为止。
解决的问题: 初始搜索通常返回差的结果,但传统RAG只是使用它得到的任何结果。
代码示例:
_base("退款政策") → 找到提及"refund_policy.pdf"的分块 2. 意识到分块没有完整政策 3. 调用retrieve_full_document("退款政策") → 返回完整文档4. 从完整文档生成全面答案
**优点:**
- 高度灵活和自适应
- 处理多样化的数据源
- 可以组合多种检索策略
**缺点:**
- 实现更复杂
- 行为不太可预测
- 由于多步推理,延迟更高
使用时机: 当你有异构数据源(文档、数据库、API)且查询复杂度差异很大时。
**策略7:自反思RAG**
作用: 检索文档后,系统评估其相关性,如果需要则优化查询,并重新搜索直到满意为止。
解决的问题: 初始搜索通常返回差的结果,但传统RAG只是使用它得到的任何结果。
**代码示例:**
asyncdefsearch_with_self_reflection(query: str, limit: int = 5, max_iterations: int = 2) -> dict:"""自校正搜索循环"""for iteration inrange(max_iterations):# 执行搜索 results = await vector_search(query, limit)# 评分相关性 grade_prompt = f"""查询:{query} 检索到的文档:{format_docs_for_grading(results)}按1-5分评估这些文档与查询的相关性。仅用数字回答。""" grade_response = await client.chat.completions.create( model="gpt-4o-mini", messages=[{"role": "user", "content": grade_prompt}], temperature=0 ) grade = int(grade_response.choices[0].message.content.strip.split[0])# 如果结果好,返回它们if grade >= 3:return {"results": results,"iterations": iteration + 1,"final_query": query }# 如果结果差且不是最后一次迭代,优化查询if iteration < max_iterat
初始化Graphiti(连接到Neo4j)graphiti = Graphiti("neo4j://localhost:7687", "neo4j", "password")asyncdefingest_document(text: str, source: str):"""摄取到知识图谱"""# Graphiti自动提取实体和关系await graphiti.add_episode( name=source, episode_body=text, source=EpisodeType.text, source_description=f"文档:{source}" )asyncdefsearch_knowledge_graph(query: str) -> str:"""混合搜索:语义 + 关键词 + 图"""# Graphiti结合:# - 语义相似性(嵌入)# - BM25关键词搜索# - 图结构遍历# - 时间上下文 results = await graphiti.search(query=query, num_results=5)# 格式化图结果 formatted = for result in results: formatted.append(f"实体:{result.node.name}\n"f"类型:{result.node.type}\n"f"关系:{result.relationships}" )return"\n---\n".join(formatted)
果)
**优点:**
- 自校正
- 在迭代中改进
- 可以从差的初始结果中恢复
**缺点:**
- 最高延迟(2\-3次LLM调用)
- 最昂贵的策略
- 对于真正困难的查询可能仍然失败
使用时机: 当答案准确性至关重要且延迟可接受时。非常适合研究应用和复杂查询。
**策略8:知识图谱**
作用: 将向量搜索与图数据库结合以捕捉实体间的关系。
解决的问题: 向量搜索找到相似的文本但错过了明确的关系,如“CEO of”、“located in”、“reported revenue”。
**使用Graphiti的概念示例:**
from graphiti_core import Graphitifrom graphiti_core.nodes import EpisodeType
初始化Graphiti(连接到Neo4j)graphiti = Graphiti("neo4j://localhost:7687", "neo4j", "password")asyncdefingest_document(text: str, source: str):"""摄取到知识图谱"""# Graphiti自动提取实体和关系await graphiti.add_episode( name=source, episode_body=text, source=EpisodeType.text, source_description=f"文档:{source}" )asyncdefsearch_knowledge_graph(query: str) -> str:"""混合搜索:语义 + 关键词 + 图"""# Graphiti结合:# - 语义相似性(嵌入)# - BM25关键词搜索# - 图结构遍历# - 时间上下文 results = await graphiti.search(query=query, num_results=5)# 格式化图结果 formatted = for result in results: formatted.append(f"实体:{result.node.name}\n"f"类型:{result.node.type}\n"f"关系:{result.relationships}" )return"\n---\n".join(formatted)
**查询流程示例:**
查询:"谁运营ACME公司,第二季度发生了什么变化?" 纯向量搜索:- "ACME公司CEO信息..."- "第二季度变化包括..."知识图谱搜索:ACME公司(公司) ├─ HAS_CEO → JaneSmith(人物) ├─ REPORTED_REVENUE → $314M(财务) │ └─ PERIOD → Q22024 └─ LOCATED_IN → California(地点)结果:可以回答 "Jane Smith运营ACME公司,收入在第二季度增加到3.14亿美元"
**优点:**
- 捕捉向量错过的关系
- 减少幻觉
- 非常适合互连数据
**缺点:**
- 需要Neo4j基础设施
- 设置和维护复杂
- 更慢且更昂贵
- 需要实体提取
使用时机: 当实体间关系至关重要时(医疗网
### 训练 model.fit( train_objectives=[(train_dataloader, train_loss)], epochs=3, warmup_steps=100 ) model.save('./fine_tuned_financial_model')return model# 使用微调模型embedding_model = SentenceTransformer('./fine_tuned_financial_model')
是EBITDA?", "EBITDA(利息、税项、折旧及摊销前利润..."), ("解释资本支出", "资本支出(CapEx)指的是..."),# ... 数千对更多 ]deffine_tune_model:"""在领域数据上微调"""# 加载基础模型 model = SentenceTransformer('all-MiniLM-L6-v2')# 准备训练数据 train_examples = prepare_training_data train_dataloader = DataLoader(train_examples, shuffle=True, batch_size=16)# 定义损失函数 train_loss = losses.MultipleNegativesRankingLoss(model)
# 训练 model.fit( train_objectives=[(train_dataloader, train_loss)], epochs=3, warmup_steps=100 ) model.save('./fine_tuned_financial_model')return model# 使用微调模型embedding_model = SentenceTransformer('./fine_tuned_financial_model')
性能比较:
查询:"什么是营运资金?"
通用嵌入:1."营运资金包括..." (0.72)2."资本市场提供..." (0.68) ← 错误!3."工作条件在..." (0.65) ← 错误!
微调嵌入:1."营运资金包括..." (0.89)2."营运资金比率计算..." (0.84)3."有效管理营运资金..." (0.81)
优点:
- 通常提高5-10%的准确性
- 更好地理解领域术语
- 较小模型可以胜过较大的通用模型
缺点:
- 需要训练数据
- 训练时间和资源
- 需要定期重新训练
使用时机: 用于通用嵌入表现不佳的专业领域(医疗、法律、财务、技术)。
三、组合策略的力量:实现94%准确率的关键
以下是关键见解: 单个策略是好的。组合策略是变革性的。
在测试了数十种组合后,我发现了三种对不同用例特别有效的强力组合。
组合1:生产就绪堆栈(最佳整体)
策略: 上下文感知分块 + 重排序 + 查询扩展 + 智能体RAG
为什么有效: 每个策略解决不同的失败模式
- 上下文感知分块确保连贯的分块
- 查询扩展处理模糊查询
- 重排序修复向量搜索错误
- 智能体方法适应查询复杂度
性能: 92%准确率,1.2秒平均延迟
成本: 约 $0.003每次查询
最适合 :通用生产系统、客户支持、内部知识库
组合2:高准确率堆栈(最适合关键应用)
策略: 上下文检索 + 多查询 + 重排序 + 自反思RAG
为什么有效: 最大冗余和自校正
- 上下文检索确保分块自包含
- 多查询捕捉所有角度
- 重排序过滤噪音
- 自反思捕捉并修复错误
性能 :96%准确率,2.5秒平均延迟
成本: 约 $0.008每次查询
最适合: 医
相关条目
- 待后续补充双链关系