智能体开发(4)rag
是一种将信息检索与大型语言模型生成相结合的技术。它通过在生成答案前从知识库中检索相关信息,为 LLM 提供准确的上下文,从而生成更准确、更可靠的回答。
一、介绍
RAG(检索增强生成) 是一种将信息检索与大型语言模型生成相结合的技术。它通过在生成答案前从知识库中检索相关信息,为 LLM 提供准确的上下文,从而生成更准确、更可靠的回答。
RAG 的核心原理
用户问题 → 检索相关文档 → 结合上下文 → LLM 生成答案
传统 LLM 的局限性:
-
知识截止:模型训练数据有时间限制
-
幻觉问题:可能生成不准确的信息
-
无法访问私有数据:无法获取训练数据之外的信息
RAG 的优势:
-
事实准确性:基于真实文档生成答案
-
实时更新:知识库可以随时更新
-
可追溯性:可以查看答案的来源
-
成本效益:比微调模型更经济
RAG 的工作流程
1. 文档处理阶段(离线)
原始文档 → 文本分割 → 向量化 → 向量数据库存储
2. 查询阶段(在线)
用户问题 → 向量化 → 相似度检索 → 上下文构建 → LLM 生成答案
RAG 的典型使用场景
1. 企业知识库问答
# 示例:公司内部文档问答
question = "我们公司的请假政策是什么?"
# RAG 会从员工手册、政策文件中检索相关信息
2. 学术研究助手
# 示例:论文研究
question = "关于深度学习在医疗影像中的应用有哪些最新进展?"
# RAG 会从学术论文库中检索相关研究
3. 客服机器人
# 示例:产品支持
question = "如何重置我的账户密码?"
# RAG 会从帮助文档、FAQ 中检索解决方案
4. 法律文档分析
# 示例:法律咨询
question = "劳动合同中关于解约的条款有哪些?"
# RAG 会从法律条文、合同模板中检索相关信息
二、使用
阶段一:准备知识库
1. 收集文档
documents = [
"公司政策文档.pdf",
"产品手册.docx",
"技术文档.md",
"常见问题解答.txt"
]
2. 文档预处理
def preprocess_documents(docs):
# 文本提取
texts = extract_text_from_files(docs)
# 文本清洗
cleaned_texts = clean_text(texts)
# 文本分割
chunks = split_text_into_chunks(cleaned_texts)
return chunks
3. 向量化存储
def build_vector_store(chunks):
# 使用嵌入模型生成向量
embeddings = embedding_model.encode(chunks)
# 存储到向量数据库
vector_store.add_documents(chunks, embeddings)
阶段二:查询使用
1. 基本查询
def rag_query(question):
# 1. 检索相关文档
relevant_docs = vector_store.search(question, top_k=3)
# 2. 构建上下文
context = build_context(relevant_docs)
# 3. 生成答案
prompt = f"""基于以下上下文信息回答问题:
上下文:
{context}
问题:{question}
请根据上下文提供准确的回答。"""
answer = llm.generate(prompt)
return answer, relevant_docs
2. 带来源引用的查询
def rag_query_with_sources(question):
relevant_docs = vector_store.search(question, top_k=5)
context = ""
sources = []
for i, doc in enumerate(relevant_docs):
context += f"[{i+1}] {doc.content}\n\n"
sources.append({
"content": doc.content,
"metadata": doc.metadata,
"score": doc.score
})
answer = llm.generate(f"上下文:\n{context}\n问题:{question}")
return {
"answer": answer,
"sources": sources,
"context": context
}
实际使用示例
示例 1:技术文档问答
# 初始化 RAG 系统
rag_system = RAGSystem(
embedding_model="text-embedding-ada-002",
llm="gpt-4",
vector_store="chromadb"
)
# 构建知识库
rag_system.build_knowledge_base("technical_docs/")
# 查询使用
result = rag_system.query("如何配置数据库连接池?")
print(f"答案: {result['answer']}")
print("参考文档:")
for source in result['sources']:
print(f"- {source['metadata']['file_name']}")
示例 2:多轮对话 RAG
class ConversationalRAG:
def __init__(self):
self.conversation_history = []
def chat(self, question):
# 结合对话历史进行检索
context_query = self.build_context_query(question)
# 检索相关文档
relevant_docs = self.vector_store.search(context_query)
# 构建包含历史的提示
prompt = self.build_prompt(question, relevant_docs, self.conversation_history)
# 生成答案
answer = self.llm.generate(prompt)
# 更新对话历史
self.conversation_history.append({"user": question, "assistant": answer})
return answer, relevant_docs
示例 3:带过滤的 RAG
def filtered_rag_query(question, filters=None):
# 使用元数据过滤
relevant_docs = vector_store.search(
question,
top_k=5,
filter={"document_type": "policy"} # 只检索政策文档
)
# 构建上下文和生成答案
context = build_context(relevant_docs)
answer = llm.generate(f"基于以下政策文档回答:\n{context}\n问题:{question}")
return answer, relevant_docs
RAG 的最佳实践
1. 文档预处理优化
-
分块策略:根据文档类型调整块大小
-
重叠设置:保持块间重叠以避免信息断裂
-
元数据丰富:为每个块添加来源、类型等元数据
2. 检索优化
-
多路检索:结合关键词检索和向量检索
-
重排序:使用更精细的模型对初步结果重排序
-
查询扩展:对原始问题进行同义扩展
3. 提示工程优化
def build_optimized_prompt(question, context):
return f"""你是一个专业的助手。请严格基于提供的上下文信息回答问题。
上下文信息:
{context}
用户问题:{question}
请遵循以下要求:
1. 如果上下文包含答案,请直接基于上下文回答
2. 如果上下文不包含足够信息,请说明信息来源不足
3. 不要编造上下文之外的信息
4. 引用具体的上下文片段
请给出专业、准确的回答:"""
4. 评估和监控
-
答案质量评估:准确性、相关性、完整性
-
检索质量评估:召回率、准确率
-
用户反馈收集: thumbs up/down 机制
常用工具和框架
向量数据库
-
ChromaDB:轻量级,易于使用
-
Pinecone:云服务,高性能
-
Weaviate:开源,功能丰富
-
Qdrant:高性能,Rust 开发
嵌入模型
-
OpenAI Embeddings:text-embedding-ada-002
-
Sentence Transformers:all-MiniLM-L6-v2
-
Cohere Embed:多语言支持
-
BGE Models:中文优化
开发框架
-
LangChain:功能全面的 RAG 框架
-
LlamaIndex:专为 RAG 优化
-
Haystack:企业级解决方案
RAG 通过将检索和生成结合,有效解决了 LLM 的知识局限性和幻觉问题。在实际使用中,关键是:
-
高质量的知识库构建
-
合适的检索策略
-
优化的提示工程
-
持续的评估和改进
三、创建
基于deepseek api_key和ChromaDB的三体知识库
1.simple_rag
-
初始化系统
-
导入RAG系统模块
-
创建系统实例
-
-
准备知识库
-
检查是否已经构建过向量数据库
-
如果是首次运行,会读取"threebody_problem.txt"文件
-
将小说内容分割成小块,转换成向量格式存储
-
-
演示问答
-
准备了6个关于《三体》的问题
-
比如"叶哲泰是谁?"、"文章开头描述的是什么场景?"等
-
-
处理每个问题
-
系统在向量数据库中搜索相关内容
-
生成基于小说内容的答案
-
显示答案和参考了哪些原文片段
-
-
结果显示
-
展示生成的答案
-
列出参考的文档片段及其来源
-
显示每个片段的前100个字符作为预览
-
import os
import sys
from typing import List, Dict, Any
import chromadb
import numpy as np
import requests
import json
class SimpleThreeBodyRAG:
"""简化的三体RAG系统"""
def __init__(self, persist_directory: str = "./chroma_db_simple", api_key: str = None):
self.persist_directory = persist_directory
self.client = None
self.collection = None
self.embedding_model = "deepseek_api" # 标记为使用 API
self.api_key = api_key or "your api_key"
self._initialize_components()
def _initialize_components(self):
"""初始化组件"""
print("使用 DeepSeek API 进行问答,ChromaDB 默认嵌入")
# 初始化ChromaDB客户端
self.client = chromadb.PersistentClient(path=self.persist_directory)
# 创建或获取集合(让 ChromaDB 使用默认嵌入函数)
try:
self.collection = self.client.get_collection("threebody_docs")
print("已加载现有向量数据库")
except:
self.collection = self.client.create_collection("threebody_docs")
print("创建新的向量数据库(使用默认嵌入)")
def load_and_chunk_documents(self, file_path: str) -> List[Dict[str, Any]]:
"""加载文档并分块"""
text = None # 先初始化变量
# 尝试多种编码
for encoding in ['utf-8', 'gbk', 'utf-16']:
try:
with open(file_path, 'r', encoding=encoding) as f:
text = f.read()
print(f"成功使用编码: {encoding}")
break # 如果成功就跳出循环
except UnicodeDecodeError:
continue # 如果失败就尝试下一个编码
# 检查是否成功读取
if text is None:
# 如果所有编码都失败,使用忽略错误的方式
try:
with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
text = f.read()
print("使用 utf-8 with ignore 读取文件")
except Exception as e:
raise ValueError(f"无法读取文件 {file_path}: {str(e)}")
# 简单的文本分割
paragraphs = text.split('\n\n')
chunks = []
for i, para in enumerate(paragraphs):
cleaned_para = para.strip()
# 更严格的内容检查
if len(cleaned_para) > 50 and any(char.isalnum() for char in cleaned_para):
chunks.append({
"id": f"chunk_{i}",
"content": cleaned_para,
"metadata": {"source": "threebody_problem.txt", "chunk_id": i}
})
print(f"成功分割为 {len(chunks)} 个文档块")
# 验证所有块都有内容
for chunk in chunks:
if not chunk["content"] or chunk["content"] is None:
print(f"警告: 发现空内容块: {chunk['id']}")
return chunks
def build_vector_store(self, file_path: str):
"""构建向量存储"""
print("正在加载和分割文档...")
chunks = self.load_and_chunk_documents(file_path)
print(f"文档已分割为 {len(chunks)} 个片段")
# 过滤有效块
valid_chunks = [
chunk for chunk in chunks
if chunk["content"] and isinstance(chunk["content"], str) and chunk["content"].strip()
]
print(f"有效文档块: {len(valid_chunks)} 个")
if not valid_chunks:
raise ValueError("没有有效的文档内容可处理")
# 直接添加到向量数据库,ChromaDB 会自动处理嵌入
print("正在构建向量数据库...")
self.collection.add(
documents=[chunk["content"] for chunk in valid_chunks],
metadatas=[chunk["metadata"] for chunk in valid_chunks],
ids=[chunk["id"] for chunk in valid_chunks]
)
print(f"向量数据库构建完成!共添加 {len(valid_chunks)} 个文档片段")
def _call_deepseek_chat(self, prompt: str) -> str:
"""调用DeepSeek API生成答案"""
try:
url = "https://api.deepseek.com/chat/completions"
headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {self.api_key}"
}
data = {
"model": "deepseek-chat",
"messages": [
{
"role": "system",
"content": "你是一个专业的《三体》系列知识助手。请基于提供的上下文信息,准确、简洁地回答用户的问题。如果上下文信息不足以回答问题,请明确说明。"
},
{
"role": "user",
"content": prompt
}
],
"temperature": 0.7,
"max_tokens": 1000
}
response = requests.post(url, headers=headers, json=data, timeout=30)
response.raise_for_status()
result = response.json()
return result["choices"][0]["message"]["content"]
except Exception as e:
print(f"DeepSeek API调用失败: {e}")
return f"抱歉,生成答案时出现错误: {str(e)}"
def query(self, question: str, n_results: int = 3) -> Dict[str, Any]:
"""查询系统"""
try:
# 直接使用文本查询,ChromaDB 会自动处理嵌入
print("正在检索相关文档...")
results = self.collection.query(
query_texts=[question],
n_results=n_results
)
# 构建更详细的参考信息
source_documents = []
if results['documents'] and results['metadatas']:
for i, (doc, metadata) in enumerate(zip(results['documents'][0], results['metadatas'][0])):
source_documents.append({
"content": doc,
"metadata": metadata,
"source": metadata.get("source", "unknown"),
"chunk_id": metadata.get("chunk_id", i)
})
context = "\n".join([doc["content"] for doc in source_documents])
# 使用DeepSeek API生成答案
if context:
prompt = f"""请基于以下上下文信息回答用户的问题:
用户问题:{question}
相关上下文:
{context}
请基于上述上下文信息,给出准确、简洁的回答。"""
print("正在使用DeepSeek API生成答案...")
answer = self._call_deepseek_chat(prompt)
else:
answer = "抱歉,在文档中没有找到与您问题相关的信息。"
return {
"question": question,
"answer": answer,
"context": context,
"source_documents": source_documents
}
except Exception as e:
return {
"question": question,
"answer": f"查询过程中出现错误: {str(e)}",
"context": "",
"source_documents": []
}
def main():
"""主函数"""
rag_system = SimpleThreeBodyRAG()
# 检查是否已构建向量数据库
if rag_system.collection.count() == 0:
print("首次运行,正在构建向量数据库...")
rag_system.build_vector_store("threebody_problem.txt")
else:
print(f"向量数据库已存在,包含 {rag_system.collection.count()} 个文档片段")
print("\n=== 简化版三体RAG系统已就绪 ===")
print("你可以用自然语言询问关于《三体》系列的任何问题")
print("输入 'quit' 或 '退出' 来结束程序\n")
while True:
try:
question = input("请输入你的问题: ").strip()
if question.lower() in ['quit', '退出', 'exit']:
print("感谢使用三体RAG系统!")
break
if not question:
continue
print("\n正在处理问题...")
result = rag_system.query(question)
print(f"\n答案: {result['answer']}")
print(f"\n参考了 {len(result['source_documents'])} 个相关文档片段")
print("-" * 80)
except KeyboardInterrupt:
print("\n\n程序被用户中断")
break
except Exception as e:
print(f"发生错误: {e}")
if __name__ == "__main__":
main()
2.demo
#!/usr/bin/env python3
"""
三体RAG系统演示脚本
"""
import os
import sys
def demo_simple_rag():
"""演示简化版RAG系统"""
print("=== 三体RAG系统演示 ===\n")
try:
# 尝试导入简化版RAG系统
from simple_rag import SimpleThreeBodyRAG
# 初始化系统
print("正在初始化RAG系统...")
rag_system = SimpleThreeBodyRAG()
# 检查是否需要构建向量数据库
if rag_system.collection.count() == 0:
print("首次运行,正在构建向量数据库...")
rag_system.build_vector_store("threebody_problem.txt")
else:
print(f"向量数据库已存在,包含 {rag_system.collection.count()} 个文档片段")
print("\n=== 演示开始 ===\n")
# 演示问题列表
demo_questions = [
"叶哲泰是谁?发生了什么事情?",
"文章开头描述的是什么场景?",
"文化大革命在故事中如何体现?",
"红卫兵在故事中扮演什么角色?",
"物理学教授在故事中遇到了什么?",
"故事开头的时间背景是什么?"
]
for i, question in enumerate(demo_questions, 1):
print(f"问题 {i}: {question}")
print("-" * 50)
result = rag_system.query(question)
print(f"答案: {result['answer']}")
print(f"\n参考了 {len(result['source_documents'])} 个相关文档片段:")
# 显示具体的参考片段
for j, doc in enumerate(result['source_documents'], 1):
print(f"\n参考片段 {j} (来源: {doc['metadata'].get('source', 'unknown')}, 块ID: {doc['metadata'].get('chunk_id', 'N/A')}):")
# 显示片段的前100个字符作为预览
preview = doc['content'][:100] + "..." if len(doc['content']) > 100 else doc['content']
print(f" {preview}")
print("\n" + "="*80 + "\n")
print("演示完成!")
except ImportError as e:
print(f"导入错误: {e}")
print("请先安装依赖:pip install chromadb sentence-transformers numpy")
except Exception as e:
print(f"演示过程中出现错误: {e}")
if __name__ == "__main__":
demo_simple_rag()
更多推荐


所有评论(0)