前言
大家好,我是寻找光的sxy !!!
本文我们将了解Agent中的重要概念RAG,了解它的作用、概念,并通过代码去实现一个RAG流程,希望能对你有用。
为何需要
相信大家现在都或多或少的有在使用AI,现在的AI已经越来越智能了,我们问它很多问题,它都能答出来,十分厉害
但是如果你问它:我今天中午吃了什么?
它要不就回答不知道,要不可能就会捏造出一个食物。。。。。
发现了没,AI没法知道你自己的私人数据!!!
再往大点说,它只能基于它学习过的训练过的数据来生成答案,否则它很容易会回答不准确,甚至产生幻觉
此时,RAG就出现了
RAG的主要作用就是让AI在生成内容时有据可依——实时的检索传入的知识库,来提升模型的准确性
此时,你再问它:我今天中午吃了什么?它就会基于你传入的数据库来回答——吃了盖浇饭
它主要解决三类问题:
- 上下文长度有限:大模型无法记住你的所有数据,比如,你三天前吃了什么饭
- 知识局限:大模型的训练数据过时且没有训练过一些垂直领域的知识,比如你公司的年报
- 降低微调成本:大模型学习新知识需要微调或者重新训练,成本很高,比如,学习你公司的数据
概念
核心概念:让语言模型在生成答案前,先从外部知识库中检索相关依据,并将检索结果作为条件输入融合进生成过程,实现“有源可溯”的智能回答
它主要有5个关键步骤:
- 文档加载:从知识库加载原始文档
- 文本分割:将长文档切分为小块
- 向量化存储:文本 → 向量 → 存入
Chroma数据库 - 检索:根据问题找到最相似的文档块
- 生成:将文档 + 问题一起发给LLM生成回答
实践
文档加载
RAG的第一步就是加载原始文档
Agent将通过这些文档来检索信息并回答问题
# 使用 DirectoryLoader 批量加载目录下的所有txt文件
loader = DirectoryLoader(
KNOWLEDGE_BASE_DIR, # 要加载的目录路径
glob="**/*.txt", # 文件匹配模式(所有txt文件)
loader_cls=TextLoader, # 使用TextLoader作为单文件加载器
loader_kwargs={"encoding": "utf-8"}, # 指定编码
)
documents = loader.load() # 执行加载
文本分割
加载完文档后需要将文本进行分割以解决三个问题:
- 嵌入模型有输入长度限制
- 小块文本能更精确地匹配用户问题
- 避免超出LLM的上下文窗口限制
# 创建文本分割器
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=300, # 每块最大300字符
chunk_overlap=50, # 相邻块重叠50字符
length_function=len, # 使用len函数计算长度
separators=["\n\n", "\n", "。", "!", "?", " ", ""], # 分割优先级
# 先尝试按双换行分割,再按单换行,再按句号...依次尝试
)
# 执行分割
splits = text_splitter.split_documents(documents)
向量化存储
将文本块转换为向量,并存入向量数据库
例如:
"RAG是检索增强生成技术" → [0.12, -0.45, 0.78, ..., 0.23] (1536维向量)
"什么是RAG?" → [0.11, -0.43, 0.76, ...]
"今天天气真好" → [0.89, 0.12, -0.34, ...]
语义相似的文本会得到相似的向量,上面的第一条和第二条向量就比较接近
# 创建嵌入模型(使用阿里云通义千问的嵌入模型)
embeddings = DashScopeEmbeddings(
dashscope_api_key=api_key,
model="text-embedding-v2", # 阿里云的嵌入模型
)
# 将文本转换为向量
sample_vector = embeddings.embed_query(splits)
# 清除旧的向量数据库(如果存在)
if os.path.exists(CHROMA_DB_DIR):
shutil.rmtree(CHROMA_DB_DIR)
检索
将用户问题也转为向量,在数据库中搜索最相似的文本块
test_queries = [
"什么是RAG?它有什么优势?",
"有哪些主流的大语言模型?",
"向量数据库有哪些?",
]
for query in test_queries:
print(f"\n 🔍 查询: \"{query}\"")
# 方法1:相似性搜索(返回最相似的K个文档)
results = vectorstore.similarity_search(query, k=2) # 返回最相似的2个结果
print(f" 📎 检索到 {len(results)} 个相关文本块:")
for i, doc in enumerate(results, 1):
source = os.path.basename(doc.metadata.get("source", "未知"))
content = doc.page_content[:80].replace("\n", " ").strip()
print(f" [{i}] 来源: {source}")
print(f" 内容: {content}...")
# 方法2:带相似度分数的搜索
results_with_scores = vectorstore.similarity_search_with_score(query, k=1)
for doc, score in results_with_scores:
print(f" 📐 最佳匹配相似度分数: {score:.4f} (越小越相似)")
# 创建检索器(Retriever)对象,方便后续使用
retriever = vectorstore.as_retriever(
search_type="similarity", # 使用相似性搜索
search_kwargs={"k": 3}, # 返回前3个结果
)
生成
将检索到的文档和用户问题一起发给LLM生成回答
# 创建LLM实例
llm = ChatOpenAI(
model="qwen-plus", # 通义千问模型
api_key=api_key,
base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
temperature=0.7,
)
# RAG的提示词模板——这是连接"检索"和"生成"的桥梁
rag_prompt_template = """你是一个专业的AI知识助手。请严格基于以下提供的参考资料来回答用户的问题。
如果参考资料中没有相关信息,请明确说明"根据现有资料无法回答该问题"。
请不要编造信息,确保回答的准确性。
=== 参考资料 ===
{context}
=== 参考资料结束 ===
用户问题:{question}
请用中文详细回答:"""
prompt = PromptTemplate(
template=rag_prompt_template,
input_variables=["context", "question"],
)
response = llm.invoke(full_prompt)
answer = response.content if hasattr(response, "content") else str(response)
print(f" {answer}")