第十三章 检索增强提示工程
大语言模型的知识受限于训练数据截止日期,且存在幻觉风险。检索增强生成(Retrieval-Augmented Generation, RAG)通过将外部知识库与语言模型相结合,让 AI 能够基于真实、鲜活的文档内容生成回答,从根本上提升了回答的准确性和可信度。

本章将系统性地讲解 RAG 技术与提示词的协同工程,从基本原理到高级优化策略,帮助您构建可靠的检索增强应用。
13.1 RAG 技术的基本原理与工作流程
13.1.1 核心概念
RAG(检索增强生成) 是一种将信息检索系统与文本生成模型相结合的技术架构。核心思路是"先检索、再生成":先从外部知识库中检索与用户问题相关的文档片段,再将这些片段注入到提示词上下文中,最后让大模型基于检索到的上下文生成回答。
┌─────────────────────────────────────────────────────────────────┐│ RAG 系统工作流程│├─────────────────────────────────────────────────────────────────┤│ ││┌──────────────┐ │││原始文档│──▶ 索引阶段│││ (PDF/网页等) │▼││└──────────────┘┌────────────────┐││ │文档切块 │││ └───────┬────────┘││ ▼││ ┌────────────────┐││ │向量化嵌入 │││ └───────┬────────┘││ ▼││ ┌────────────────┐││ │存入向量数据库 │││ │(Pinecone等)│││ └────────────────┘││ ││┌──────────────┐ │││用户查询│──▶ 检索阶段││└──────────────┘▼││ ┌────────────────┐││ │查询向量化 │││ └───────┬────────┘││ ▼││ ┌────────────────┐││ │相似度搜索│││ │(Top-K)│││ └───────┬────────┘││ ▼││ ┌────────────────┐││ │返回相关文档 │││ └────────────────┘││ ││┌──────────────┐ │││相关文档│──▶ 生成阶段│││(上下文)│▼││└──────────────┘┌────────────────┐││ │构建增强提示 │││ │(RAG Prompt) │││ └───────┬────────┘││ ▼││ ┌────────────────┐││ │LLM 生成回答 │││ └────────────────┘││ │└─────────────────────────────────────────────────────────────────┘
13.1.2 RAG 的三大核心阶段
阶段一:索引(Indexing)
将原始文档转换为可检索的向量表示:
import voyageai# Anthropic 官方推荐的嵌入模型vo = voyageai.Client()documents = ["The Mediterranean diet emphasizes fish, olive oil, and vegetables...","Photosynthesis in plants converts light energy into glucose...","Machine learning is a subset of artificial intelligence...",]# 嵌入文档(使用 document input_type)doc_embeddings = vo.embed(documents,model="voyage-3",input_type="document").embeddings# 将向量存入向量数据库(以 Pinecone 为例)import pineconepc = pinecone.Client()index = pc.Index("knowledge-base")for i, embedding in enumerate(doc_embeddings):index.upsert([(f"doc-{i}", embedding, {"text": documents[i]})])
阶段二:检索(Retrieval)
将用户查询转换为向量,并在向量数据库中搜索最相关的内容:
# 嵌入用户查询query = "What is machine learning?"query_embedding = vo.embed([query],model="voyage-3",input_type="query").embeddings[0]# 在向量数据库中搜索# Voyage 嵌入已归一化,点积等于余弦相似度response = index.query(vector=query_embedding,top_k=5,include_metadata=True)# 获取检索结果retrieved_docs = [match["metadata"]["text"] for match in response["matches"]]print(retrieved_docs)
阶段三:生成(Generation)
将检索到的文档作为上下文,让 LLM 生成回答:
# 构建 RAG 提示词rag_prompt = f"""请根据以下参考文档回答用户问题。如果参考文档中没有包含回答所需的信息,请明确说明"根据已知信息无法回答"。不要基于训练知识编造答案。参考文档:---{chr(10).join([f'[文档{i+1}] {doc}' for i, doc in enumerate(retrieved_docs)])}---用户问题:{query}回答:"""# 调用 LLM 生成response = client.chat.completions.create(model="gpt-4",messages=[{"role": "system", "content": "你是一个基于文档回答问题的助手。"},{"role": "user", "content": rag_prompt}],temperature=0# RAG 场景通常使用确定性生成)
13.1.3 Anthropic 的 Citations 功能
Anthropic Claude 提供原生引用功能,可自动将回答中的声明关联到源文档的具体位置:
import anthropicclient = anthropic.Anthropic()response = client.messages.create(model="claude-opus-4-7",max_tokens=1024,messages=[{"role": "user","content": [{"type": "document","source": {"type": "text","media_type": "text/plain","data": "The grass is green. The sky is blue."},"title": "My Document","citations": {"enabled": True},},{"type": "text", "text": "What color is the grass and sky?"},],}],)# 响应中自动包含引用信息print(response.content)# [{type: "text", text: "According to the document, "},#{type: "text", text: "the grass is green", citations: [{cited_text: "The grass is green.", ...}]}]
13.1.4 RAG 的适用场景与优势
最佳适用场景:
- ✅ 需要最新信息的任务:新闻查询、股价、天气
- ✅ 私有知识库问答:企业文档、内部知识库
- ✅ 长文档理解与问答:合同分析、研究报告
- ✅ 减少幻觉:要求回答基于特定文档时
RAG vs 纯 LLM 的优势:
| 维度 | 纯 LLM | RAG |
|---|---|---|
| 知识时效性 | 受限于训练数据 | 可实时更新 |
| 幻觉风险 | 较高 | 显著降低 |
| 可追溯性 | 无 | 可引用来源 |
| 隐私安全 | 无法控制 | 可限制数据范围 |
| 成本 | 固定 | 检索+生成 |
13.2 检索结果的提示词融合方法
13.2.1 基本融合模板
将检索结果注入提示词的核心原则:
def build_rag_prompt(query: str, retrieved_docs: list[str],include_citations: bool = True) -> str:"""构建 RAG 提示词核心设计原则:1. 明确告知模型上下文边界2. 提供无法回答时的退出路径3. 为每个文档块添加来源标识4. 控制上下文总量避免注意力分散"""# 构建参考文档部分docs_section = "nn".join([f"【来源 {i+1}】n{doc}" for i, doc in enumerate(retrieved_docs)])prompt = f"""你是一个专业的问答助手。请根据以下参考文档回答用户问题。## 参考文档{docs_section}## 回答要求1. 仅基于上述参考文档中的信息进行回答2. 不要使用文档之外的训练知识3. 如果文档中没有包含回答问题所需的信息,请明确说明"根据提供的文档无法回答此问题"4. 在回答中使用【来源X】标注信息来源## 用户问题{query}## 回答"""return prompt
13.2.2 检索结果排序与截断策略
排序策略:
def rank_and_truncate(retrieved_docs: list[dict],# [{"text": ..., "score": ...}, ...]max_docs: int = 5,min_score: float = 0.7) -> list[dict]:"""检索结果排序与截断策略:1. 按相似度得分从高到低排序2. 设定最低分数阈值过滤低相关结果3. 限制返回文档数量"""# 按相似度降序排序sorted_docs = sorted(retrieved_docs, key=lambda x: x["score"], reverse=True)# 过滤低分结果filtered_docs = [doc for doc in sorted_docs if doc["score"] >= min_score]# 截断到最大数量return filtered_docs[:max_docs]
Token 预算截断:
import anthropicclient = anthropic.Anthropic()def truncate_by_token_budget(query: str,retrieved_docs: list[str],max_input_tokens: int = 80000,# 保留一些空间给系统提示和问题model: str = "claude-sonnet-4-6") -> list[str]:"""按 token 预算截断文档"""# 构建测试提示test_prompt = f"Query: {query}nnDocs:n" + "nn".join(retrieved_docs)# 计算当前 token 数count = client.messages.count_tokens(model=model,messages=[{"role": "user", "content": test_prompt}])# 如果超预算,按顺序移除文档直到满足要求while count.input_tokens > max_input_tokens and retrieved_docs:retrieved_docs.pop()test_prompt = f"Query: {query}nnDocs:n" + "nn".join(retrieved_docs)count = client.messages.count_tokens(model=model,messages=[{"role": "user", "content": test_prompt}])return retrieved_docs
13.2.3 上下文位置效应
"Lost in the Middle" 现象:当相关信息放置在输入中间时,模型准确率可能下降超过 30%。研究表明,Transformer 对开头和结尾的信息关注度最高,形成 U 型注意力曲线。
应对策略:
def position_sensitive_rerank(query: str,retrieved_docs: list[dict],top_k: int = 10,strategy: str = "interleave"# interleave | front_weight | back_weight) -> list[dict]:"""位置敏感的重排序策略策略说明:- interleave: 将文档交替放置,利用首尾效应- front_weight: 将高分文档放在前面- back_weight: 将高分文档放在后面"""sorted_docs = sorted(retrieved_docs, key=lambda x: x["score"], reverse=True)if strategy == "interleave":# 交替放置:高分组、低分组交替high, low = sorted_docs[:top_k//2], sorted_docs[top_k//2:top_k]result = []for i in range(len(high)):result.append(high[i])if i < len(low):result.append(low[i])return resultelif strategy == "front_weight":return sorted_docs[:top_k]elif strategy == "back_weight":# 翻转:将低分文档放在前面,高分放在后面docs = sorted_docs[:top_k]return docs[::-1]return sorted_docs[:top_k]
13.2.4 多样性重排序
避免检索结果过于相似(内容重复),增加多样性:
def diverse_rerank(retrieved_docs: list[dict],top_k: int = 5,diversity_threshold: float = 0.8) -> list[dict]:"""多样性重排序:避免返回过于相似的文档"""result = []remaining = retrieved_docs.copy()while len(result) < top_k and remaining:# 每次选择当前最高分且与已有结果不太相似的文档best_idx = 0best_score = -float('inf')for i, doc in enumerate(remaining):# 综合相关性得分和多样性diversity_penalty = 0for selected_doc in result:# 简单相似度估计(实际可用嵌入余弦相似度)similarity = len(set(doc["text"].split()) & set(selected_doc["text"].split())) / len(set(doc["text"].split()) | set(selected_doc["text"].split()))diversity_penalty += similarity * diversity_threshold 综合得分 = doc["score"] - diversity_penaltyif 综合得分 > best_score:best_score = 综合得分best_idx = iresult.append(remaining.pop(best_idx))return result
13.3 分块与引用提示:让模型准确引用来源
13.3.1 文本分块策略
策略一:固定大小分块(Fixed-Size Chunking)
最简单的分块方式,按固定 token 数或字符数切分:
def fixed_size_chunking(text: str,chunk_size: int = 500,# tokenschunk_overlap: int = 50# tokens) -> list[str]:"""固定大小分块"""tokens = text.split()# 简化的 token 化chunks = []start = 0while start < len(tokens):end = start + chunk_sizechunk = " ".join(tokens[start:end])chunks.append(chunk)# 移动窗口(考虑重叠)start = end - chunk_overlapreturn chunks
策略二:递归分块(Recursive Chunking)
LangChain 官方推荐的通用文本分割器,按分隔符优先级递归切分:
from langchain_text_splitters import RecursiveCharacterTextSplitter# 中文文本处理text_splitter = RecursiveCharacterTextSplitter(separators=["nn",# 优先按段落切分"n",# 其次按句子换行"。",# 中文句号"!","?",",",# 中文逗号" ","",# 最后按单词/字符],chunk_size=500,chunk_overlap=50,)chunks = text_splitter.split_text(long_chinese_document)
策略三:语义分块(Semantic Chunking)
基于嵌入向量的语义相似性对句子进行分组:
def semantic_chunking(text: str,sentences: list[str],similarity_threshold: float = 0.5) -> list[str]:"""语义分块:根据句子间语义相似度进行分组核心思想:当相邻句子的语义相似度低于阈值时,进行切分"""if not sentences:return [text]# 计算相邻句子的嵌入相似度embeddings = vo.embed(sentences, model="voyage-3", input_type="document")chunks = []current_chunk = [sentences[0]]for i in range(1, len(sentences)):# 计算相似度similarity = embeddings[i-1] @ embeddings[i]# 点积=余弦相似度if similarity >= similarity_threshold:# 相似度高,继续当前块current_chunk.append(sentences[i])else:# 相似度低,保存当前块并开始新块chunks.append("".join(current_chunk))current_chunk = [sentences[i]]# 保存最后一个块if current_chunk:chunks.append("".join(current_chunk))return chunks
13.3.2 让模型准确引用来源的提示技术
方法一:基于提示词的引用(通用方案)
citation_prompt = """请根据以下参考文档回答问题。## 参考文档{formatted_docs}## 回答要求1. 仅基于参考文档中的信息回答,不要编造信息2. 在回答的每个事实性声明后,使用 [来源X] 的格式标注信息来源3. 如果某个信息来自多个来源,标注所有相关来源,如 [来源1, 来源3]4. 如果文档中没有相关信息,请明确说明## 用户问题{query}## 回答(请使用引用标注)"""
方法二:使用 Anthropic Citations API(推荐)
Anthropic 的 Citations 功能提供了比纯提示词方案更可靠的引用机制:
def rag_with_citations(query: str, documents: list[str]) -> dict:"""使用 Anthropic Citations 实现结构化引用"""response = client.messages.create(model="claude-opus-4-7",max_tokens=1024,messages=[{"role": "user","content": [{"type": "document","source": {"type": "content","content": [{"type": "text", "text": doc} for doc in documents],},"title": "知识库文档","citations": {"enabled": True},},{"type": "text", "text": query},],}],)# 解析响应中的引用信息answer_text = ""citations = []for block in response.content:if block.type == "text":answer_text += block.textif hasattr(block, 'citations') and block.citations:citations.extend(block.citations)return {"answer": answer_text,"citations": citations}
13.3.3 引用格式设计
| 格式类型 | 示例 | 适用场景 |
|---|---|---|
| 编号引用 | "...根据研究[1],效果提升了30%..." | 学术、技术文档 |
| 括号来源 | "...(来源:产品手册第5页)..." | 面向终端用户 |
| 脚注引用 | "...效果显著提升[^1]" | 长文档、报告 |
| Claude Citations | 自动生成带位置的引用 | API 集成应用 |
13.4 多轮检索与动态提示生成
13.4.1 查询重写(Query Rewriting)
查询重写是将用户原始查询转换为更适合检索的形式:
def rewrite_query_for_retrieval(query: str, history: list[str] = None) -> str:"""查询重写:优化查询以获得更好的检索结果"""rewrite_prompt = f"""你是一个查询优化专家。将以下用户查询重写为更适合信息检索的形式。重写原则:1. 识别查询中的核心概念和关键词2. 补充可能的同义词和相关术语3. 将模糊问题改写为更精确的检索 query4. 如果有对话历史,将其整合到当前查询中{ '对话历史:n' + 'n'.join([f'- {h}' for h in history]) + 'n' if history else '' }原始查询:{query}重写后的查询:"""response = client.chat.completions.create(model="gpt-4",messages=[{"role": "user", "content": rewrite_prompt}],temperature=0)return response.choices[0].message.content.strip()
13.4.2 HyDE(Hypothetical Document Embedding)
HyDE 不直接改写 query,而是让 LLM 先根据 query 生成一个"假设性答案文档",然后用这个假设文档的嵌入向量替代原始 query 的嵌入向量进行检索。
def hyde_retrieval(query: str, documents: list[str]) -> str:"""HyDE 检索:使用假设性文档增强检索效果核心原理:假设性答案通常比原始问题包含更多领域术语,在嵌入空间中与真实相关文档的距离更近。"""# 步骤1:让 LLM 生成假设性答案文档hyde_prompt = f"""请写一段详细的内容,回答以下问题。这段内容应该是对问题的全面解答,不需要完全准确,但要有信息量。问题:{query}假设性内容:"""hypothetical_doc = client.chat.completions.create(model="gpt-4",messages=[{"role": "user", "content": hyde_prompt}],temperature=0.7).choices[0].message.content# 步骤2:用假设文档的嵌入进行检索hypothetical_embedding = vo.embed([hypothetical_doc],model="voyage-3",input_type="document"# 注意:使用 document 而非 query).embeddings[0]# 步骤3:在向量数据库中搜索similarities = [hypothetical_embedding @ doc_emb for doc_emb in doc_embeddings]top_indices = sorted(range(len(similarities)), key=lambda i: similarities[i], reverse=True)[:5]# 步骤4:用真实文档生成最终回答retrieved_docs = [documents[i] for i in top_indices]final_prompt = f"""基于以下文档回答问题:文档:{chr(10).join([f'{i+1}. {doc}' for i, doc in enumerate(retrieved_docs)])}问题:{query}回答:"""final_response = client.chat.completions.create(model="gpt-4",messages=[{"role": "user", "content": final_prompt}])return final_response.choices[0].message.content
13.4.3 多轮检索策略
迭代检索:基于前一轮结果进行补充检索:
def multi_round_retrieval(query: str,max_rounds: int = 3,initial_k: int = 5,expand_k: int = 3) -> list[str]:"""多轮检索:迭代扩展检索结果"""all_docs = []seen_ids = set()for round_num in range(max_rounds):if round_num == 0:# 第一轮:直接检索current_query = queryelse:# 后续轮次:基于前一轮回答生成追问current_query = generate_followup_query(original_query=query,previous_docs=all_docs[-initial_k:],previous_answer=last_answer)# 检索results = retrieve_documents(current_query, top_k=initial_k if round_num == 0 else expand_k)# 去重合并for doc in results:if doc["id"] not in seen_ids:all_docs.append(doc)seen_ids.add(doc["id"])# 检查是否需要继续if round_num > 0 and should_stop(all_docs, query):breakreturn all_docs
13.5 RAG 与提示词工程的协同优化
13.5.1 RAGAS 评估指标体系
RAGAS 是最主流的 RAG 系统自动评估框架:
from ragas import EvaluationDatasetfrom ragas.dataset_schema import SingleTurnSamplefrom ragas.metrics import Faithfulness, AnswerRelevancy, ContextPrecision# 构建测试数据集data_samples = [SingleTurnSample(user_input="What is Ragas?",response="Ragas is an evaluation framework for LLM applications",reference="Ragas is an evaluation framework for LLM applications",retrieved_contexts=["Ragas provides objective metrics for evaluation."],),]dataset = EvaluationDataset(samples=data_samples)# 运行评估results = dataset.evaluate(metrics=[Faithfulness(), AnswerRelevancy(), ContextPrecision()])print(results)
三大核心指标:
| 指标 | 含义 | 衡量内容 |
|---|---|---|
| Faithfulness | 忠实度 | 生成回答与上下文中事实的一致性 |
| Answer Relevancy | 答案相关性 | 回答与用户问题的相关程度 |
| Context Precision | 上下文精确度 | 相关文档排在前面、不相关文档排在后面的能力 |
13.5.2 提示词对 RAG 效果的影响
关键因素:
- 上下文位置:将检索到的文档放在提示词的前部还是后部会影响模型注意力分配
- 指令明确性:明确要求"仅基于提供的文档回答"可以显著降低幻觉率
- 来源标注要求:要求模型标注来源可以同时提高忠实度和可追溯性
- 格式约束:指定回答格式(如结构化输出)可以提升回答的一致性
- 不确定性处理:提供"不知道"的退出路径可以减少编造
13.5.3 端到端 RAG 优化框架
class RAGOptimizer:"""RAG 系统优化器"""def __init__(self):self.retriever = Noneself.generator = Noneself.evaluator = Nonedef optimize(self, train_samples: list, n_iterations: int = 5):"""迭代优化 RAG 系统"""best_score = 0best_config = {}for iteration in range(n_iterations):# 1. 使用当前提示词生成回答responses = []for sample in train_samples:query = sample["query"]docs = self.retriever.retrieve(query)prompt = self.build_prompt(query, docs)response = self.generator.generate(prompt)responses.append({"query": query,"response": response,"retrieved_docs": docs})# 2. 评估当前性能scores = self.evaluator.evaluate(responses)avg_score = sum(scores) / len(scores)print(f"迭代 {iteration+1}: 得分 = {avg_score:.4f}")# 3. 如果有提升,保存配置if avg_score > best_score:best_score = avg_scorebest_config = self.get_current_config()# 4. 基于反馈调整提示词self.adjust_prompt_based_on_errors(responses, train_samples)return best_config, best_score
13.5.4 RAG 与其他技术的融合
RAG + 长上下文:2025 年的主流架构是 Hybrid Context(混合上下文)—— 用 RAG 做宽召回,用长上下文做深推理。
RAG + Agent:RAG 系统作为 Agent 的工具之一,实现自主知识探索:
def rag_agent_system(user_query: str):"""RAG + Agent 协作系统"""agent = Agent(instructions="你是知识探索专家,可以自主决定是否需要检索外部知识。",tools=[retrieve_documents_tool,# RAG 检索工具web_search_tool,# 网页搜索工具# 其他工具])return agent.run(user_query)
本章小结
核心概念回顾
| 概念 | 说明 |
|---|---|
| RAG | 检索增强生成,先检索后生成,提升准确性和可信度 |
| 索引 | 将文档切块、向量化,存入向量数据库 |
| 检索 | 查询向量化,在向量库中相似度搜索 |
| 生成 | 将检索结果注入提示词,LLM 基于上下文生成 |
| Citations | Anthropic 原生引用功能,不占用输出 token |
| HyDE | 假设性文档嵌入,用假设答案增强检索效果 |
| RAGAS | RAG 评估框架,Faithfulness/AnswerRelevancy/ContextPrecision |
关键要点
- RAG 三大阶段:索引(切块→向量化→入库)→ 检索(查询→搜索→返回)→ 生成(构建提示→调用 LLM)
- 检索质量决定上限:RAG 的效果主要由检索质量决定,Anthropic 强调"检索质量比模型大小更重要"
- 分块策略影响显著:递归分块兼顾语义完整性和嵌入效果
- 引用格式可设计:从提示词标注到 Anthropic 原生 Citations
- HyDE 增强检索:通过生成假设性答案来弥合 query-doc 语义鸿沟
- RAGAS 量化评估:Faithfulness(忠实度)、Answer Relevancy(相关性)、Context Precision(精确度)
参考来源:
| 来源 | 链接 |
|---|---|
| Anthropic Embeddings 指南 | docs.anthropic.com/en/docs/bui… |
| Anthropic Citations | docs.anthropic.com/en/docs/bui… |
| Anthropic Cookbook (GitHub) | github.com/anthropics/… |
| OpenAI Embeddings 指南 | platform.openai.com/docs/guides… |
| LangChain Recursive Text Splitter | python.langchain.com/docs/how_to… |
| RAGAS GitHub | github.com/explodinggr… |
| RAGAS 官方文档 | docs.ragas.io/ |
| HyDE Paper | arxiv.org/abs/2212.09… |
| Voyage AI 嵌入文档 | docs.voyageai.com/docs/embedd… |
-
07.24
三国百将牌测试资格获取攻略 三国百将牌内测名额申请方法与技巧
-
07.24
小花仙拉贝尔之约兑换码 小花仙拉贝尔之约最新可用礼包码汇总
-
07.24
最强斗王陈国汉阵容推荐 最强斗王搭配实战指南
-
07.24
网飞猫如何登陆
-
07.24
最强斗王 玩家必看 第一天核心养成策略与战力飙升速成指南
-
07.24
拳皇98终极之战库拉 最强斗王库拉连招技巧与实战打法详解
-
-
下载
- |
-
-
下载
- 《行尸走肉第一章》免安装中文汉化硬盘版下载
- 单机|436 MB
- 一款以动作冒险为主题的游戏
-
-
下载
- 《街头霸王X铁拳》免安装中文汉化硬盘版下载
- 单机|111MB
- 一款非常好玩的格斗游戏
-
-
下载
- |
-
-
下载
- 《暗黑破坏神3》免安装繁体中文正式版下载
- 单机|7630 MB
- 一款以角色扮演为主题的游戏
-
-
下载
- 《马克思佩恩3》免安装硬盘版下载
- 单机|27033 MB
- 一款以第三人称射击为主题的游戏