本文较长,建议点赞收藏,以免遗失。文末还有粉丝福利,实力宠粉。
为了帮助大家从底层更好地理解 RAG 的工作原理,带你撕开技术黑箱,仅用numpy等Python基础库构建RAG系统,从零手撕RAG内核!从文本划分、向量化、相似度检索到生成优化,逐行代码解剖检索增强生成的核心逻辑,更深度解析9大实战技巧:从智能分块策略到动态上下文压缩,助你突破回答质量瓶颈。拒绝做调参工具人,这次彻底掌握RAG的底层基因!

一、RAG(检索增强生成)
1.1 什么是 RAG(检索增强生成)
大语言模型 (LLM) 功能强大,但也存在一些问题,例如生成不准确或不相关的内容(幻觉)、使用过时的信息以及以不透明的方式运行(黑盒推理) 。检索增强生成 (RAG) 是一种通过添加特定领域数据来增强 LLM 知识来解决这些问题的技术。
LLM 的一个关键用途是高级问答 (Q&A) 聊天机器人。为了创建一个能够理解并响应私人或特定主题查询的聊天机器人,需要利用所需的特定数据来扩展 LLM 的知识。RAG 可以在这方面提供帮助。
1.2 Basic RAG Pipeline
Basic RAG Pipeline 如下所示:

基本检索增强生成 (RAG) 管道通过两个主要阶段运行:
- 数据索引
- 检索与生成
数据索引过程:
- 数据加载: 这涉及导入所有要使用的文档或信息。
- 数据拆分: 将大文档分成较小的部分,例如,每个部分不超过 500 个字符。
- 数据嵌入: 使用嵌入模型将数据转换为矢量形式,以便计算机可以理解。
- 数据存储: 这些向量嵌入保存在向量数据库中,以便于搜索。
检索和生成过程:
- 检索: 当用户提出问题时:
-
- 首先使用与数据索引阶段相同的嵌入模型将用户的输入转换为向量(查询向量)。
- 然后,将此查询向量与向量数据库中的所有向量进行匹配,以找到最相似的向量(例如,使用欧几里得距离度量),这些向量可能包含用户问题的答案。此步骤旨在识别相关的知识块。
- 生成: LLM 模型将用户的问题与从向量数据库中检索到的相关信息结合起来,生成响应。此过程将问题与已识别的数据相结合,生成答案。
1.3 Basic RAG Pipeline Code for PDF Q&A
只需几行代码即可创建一个基本的 RAG Pipeline!用于构建自定义 RAG 应用程序的最流行的 Python 库是:
- LlamaIndex
- Langchain
以下代码片段创建了一个用于 PDF 文档问答的基本 RAG 管道。这里我们使用:
- Langchain
- OpenAI Embeddings
- Chroma Vector Database
- OpenAI LLM
ini
import os
from langchain_community.document_loaders import PyPDFLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.embeddings.openai import OpenAIEmbeddings
from langchain.vectorstores import Chroma
from langchain.prompts import ChatPromptTemplate
from langchain.chat_models import ChatOpenAI
OPENAI_API_KEY = os.getenv('OPENAI_API_KEY') # add your OpenAI API Key
DOC_PATH = "knowledge_base/alphabet_10K_2022.pdf"
CHROMA_PATH = "your_db_name"
# ----- Data Indexing Process -----
# load your pdf doc
loader = PyPDFLoader(DOC_PATH)
pages = loader.load()
# split the doc into smaller chunks i.e. chunk_size=500
text_splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=50)
chunks = text_splitter.split_documents(pages)
# get OpenAI Embedding model
embeddings = OpenAIEmbeddings(openai_api_key=OPENAI_API_KEY)
# embed the chunks as vectors and load them into the database
db_chroma = Chroma.from_documents(chunks, embeddings, persist_directory=CHROMA_PATH)
# ----- Retrieval and Generation Process -----
# this is an example of a user question (query)
query = 'what are the top risks mentioned in the document?'
# retrieve context - top 5 most relevant (closests) chunks to the query vector
# (by default Langchain is using cosine distance metric)
docs_chroma = db_chroma.similarity_search_with_score(query, k=5)
# generate an answer based on given user query and retrieved context information
context_text = "\n\n".join([doc.page_content for doc, _score in docs_chroma])
# you can use a prompt template
PROMPT_TEMPLATE = """
Answer the question based only on the following context:
{context}
Answer the question based on the above context: {question}.
Provide a detailed answer.
Don't justify your answers.
Don't give information not mentioned in the CONTEXT INFORMATION.
Do not say "according to the context" or "mentioned in the context" or similar.
"""
# load retrieved context and user query in the prompt template
prompt_template = ChatPromptTemplate.from_template(PROMPT_TEMPLATE)
prompt = prompt_template.format(context=context_text, question=query)
# call LLM model to generate the answer based on the given context and query
model = ChatOpenAI()
response_text = model.predict(prompt)
"""
Generated response:
The top risks mentioned in the provided context are:
1. Decline in the value of investments
2. Lack of adoption of products and services
3. Interference or interruption from various factors such as modifications, terrorist attacks, natural disasters, etc.
4. Compromised trade secrets and legal and financial risks
5. Reputational, financial, and regulatory exposure
6. Abuse of platforms and misuse of user data
7. Errors or vulnerabilities leading to service interruptions or failure
8. Risks associated with international operations.
"""
对RAG有了基本的了解之后,我们就可以开始从零手撕RAG内核之旅!
二、从0开始:简易RAG实现
在构建更复杂的 RAG 架构之前,我们先从最基础的版本入手。整个流程可以分为以下几个关键步骤:
1.数据导入: 加载并预处理原始文本数据,为后续处理做好准备。
2.文本分块: 将长文本分割成较小的段落或句子,以提高检索效率和相关性。
3.创建 Embedding: 使用嵌入模型将文本块转换为向量表示,便于进行语义层面的比较与匹配。
4.语义搜索: 根据用户输入的查询内容,在已有向量库中检索出最相关的文本块。
5.响应生成: 基于检索到的相关内容,结合语言模型生成最终的回答输出。
2.1 设置环境
首先,我们需要安装并导入必要的库:
bash
# !pip install numpy
# !pip install pymupdf
# !pip install dashscope
# !pip install openai
# !pip install scikit-learn
javascript
import os
import numpy as np
import json
import fitz
import dashscope
from openai import OpenAI
2.2 从PDF文件中提取文本
首先我们需要一个文本数据源。我们使用PyMuPDF库从PDF文件中提取文本,这里定义一个函数来从PDF中提取文本:
ini
def extract_text_from_pdf(pdf_path):
# 打开PDF文件
document = fitz.open(pdf_path)
all_text = "" # 初始化一个空字符串存储提取出的文本
# 遍历PDF中的每一页
for page_num in range(document.page_count):
page = document[page_num] # 获取页面
text = page.get_text("text") # 从页面提取文本
all_text += text # 将提取出的文本追加到all_text字符串
return all_text # 返回提取出的文本
2.3 对提取出的文本进行分块
有了提取出的文本后,我们将它分成较小的、有重叠的块,以提高检索准确性。
python
def chunk_text(text_input, chunk_size, overlap_size):
text_chunks = [] # 初始化一个列表存储文本块
# 循环遍历文本,步长为(chunk_size - overlap_size)
for i in range(0, len(text_input), chunk_size - overlap_size):
text_chunks.append(text_input[i:i + chunk_size]) # 追加从i到i+chunk_size的文本块到text_chunks列表
return text_chunks # 返回文本块列表
2.4 设置OpenAI API客户端
初始化OpenAI客户端用于生成嵌入和响应。
核心:选择一个合适的 Embedding Model,BGE-M3 ,BGE-large-zh
ini
client = OpenAI(
api_key=os.getenv("DASHSCOPE_API_KEY"), # 如果您没有配置环境变量,请在此处用您的API Key进行替换
base_url="https://dashscope.aliyuncs.com/compatible-mode/v1" # 百炼服务的base_url
)
2.5 提取并分块PDF文件中的文本
现在,我们加载PDF,提取文本,并将其分割成块。
ini
# 定义PDF文件路径
pdf_path = "knowledge_base/智能编码助手通义灵码.pdf"
# 从PDF文件中提取文本
extracted_text = extract_text_from_pdf(pdf_path)
# 将提取出的文本分割成1000字符大小的块,重叠部分为100字符
text_chunks = chunk_text(extracted_text, 1000, 100)
# 打印创建的文本块数量
print("文本块数量:", len(text_chunks))
# 打印第一个文本块
print("\n第一个文本块:")
print(text_chunks[0])
2.6 创建文本块的嵌入向量
嵌入向量将文本转换为数值向量,允许高效地进行相似度搜索。这里用了阿里云的embedding模型"text-embedding-v3"。
ini
# 创建文本块的嵌入向量
def create_embeddings(texts, model="text-embedding-v3"):
"""
输入一组文本(字符串或列表),返回对应的嵌入向量列表
"""
if isinstance(texts, str):
texts = [texts] # 确保输入为列表形式
completion = client.embeddings.create(
model=model,
input=texts,
encoding_format="float"
)
# 将响应转换为 dict 并提取所有 embedding
data = json.loads(completion.model_dump_json())
embeddings = [item["embedding"] for item in data["data"]]
return embeddings
2.7 执行语义搜索
我们通过计算余弦相似度来找到与用户查询最相关的文本块。
python
from sklearn.metrics.pairwise import cosine_similarity
# 语义搜索函数
def semantic_search(query, text_chunks, embeddings=None, k=2):
"""
在 text_chunks 中找出与 query 最相关的 top-k 文本块
参数:
query: 查询语句
text_chunks: 候选文本块列表
embeddings: 对应的嵌入向量列表(如果已提前计算)
k: 返回最相关的结果数量
返回:
top_k_chunks: 最相关的 top-k 文本块
"""
if embeddings is None:
embeddings = create_embeddings(text_chunks) # 如果没有提供,则自动生成
else:
assert len(embeddings) == len(text_chunks), "embeddings 和 text_chunks 必须长度一致"
query_embedding = create_embeddings(query)[0] # 获取查询的嵌入
# 计算相似度
similarity_scores = []
for i, chunk_embedding in enumerate(embeddings):
score = cosine_similarity([query_embedding], [chunk_embedding])[0][0]
similarity_scores.append((i, score))
# 排序并取 top-k
similarity_scores.sort(key=lambda x: x[1], reverse=True)
top_indices = [index for index, _ in similarity_scores[:k]]
return [text_chunks[index] for index in top_indices]
最后,执行查询操作,并打印结果。
python
# 执行语义搜索
query = '通义灵码的智能体能力是什么?'
top_chunks = semantic_search(query, text_chunks, k=2)
# 输出结果
print("Query:", query)
for i, chunk in enumerate(top_chunks):
print(f"Context {i + 1}:\n{chunk}\n=====================================")
2.8 基于检索块生成响应
ini
# 初始化 DashScope 客户端(使用阿里云通义千问)
client = OpenAI(
api_key=os.getenv("DASHSCOPE_API_KEY"), # 确保提前设置好环境变量
base_url="https://dashscope.aliyuncs.com/compatible-mode/v1"
)
# 设置系统提示词
SYSTEM_PROMPT = (
"你是一个 AI 助手,必须严格根据提供的上下文内容进行回答。"
"如果无法从提供的上下文中直接得出答案,请回复:'我无法根据现有信息回答这个问题。'"
)
def generate_response(system_prompt, user_message, model="qwen-max"):
"""
使用 DashScope 的通义千问模型生成基于上下文的回答。
参数:
system_prompt (str): 控制 AI 行为的系统指令
user_message (str): 用户输入的问题及上下文
model (str): 使用的模型名称,默认为 qwen-plus
返回:
str: 模型生成的回答内容
"""
response = client.chat.completions.create(
model=model,
temperature=0.0, # 温度设为0,保证输出确定性
max_tokens=512, # 可按需调整最大输出长度
messages=[
{"role": "system", "content": system_prompt},
{"role": "user", "content": user_message}
]
)
return response.choices[0].message.content.strip()
# 示例 top_chunks(假设这是 semantic_search 返回的结果)
top_chunks = [
"通义灵码是一个基于 AI 的智能编程助手。",
"文件编辑能力包括自动补全、错误修复和代码重构等功能。"
]
query = "通义灵码的智能体能力是什么?"
# 构建用户 prompt(包含上下文 + 问题)
user_prompt = "\n".join([f"上下文 {i + 1}:\n{chunk}" for i, chunk in enumerate(top_chunks)])
user_prompt += f"\n\n问题:{query}"
# 生成 AI 回答
answer = generate_response(system_prompt, user_prompt)
# 输出结果
print("AI 回答:")
print(answer)
2.9 评估回答
ini
# 定义评估系统的提示词
evaluate_system_prompt = (
"你是一个智能评估系统,负责评估 AI 助手的回答质量。"
"如果 AI 助手的回答与真实答案非常接近,请打 1 分;"
"如果回答错误或与真实答案不相关,请打 0 分;"
"如果回答部分符合真实答案,请打 0.5 分。"
"请直接输出评分结果:0、0.5 或 1。"
)
#构建评估 prompt 并获取评分
evaluation_prompt = f"""
用户问题: {query}
AI 回答:{answer}
真实答案: {ideal_answer}
请根据以下标准进行评分:
- 如果 AI 回答与真实答案非常接近 → 输出 1
- 如果回答错误或不相关 → 输出 0
- 如果部分匹配 → 输出 0.5
"""
evaluation_result = generate_response(evaluate_system_prompt, evaluation_prompt)
# 输出最终评分
print("AI 回答评分:", evaluation_result)
由于文章篇幅有限,我这边将《2025全网最具权威深度解析并手写RAG Pipeline》整理到了我的飞书文档,分享本文并关注AI大模型技术社公众号,发送"RAG实战"即可获取链接和专属访问密码。以下是文档整体目录:
如果本次分享对你有所帮助,记得告诉身边有需要的朋友,"我们正在经历的不仅是技术迭代,而是认知革命。当人类智慧与机器智能形成共生关系,文明的火种将在新的维度延续。"在这场波澜壮阔的文明跃迁中,主动拥抱AI时代,就是掌握打开新纪元之门的密钥,让每个人都能在智能化的星辰大海中,找到属于自己的航向。