chromadb + Ollama 快速实现RAG应用

RAG简介

RAG (Retrieval Augmented Generation) 是一种将信息检索文本生成相结合的技术,它正在成为工作和学习中的强大助理。

工作原理:

  1. 检索 (Retrieval): 当你提出一个问题或请求时,RAG 首先会从大量的外部知识库(例如维基百科、公司文档、代码库等)中检索相关的文档或片段。
  2. 增强 (Augmentation): 检索到的信息会被用来增强语言模型的上下文,使其能够更好地理解你的需求。
  3. 生成 (Generation): 最后,语言模型利用检索到的信息和自身的知识,生成更准确、更全面的答案、总结、翻译、代码等。

优势:

  • 更准确的答案: RAG 可以访问外部知识库,从而提供比仅依赖自身参数的语言模型更准确、更可靠的答案。
  • 更全面的信息: RAG 可以整合多个来源的信息,提供更全面的视角和更深入的理解。
  • 更强的适应性: RAG 可以根据不同的任务和领域选择不同的知识库,从而更好地适应各种应用场景。
  • 减少幻觉: 通过 grounding 生成的文本到检索到的信息,RAG 可以减少语言模型产生幻觉(即编造事实)的可能性。

应用场景:

  • 问答系统: 提供更准确、更全面的答案,例如客服机器人、知识库搜索。
  • 文本摘要: 生成更准确、更简洁的摘要,例如新闻摘要、论文摘要。
  • 代码生成: 根据自然语言描述生成代码,例如代码补全、代码生成工具。
  • 翻译: 提供更准确、更流畅的翻译,例如机器翻译系统。
  • 教育: 提供个性化的学习资源和辅导,例如智能辅导系统、学习助手。

RAG 应用流程

完整的 RAG 应用流程主要包含两个阶段:

数据准备阶段

graph LR A[数据提取] --> B(分块 Chunking) B --> C(向量化 Embedding) C --> D(数据入库)
  1. 数据提取: 从各种数据源(例如网页、文档、数据库等)提取需要用于 RAG 系统的文本数据。
  2. 分块: 将提取的文本数据分割成更小的块(例如段落、句子等),以便于后续的向量化和检索。
  3. 向量化: 使用嵌入模型将每个文本块转换为向量表示,捕捉其语义信息。
  4. 数据入库: 将文本块的向量表示存储到向量数据库中,以便于快速检索。

检索生成阶段

graph LR 1[问题向量化] --> 2(根据问题查询匹配数据) 2 --> 3(获取索引数据) 3 --> 4(将数据注入 Prompt) 4 --> 5(LLM 生成答案)
  1. 问题向量化: 将用户提出的问题转换为向量表示。
  2. 根据问题查询匹配数据: 使用问题向量在向量数据库中进行相似性搜索,找到与问题最相关的文本块。
  3. 获取索引数据: 获取与问题相关的文本块的索引信息,例如文本块的来源、位置等。
  4. 将数据注入 Prompt: 将检索到的相关文本块作为上下文信息,注入到 Prompt 中,提供给 LLM。
  5. LLM 生成答案: LLM 根据 Prompt 中的问题和上下文信息,生成最终的答案。

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 中的应用:
    • 对初始检索结果进行精细化排序,将最相关的文档排在前面。

技术选型

下面我们通过两个例子来讲解RAG应用的开发,我们选择:

  • Embedding Models:chromadb 内置的 all-MiniLM-L6-v2 或者 Ollama + mxbai-embed-large
  • Vector Databases:chromadb
  • Generation Models: Ollama + qwen2.5:7b

这样的选择主要是因为安装的软件较少,对机器配置要求低。极简模式仅需安装chromadb(使用API和生成模型对话),至多也只需安装chromadb + Ollama即可。

chromadb + Ollama 实现RAG应用

Chroma的目标是帮助用户更加便捷地构建大模型应用,更加轻松的将知识(knowledge)、事实(facts)和技能(skills)等我们现实世界中的文档整合进大模型中。

Chroma提供的工具:

  • 存储文档数据和它们的元数据:store embeddings and their metadata
  • 嵌入:embed documents and queries
  • 搜索: search embeddings

🧪 Usage Guide | Chroma Docs (trychroma.com)

Chroma目前提供了Python和Javascript的SDK,建议使用Python开发。

安装环境

Ollama安装很简单,官网Ollama下载后直接安装就行了。

当然我们也可以直接选取其它大模型的API,而不使用Ollama.

sh 复制代码
# 如不使用ollama可以只安装chromadb
pip install ollama chromadb

chromadb内置了all-MiniLM-L6-v2作为嵌入模型,首次运行会下载。默认存放在C:\Users\{用户名}\.cache\chroma\onnx_models\

开发过程

生成embeddings

python 复制代码
import ollama
import chromadb

# 文档定义
documents = [
    "咖啡树是一种茜草科的常绿灌木或小乔木,原产于非洲的埃塞俄比亚和苏丹",
    "咖啡树的果实被称为咖啡樱桃,成熟时会变成鲜红色或紫色,每个果实通常包含两颗咖啡豆",
    "咖啡豆是咖啡树果实内的种子,经过烘焙后可以研磨成粉末冲泡咖啡",
    "阿拉比卡咖啡和罗布斯塔咖啡是两种主要的咖啡品种,阿拉比卡咖啡口感更佳,而罗布斯塔咖啡咖啡因含量更高",
    "咖啡种植需要温暖的气候和充足的降雨,咖啡树通常生长在海拔1000米到2000米的山区",
    "咖啡是世界上最受欢迎的饮料之一,具有提神醒脑的作用,并且富含抗氧化剂",
]

client = chromadb.Client()
collection = client.create_collection(name="docs")

# 将每个文档存储在向量嵌入数据库中
for i, d in enumerate(documents):
    collection.upsert(documents=d, ids=[str(i)])

在添加documents的时候,我们也可以补充metadatas为后续提供更多可用数据。例如:

python 复制代码
collection.add(
    embeddings=[[1.1, 2.3, 3.2], [4.5, 6.9, 4.4], [1.1, 2.3, 3.2], ...],
    metadatas=[{"chapter": "3", "verse": "16"}, {"chapter": "3", "verse": "5"}, {"chapter": "29", "verse": "11"}, ...],
    ids=["id1", "id2", "id3", ...]
)

检索prompt的相关信息

python 复制代码
# 一个示例提示
prompt = "咖啡树的果实被称为什么?"

results = collection.query(
    query_texts=[prompt],
    n_results=3,
)
print(results)

这里返回的results格式是:

json 复制代码
{'ids': [['1', '2', '5']], 'distances': [[0.7086873054504395, 0.7665070295333862, 1.0334540605545044]], 'metadatas': [[None, None, None]], 'embeddings': None, 'documents': [['咖啡树的果实被称为咖啡樱桃,成熟时会变成鲜红色或紫色,每个果实通常包含两颗咖啡豆', '咖啡豆是咖啡树果实内的种子,经过烘焙后可以 研磨成粉末冲泡咖啡', '咖啡是世界上最受欢迎的饮料之一,具有提神醒脑的作用,并且富含抗氧化剂']], 'uris': None, 'data': None, 'included': ['metadatas', 'documents', 'distances']}

其中distances是近似度,我们可以根据这个来判断相关性,在 ChromaDB 中,distances 值越小,代表相似度越高。

现实使用过程中相似性查询不是特别准确,我们一般会和全文索引结果结合起来一起评估。

AI对话

把检索出来的相关性数据作为prompt的一部分提供给AI进行对话,输出数据,我这里用的是qwen2.5:7b,当然,这里不一定使用ollama,你可以使用其它任意大模型API

python 复制代码
data = results["documents"][0][0]
# 这里不一定使用ollama,你可以使用其它任意大模型API,替换以下代码即可
output = ollama.generate(
    model="qwen2.5:7b",
    prompt=f"根据这段文字:{data}。回答这个问题:{prompt}",
)

print(output["response"])

得到的返回内容

sh 复制代码
咖啡树的果实被称为咖啡樱桃。

封装成REST API

chromadb默认内存存储,这里把它改成持久化存储

sh 复制代码
# 初始化 ChromaDB 客户端和集合
client = chromadb.PersistentClient(path="./data/chromadb_data")
collection = client.get_or_create_collection(name="docs")

使用Flask封装

python 复制代码
from flask import Flask, request, jsonify
import ollama
import chromadb

app = Flask(__name__)

# 初始化 ChromaDB 客户端和集合
client = chromadb.PersistentClient(path="./data/chromadb_data")
collection = client.get_or_create_collection(name="docs")


@app.route("/add_document", methods=["POST"])
def add_document():
    # 从请求中获取文档
    data = request.json
    documents = data.get("documents", [])

    # 将每个文档存储在向量嵌入数据库中
    for i, d in enumerate(documents):
        collection.upsert(documents=d, ids=[str(i)])

    return jsonify({"message": "Documents added successfully!"}), 201


@app.route("/query", methods=["POST"])
def query():
    # 从请求中获取提示
    data = request.json
    prompt = data.get("prompt", "")

    # 查询 ChromaDB
    results = collection.query(
        query_texts=[prompt],
        n_results=3,
    )

    # 获取查询结果中的第一个文档
    if results["documents"]:
        data = results["documents"][0][0]
        output = ollama.generate(
            model="qwen2.5:7b",
            prompt=f"根据这段文字:{data}。回答这个问题:{prompt}",
        )
        return jsonify({"response": output["response"]}), 200
    else:
        return jsonify({"message": "No documents found."}), 404


if __name__ == "__main__":
    app.run(debug=True)

使用curl测试add_document

sh 复制代码
curl -X POST http://127.0.0.1:5000/add_document \
-H "Content-Type: application/json;" \
-d '{
    "documents": [
        "咖啡树是一种茜草科的常绿灌木或小乔木,原产于非洲的埃塞俄比亚和苏丹",
        "咖啡树的果实被称为咖啡樱桃,成熟时会变成鲜红色或紫色,每个果实通常包含两颗咖啡豆",
        "咖啡豆是咖啡树果实内的种子,经过烘焙后可以研磨成粉末冲泡咖啡",
        "阿拉比卡咖啡和罗布斯塔咖啡是两种主要的咖啡品种,阿拉比卡咖啡口感更佳,而罗布斯塔咖啡咖啡因含量更高",
        "咖啡种植需要温暖的气候和充足的降雨,咖啡树通常生长在海拔1000米到2000米的山区",
        "咖啡是世界上最受欢迎的饮料之一,具有提神醒脑的作用,并且富含抗氧化剂"
    ]
}'

获得响应

json 复制代码
{
  "message": "Documents added successfully!"
}

使用curl测试query

sh 复制代码
curl -X POST http://127.0.0.1:5000/query \
-H "Content-Type: application/json" \
-d '{
    "prompt": "咖啡树的果实被称为什么?"
}'

获得响应

json 复制代码
{
  "response": "咖啡树的果实被称为咖啡樱桃。"
}

当然,我们可以吧query中的AI对话去掉,仅返回相似性的检索结果,这样将更加灵活

chromadb + Ollama + 嵌入模型实现RAG应用

all-MiniLM-L6-v2模型非常小,而Ollama支持embedding models嵌入模型,可以有更多选择。

Ollama的嵌入模型有三种:mxbai-embed-large、nomic-embed-text 、all-minilm。具体可以查看:Embedding models · Ollama Blog

注意如果上面使用的all-MiniLM-L6-v2时创建的collection,这里使用mxbai-embed-large会不兼容,提示:chromadb.errors.InvalidDimensionException: Embedding dimension 1024 does not match collection dimensionality 384,重建collection即可

安装环境

ollama安装很简单,官网Ollama下载后直接安装就行了。安装完成后下载mxbai-embed-large

sh 复制代码
ollama pull mxbai-embed-large

先用REST API测试一下

sh 复制代码
curl http://localhost:11434/api/embeddings -d '{
  "model": "mxbai-embed-large",
  "prompt": "Ollama支持embedding models嵌入模型,从而支持RAG(retrieval augmented generation)应用,结合文本提示词,检索到文档或相关数据。嵌入模型是通过训练生成向量嵌入,这是一长串数字数组,代表文本序列的关联关系。"
}'
json 复制代码
{"embedding":[-0.10860875248908997,0.3863958716392517,0.11467033624649048,'略......']}

开发过程

生成embeddings

首先安装相关的包

pip install ollama chromadb

创建文件:example.py:这里是使用ollama+mxbai-embed-largedocuments生成向量嵌入并写入向量库chromadb

这里和直接使用chromadb不同,我们是通过mxbai-embed-large进行向量化后,把向量化结果直接写入chromadb

py 复制代码
import ollama
import chromadb

documents = [
    "咖啡树是一种茜草科的常绿灌木或小乔木,原产于非洲的埃塞俄比亚和苏丹",
    "咖啡树的果实被称为咖啡樱桃,成熟时会变成鲜红色或紫色,每个果实通常包含两颗咖啡豆",
    "咖啡豆是咖啡树果实内的种子,经过烘焙后可以研磨成粉末冲泡咖啡",
    "阿拉比卡咖啡和罗布斯塔咖啡是两种主要的咖啡品种,阿拉比卡咖啡口感更佳,而罗布斯塔咖啡咖啡因含量更高",
    "咖啡种植需要温暖的气候和充足的降雨,咖啡树通常生长在海拔1000米到2000米的山区",
    "咖啡是世界上最受欢迎的饮料之一,具有提神醒脑的作用,并且富含抗氧化剂",
]

client = chromadb.Client()
collection = client.create_collection(name="docs")

# 将每个文档存储在向量嵌入数据库中
for i, d in enumerate(documents):
    response = ollama.embeddings(model="mxbai-embed-large", prompt=d)
    embedding = response["embedding"]
    collection.add(ids=[str(i)], embeddings=[embedding], documents=[d])

检索prompt的相关信息

根据用户输入的prompt,将其向量化,然后在chromadb中查找相关性

这里和直接使用chromadb不同,我们是把prompt通过mxbai-embed-large进行向量化后再到chromadb查询

python 复制代码
# 一个示例提示
prompt = "咖啡树的果实被称为什么?"

# 为提示生成一个嵌入并检索最相关的文档。
response = ollama.embeddings(prompt=prompt, model="mxbai-embed-large")
results = collection.query(query_embeddings=[response["embedding"]], n_results=3)
print(results)

这里返回的results格式是:

json 复制代码
{'ids': [['1', '2', '3']], 'distances': [[309.55010986328125, 331.3553466796875, 337.376953125]], 'metadatas': [[None, None, None]], 'embeddings': None, 'documents': [['咖啡树的果实被称为咖啡樱桃,成熟时会变成鲜红色或紫色,每个果实通常包含两颗咖啡豆', '咖啡豆是咖啡树果实内的种子,经过烘焙后可以研磨成 粉末冲泡咖啡', '阿拉比卡咖啡和罗布斯塔咖啡是两种主要的咖啡品种,阿拉比卡咖啡口感更佳,而罗布斯塔咖啡咖啡因含量更高']], 'uris': None, 'data': None, 'included': ['metadatas', 'documents', 'distances']}

AI对话

把检索出来的相关性数据作为prompt的一部分提供给AI进行对话,输出数据,我这里用的是qwen2.5:7b,当然,这里不一定使用ollama,你可以使用其它任意大模型接口

python 复制代码
data = results["documents"][0][0]
# 这里不一定使用ollama,你可以使用其它任意大模型API,替换以下代码即可
output = ollama.generate(
    model="qwen2.5:7b",
    prompt=f"根据这段文字:{data}。回答这个问题:{prompt}",
)

print(output["response"])

得到的返回内容

咖啡树的果实被称为咖啡樱桃。
相关推荐
Icomi_1 小时前
【神经网络】0.深度学习基础:解锁深度学习,重塑未来的智能新引擎
c语言·c++·人工智能·python·深度学习·神经网络
半问1 小时前
广告营销,会被AI重构吗?
人工智能·重构
movee1 小时前
一台低配云主机也能轻松愉快地玩RDMA
linux·人工智能·后端
张琪杭1 小时前
机器学习-随机森林解析
人工智能·随机森林·机器学习
訾博ZiBo1 小时前
AI日报 - 2025年3月11日
人工智能
刘大猫262 小时前
一、MyBatis简介:MyBatis历史、MyBatis特性、和其它持久化层技术对比、Mybatis下载依赖包流程
人工智能·数据挖掘·数据分析
@心都2 小时前
机器学习数学基础:42.AMOS 结构方程模型(SEM)分析的系统流程
人工智能·算法·机器学习
陆鳐LuLu3 小时前
深度学习与数据挖掘题库:401-500题精讲
人工智能·深度学习·数据挖掘
子洋3 小时前
AnythingLLM + SearXNG 实现私有搜索引擎代理
前端·人工智能·后端
紫雾凌寒3 小时前
深度学习|MAE技术全景图:自监督学习的“掩码魔法“如何重塑AI基础
人工智能·深度学习·计算机视觉·自监督学习·vit·视频理解·mae