RAG简介
RAG (Retrieval Augmented Generation) 是一种将信息检索与文本生成相结合的技术,它正在成为工作和学习中的强大助理。
工作原理:
- 检索 (Retrieval): 当你提出一个问题或请求时,RAG 首先会从大量的外部知识库(例如维基百科、公司文档、代码库等)中检索相关的文档或片段。
- 增强 (Augmentation): 检索到的信息会被用来增强语言模型的上下文,使其能够更好地理解你的需求。
- 生成 (Generation): 最后,语言模型利用检索到的信息和自身的知识,生成更准确、更全面的答案、总结、翻译、代码等。
优势:
- 更准确的答案: RAG 可以访问外部知识库,从而提供比仅依赖自身参数的语言模型更准确、更可靠的答案。
- 更全面的信息: RAG 可以整合多个来源的信息,提供更全面的视角和更深入的理解。
- 更强的适应性: RAG 可以根据不同的任务和领域选择不同的知识库,从而更好地适应各种应用场景。
- 减少幻觉: 通过 grounding 生成的文本到检索到的信息,RAG 可以减少语言模型产生幻觉(即编造事实)的可能性。
应用场景:
- 问答系统: 提供更准确、更全面的答案,例如客服机器人、知识库搜索。
- 文本摘要: 生成更准确、更简洁的摘要,例如新闻摘要、论文摘要。
- 代码生成: 根据自然语言描述生成代码,例如代码补全、代码生成工具。
- 翻译: 提供更准确、更流畅的翻译,例如机器翻译系统。
- 教育: 提供个性化的学习资源和辅导,例如智能辅导系统、学习助手。
RAG 应用流程
完整的 RAG 应用流程主要包含两个阶段:
RAG 系统核心技术
嵌入模型 (Embedding Models)
嵌入模型是通过训练生成向量嵌入,这是一长串数字数组,代表文本序列的关联关系。
除了文本,类似图片之类的非结构化数据也能通过模型向量化,比如我们常见的人脸识别系统。
- 作用: 将文本(例如文档、查询、代码等)转换为稠密的向量表示,称为嵌入向量。这些向量捕捉了文本的语义信息,使得语义相似的文本在向量空间中彼此靠近。
- 工作原理: 嵌入模型通常基于深度学习技术,例如神经网络。它们通过学习大量的文本数据,将每个单词或短语映射到一个固定维度的向量空间中。在这个空间中,语义相似的单词或短语的向量距离会更近。
- 在 RAG 中的应用:
- 将文档和查询转换为嵌入向量,以便于后续的相似度计算和检索。
向量数据库 (Vector Databases)
- 作用: 专门用于存储和检索高维向量,例如文本嵌入向量。它们能够高效地执行相似性搜索,即找到与给定查询向量最相似的向量。
- 工作原理: 向量数据库使用 specialized 的索引结构和算法来存储和检索向量。这些索引结构能够快速地找到与查询向量距离最近的向量,从而实现高效的相似性搜索。
- 在 RAG 中的应用:
- 存储文档的嵌入向量,以便于快速检索。
- 根据查询向量,快速检索与之最相似的文档向量,从而找到相关文档。
生成模型 (Generation Models)
- 作用: 根据输入的文本或信息,生成新的文本内容,例如答案、摘要、翻译、代码等。
- 工作原理: 生成模型通常基于深度学习技术,例如循环神经网络 (RNN) 或 Transformer。它们通过学习大量的文本数据,掌握语言的语法和语义规则,从而能够生成流畅、连贯的文本。
- 在 RAG 中的应用:
- 根据检索到的相关文档和查询,生成最终的答案或文本内容。
其他技术
文本预处理 (Text Preprocessing)
- 作用: 对文本进行清洗、分词、去除停用词等操作,以便于后续的嵌入和检索。
- 常见操作:
- 分词 (Tokenization): 将文本分割成单词或子词单元。
- 去除停用词 (Stop Word Removal): 去除一些常见的、对语义贡献较小的词语,例如 "the", "a", "is" 等。
- 词干提取 (Stemming) / 词形还原 (Lemmatization): 将单词的不同形态转换为其基本形式,例如 "running" -> "run"。
- 大小写转换 (Lowercasing): 将所有字母转换为小写。
- 在 RAG 中的应用:
- 对文档和查询进行预处理,提高嵌入和检索的效率和准确性。
相似度度量 (Similarity Metrics)
- 作用: 用于计算查询向量和文档向量之间的相似度,从而判断文档与查询的相关程度。
- 常见度量方法:
- 余弦相似度 (Cosine Similarity): 计算两个向量之间夹角的余弦值,值越大表示相似度越高。
- 点积 (Dot Product): 计算两个向量的点积,值越大表示相似度越高。
- 欧氏距离 (Euclidean Distance): 计算两个向量之间的欧氏距离,值越小表示相似度越高。
- 在 RAG 中的应用:
- 用于检索与查询最相关的文档。
重排序 (Reranking)
- 作用: 对检索到的文档进行重新排序,以进一步提高检索结果的准确性。
- 常见方法:
- 交叉编码器 (Cross-Encoder): 将查询和文档一起输入到一个编码器模型中,计算它们之间的相关性得分,用于排序。
- 在 RAG 中的应用:
- 对初始检索结果进行精细化排序,将最相关的文档排在前面。
技术选型
选择:
- Embedding Models:nomic-embed-text
- Vector Databases:chromadb
- Generation Models: deepseek-r1:1.5b
一、系统概述
我们构建的本地私有库问答系统主要分为以下几个部分:
-
数据存储:使用
chromadb
存储用户数据的向量表示,以便进行高效的相似度查询。 -
数据嵌入:借助
nomic-embed-text
提供的嵌入功能,将用户数据转换为向量形式,方便chromadb
进行存储和检索。 -
查询处理:当用户提出问题时,利用
nomic-embed-text
将问题嵌入为向量,并在chromadb
中搜索相关的用户数据。 -
结果生成:结合检索到的相关数据和用户问题,使用
deepseek-r1:1.5b
生成最终的回答。
嵌入模型(Embedding Models)
- nomic-embed-text: 这个模型用于生成文本的嵌入向量。嵌入向量是一种能够表示文本语义信息的数学表达形式,它允许我们在向量空间中计算文本之间的相似度。
向量数据库(Vector Databases)
- chromadb: ChromaDB 是一个高效的向量数据库,用于存储、索引以及查询嵌入向量。它允许快速检索与给定查询最相似的数据点,非常适合基于内容的搜索任务。
生成模型(Generation Models)
- deepseek-r1:1.5b: 这是一个语言生成模型,可以基于给定的提示(prompt)生成自然语言文本。这类模型通常用于文本生成、问答系统、摘要等需要理解和生成自然语言的任务。
二、环境搭建
在开始构建系统之前,需要确保安装了以下依赖库:
bash复制
pip install ollama chromadb
同时,确保 Ollama 服务正在运行,可以通过以下命令启动:
bash复制
ollama run
ollama拉取deepseek及nomic-embed-text就不在赘述,官网有相关命令;
这是我本地模型:
C:\Users\skyvis>ollama list
NAME ID SIZE MODIFIED
nomic-embed-text:latest 0a109f422b47 274 MB 2 hours ago
deepseek-r1:1.5b a42b25d8c10a 1.1 GB 6 days ago
三、代码实现
以下是实现用户数据查询系统的完整代码:
1. 初始化 ChromaDB 客户端
Python复制
import ollama
import chromadb
from chromadb.utils.embedding_functions import OllamaEmbeddingFunction
chroma_client = chromadb.PersistentClient(path="./chroma_db") # 使用持久化存储
2. 定义嵌入模型
Python复制
embedding_function = OllamaEmbeddingFunction(
model_name="nomic-embed-text:latest",
url="http://localhost:11434/api/embeddings" # Ollama 的嵌入 API 地址
)
3. 创建或获取集合
Python复制
try:
collection = chroma_client.create_collection(
name="user_data",
embedding_function=embedding_function
)
print("集合已创建")
except chromadb.errors.UniqueConstraintError as e:
print("集合已存在,直接获取")
collection = chroma_client.get_collection(
name="user_data",
embedding_function=embedding_function
)
4. 添加用户数据
假设我们有以下用户数据:
Python复制
data = """
姓名 年龄 手机号 地址
张三 25 13800138000 北京市朝阳区望京SOHO
李四 30 13900139000 上海市浦东新区陆家嘴
王五 28 13700137000 广州市天河区珠江新城
"""
将数据添加到 ChromaDB 中:
Python复制
documents = [line for line in data.split('\n')[1:] if line.strip() != ''] # 去掉空行和表头
for idx, doc in enumerate(documents):
collection.add(
documents=[doc],
ids=[str(idx)]
)
5. 查询用户数据
定义问题并进行查询:
Python复制
query = "查询李四的数据"
query_embedding = embedding_function([query]) # 使用列表作为输入
results = collection.query(
query_embeddings=query_embedding,
n_results=3
)
6. 生成回答
Python复制
related_documents = results['documents'][0]
if not related_documents:
answer = "没有找到相关数据"
else:
context = "\n".join(related_documents)
prompt = f"根据以下上下文:\n{context}\n回答:{query}"
response = ollama.generate(model="deepseek-r1:1.5b", prompt=prompt)
answer = response['response']
print(answer)
四、运行结果
假设运行上述代码,查询“李四的数据”时,输出结果如下:
复制
姓名:李四
年龄:30 岁
手机号:13900139000
地址:上海市浦东新区陆家嘴
五、完整代码:
import ollama
import chromadb
from chromadb.utils.embedding_functions import OllamaEmbeddingFunction
# 自定义文本内容
data = """
姓名 年龄 手机号 地址
张三 25 13800138000 北京市朝阳区望京SOHO
李四 30 13900139000 上海市浦东新区陆家嘴
王五 28 13700137000 广州市天河区珠江新城
"""
# 初始化 ChromaDB 客户端
chroma_client = chromadb.PersistentClient(path="./chroma_db") # 使用持久化存储
# 定义嵌入模型
embedding_function = OllamaEmbeddingFunction(
model_name="nomic-embed-text:latest",
url="http://localhost:11434/api/embeddings"
)
# 创建或获取集合
try:
collection = chroma_client.create_collection(
name="user_data",
embedding_function=embedding_function
)
print("集合已创建")
except chromadb.errors.UniqueConstraintError as e:
print("集合已存在,直接获取")
collection = chroma_client.get_collection(
name="user_data",
embedding_function=embedding_function
)
# 将自定义文本存储到向量数据库
documents = [line for line in data.split('\n')[1:] if line.strip() != ''] # 去掉空行和表头
for idx, doc in enumerate(documents):
collection.add(
documents=[doc],
ids=[str(idx)]
)
# 定义问题
query = "查询李四的数据"
# 将问题向量化并查询数据库
query_embedding = embedding_function([query]) # 使用列表作为输入
results = collection.query(
query_embeddings=query_embedding,
n_results=3
)
# 获取最相关的文档
related_documents = results['documents'][0]
# 如果没有找到相关文档,返回默认信息
if not related_documents:
answer = "没有找到相关数据"
else:
# 将相关文档作为上下文提供给模型
context = "\n".join(related_documents)
prompt = f"根据以下上下文:\n{context}\n回答:{query}"
response = ollama.generate(model="deepseek-r1:1.5b", prompt=prompt)
answer = response['response']
# 输出结果
print(answer)
运行结果:
<think>
嗯,好的,我现在需要回答用户的问题。用户说:“根据以下上下文:李四 30 岁,13900139000,上海市浦东新区陆家嘴,王五 28 岁,13700137000,广州市天河区珠江新城。姓名 年龄 数号 地址”。然后用户说“回答:查询李四的数据”
。
首先,我需要理解用户的上下文。看起来用户已经提供了三个人的信息:李四和王五,还有他们的手机号、年龄和地址。用户的问题是要“回答”,所以可能是在请求提供李四的具体信息。
接下来,我要确定每个信息的具体内容:
- 李四的年龄是30岁。
- 声调是13900139000。
- 地址在上海市浦东新区陆家嘴。
- 王五的数据与李四类似,但年龄28岁,地址在广州市天河区珠江新城。
现在,用户的问题是“查询李四的数据”。这意味着他们希望得到李四的详细信息。根据上下文,数据包括年龄、号码和地址。因此,我需要按照这个顺序整理出数据。
首先列出姓名:李四。
然后是年龄:30岁。
接着号码:13900139000。
最后地址:上海市浦东新区陆家嘴。
接下来,按照用户的指示“回答”,可能需要明确写出这些信息的结构。因为用户之前已经提到了李四和王五的数据,但现在的问题是关于查询李四的数据,所以不需要重复王五的信息。
总结一下,我应该按照以下格式呈现数据:
姓名:李四
年龄:30岁
号码:13900139000
地址:上海市浦东新区陆家嘴
这样用户就能清楚地看到李四的详细信息了。
</think>
根据提供的上下文,以下是查询李四的数据:
姓名:李四
年龄:30 岁
号码:13900139000
地址:上海市浦东新区陆家嘴