从零构建简易RAG系统:核心原理与Python实现指南

11 阅读

引言

近年来,检索增强生成(Retrieval-Augmented Generation, RAG)作为人工智能与大语言模型(LLM)领域的一项关键技术,正迅速改变着AI应用的构建方式。RAG通过结合信息检索与文本生成,使模型能够利用外部知识源,从而显著提升回答的准确性和时效性。无论是问答系统、对话机器人还是内容生成工具,RAG都展现出了巨大的应用潜力。

本文旨在从零开始,带领读者构建一个简易的RAG系统。我们将使用Python和Ollama工具,通过清晰的代码示例,深入剖析RAG的核心组件与工作原理。无论你是AI初学者还是经验丰富的开发者,本文都将为你提供实用的指导,帮助你理解并实现自己的RAG应用。

RAG的基本概念

传统聊天机器人的局限

设想一个简单的聊天机器人,它基于预训练的语言模型进行回答。当用户询问“我妈妈的名字是什么?”时,模型由于缺乏外部知识,往往无法给出准确答案。这是因为模型的知识仅限于训练数据,无法获取用户个性化的实时信息。

RAG的引入

RAG通过引入外部知识检索机制,有效解决了上述问题。其核心思想是:在生成回答之前,先从外部知识库中检索与用户查询相关的信息,然后将这些信息作为上下文提供给语言模型,从而生成更准确、更相关的回答。

一个典型的RAG系统包含两个关键组件:

  • 检索模型:负责从外部知识源(如数据库、搜索引擎)中获取相关信息。
  • 语言模型:基于检索到的知识生成最终回答。

RAG的实现方式多种多样,包括Graph RAG、Hybrid RAG、Hierarchical RAG等,我们将在后文简要介绍。

简易RAG系统设计

为了便于理解,我们将构建一个简易的RAG系统,其核心组件包括:

  1. 嵌入模型:将文本转换为向量表示,用于语义相似度计算。
  2. 向量数据库:存储知识块及其对应的嵌入向量,支持高效检索。
  3. 聊天机器人:基于检索到的知识生成回答。

索引阶段

索引阶段是RAG系统的第一步。我们将数据集(或文档)切分为较小的块(chunk),并为每个块计算嵌入向量。这些向量将用于后续的相似度检索。

块的大小可根据应用场景灵活调整。例如,在文档检索中,块可以是段落或句子;在对话系统中,块可以是对话轮次。

索引完成后,每个块及其嵌入向量被存储在向量数据库中。以下是一个示例:

块内容 嵌入向量
Italy and France produce over 40% of all wine in the world. [0.1, 0.04, -0.34, 0.21, ...]
The Taj Mahal in India is made entirely out of marble. [-0.12, 0.03, 0.9, -0.1, ...]
90% of the world's fresh water is in Antarctica. [-0.02, 0.6, -0.54, 0.03, ...]
... ...

这些向量可用于根据查询向量检索最相关的块。类似于SQL的WHERE子句,但基于向量相似度而非精确匹配。

检索阶段

当用户输入查询时,系统首先计算查询的嵌入向量,然后与数据库中的向量进行相似度比较,返回最相关的Top N个块。这些块将作为上下文提供给语言模型。

相似度计算常用余弦相似度,公式如下:

余弦相似度 = (A·B) / (||A|| * ||B||)

其中A和B分别为两个向量。余弦相似度值越接近1,表示两个向量越相似。

代码实现

环境准备

我们将使用Ollama来运行模型,它支持在本地运行Hugging Face上的模型,无需云服务。首先,安装Ollama并下载所需模型:

ollama pull hf.co/CompendiumLabs/bge-base-en-v1.5-gguf
ollama pull hf.co/bartowski/Llama-3.2-1B-Instruct-GGUF

同时,安装Python的Ollama库:

pip install ollama

加载数据集

我们使用一个简单的猫事实数据集,每行一个事实,作为知识块。

dataset = []
with open('cat-facts.txt', 'r') as file:
    dataset = file.readlines()
    print(f'Loaded {len(dataset)} entries')

实现向量数据库

我们使用Ollama的嵌入模型将每个块转换为向量,并存储在列表中。

import ollama

EMBEDDING_MODEL = 'hf.co/CompendiumLabs/bge-base-en-v1.5-gguf'
LANGUAGE_MODEL = 'hf.co/bartowski/Llama-3.2-1B-Instruct-GGUF'

VECTOR_DB = []

def add_chunk_to_database(chunk):
    embedding = ollama.embed(model=EMBEDDING_MODEL, input=chunk)['embeddings'][0]
    VECTOR_DB.append((chunk, embedding))

将数据集中的每个块添加到数据库:

for i, chunk in enumerate(dataset):
    add_chunk_to_database(chunk)
    print(f'Added chunk {i+1}/{len(dataset)} to the database')

实现检索函数

首先,定义余弦相似度计算函数:

def cosine_similarity(a, b):
    dot_product = sum([x * y for x, y in zip(a, b)])
    norm_a = sum([x ** 2 for x in a]) ** 0.5
    norm_b = sum([x ** 2 for x in b]) ** 0.5
    return dot_product / (norm_a * norm_b)

余弦相似度(Cosine Similarity)的数学公式示

然后,实现检索函数,返回Top N个最相关的块:

def retrieve(query, top_n=3):
    query_embedding = ollama.embed(model=EMBEDDING_MODEL, input=query)['embeddings'][0]
    similarities = []
    for chunk, embedding in VECTOR_DB:
        similarity = cosine_similarity(query_embedding, embedding)
        similarities.append((chunk, similarity))
    similarities.sort(key=lambda x: x[1], reverse=True)
    return similarities[:top_n]

生成阶段

在生成阶段,我们将检索到的知识块作为上下文,构造提示词,并调用语言模型生成回答。

input_query = input('Ask me a question: ')
retrieved_knowledge = retrieve(input_query)

print('Retrieved knowledge:')
for chunk, similarity in retrieved_knowledge:
    print(f' - (similarity: {similarity:.2f}) {chunk}')

instruction_prompt = f'''You are a helpful chatbot.
Use only the following pieces of context to answer the question. Don't make up any new information:
{'\n'.join([f' - {chunk}' for chunk, similarity in retrieved_knowledge])}
'''

stream = ollama.chat(
    model=LANGUAGE_MODEL,
    messages=[
        {'role': 'system', 'content': instruction_prompt},
        {'role': 'user', 'content': input_query},
    ],
    stream=True,
)

![cover](https://cdn-gcs.ngxson.com/nuiblog2/2024/10/1730295080894_c8e71c38.jpg)

print('Chatbot response:')
for chunk in stream:
    print(chunk['message']['content'], end='', flush=True)

完整代码与运行

将上述代码整合到一个demo.py文件中,运行python demo.py即可体验。例如,询问“tell me about cat speed”,系统将检索相关事实并生成回答。

改进方向

尽管我们的简易RAG系统已能工作,但仍存在诸多改进空间:

  • 多主题查询:当查询涉及多个主题时,单一检索可能不够准确。可让模型生成多个查询,或使用多查询检索。
  • 重排序:基于余弦相似度的Top N结果可能不够精准,可引入重排序模型(如Cross-Encoder)对结果进行二次排序。
  • 向量数据库扩展:内存存储不适合大规模数据,可改用Qdrant、Pinecone等专业向量数据库。
  • 智能分块:简单的按行分块可能不理想,可根据语义或结构进行更精细的分块。
  • 更大模型:1B参数的语言模型能力有限,可尝试更大的模型以提升生成质量。

其他类型的RAG

RAG的实现方式多种多样,以下是一些常见类型:

  • Graph RAG:将知识表示为图结构,节点为实体,边为关系,模型可遍历图进行检索。
  • Hybrid RAG:结合知识图谱和向量数据库,提升问答系统的性能。
  • Modular RAG:采用模块化设计,支持路由、调度和融合机制,实现更灵活的流程。

结语

通过本文的实践,我们成功构建了一个简易的RAG系统,掌握了嵌入、检索和生成的核心流程。RAG技术为语言模型注入了外部知识,使其回答更加准确和可靠。未来,随着向量数据库、重排序模型等技术的不断进步,RAG将在更多场景中发挥重要作用。希望本文能为你进一步探索RAG提供坚实的基础。

参考资料