在 generative-ai-for-beginners 中构建 RAG:检索增强生成与向量数据库实战指南
【免费下载链接】generative-ai-for-beginners21 Lessons, Get Started Building with Generative AI项目地址: https://gitcode.com/GitHub_Trending/ge/generative-ai-for-beginners
本指南以 generative-ai-for-beginners 课程第 15 课的 RAG 与向量数据库章节为主体,结合仓库内完整的 Jupyter Notebook 实现与示例数据,讲解如何将自有数据(个人笔记、课程讲义)接入 LLM,构建一个能回答领域问题的 RAG 聊天机器人。读完本文,你将掌握 RAG 的工作原理、向量数据库与嵌入(embedding)的概念、本地搜索索引与相似度检索的实现,以及如何用 Azure OpenAI、Azure AI Search、Azure Cosmos DB 搭建并评估一套完整的检索增强生成应用。
一、RAG 是什么,为什么需要它
一个由 LLM 驱动的聊天机器人会处理用户提示并生成回复,它被设计成交互式的,可以围绕大量主题与用户对话。然而它的回复被严格限制在"提供的上下文"和"基础训练数据"范围内。例如 GPT-4 的知识截止日期是 2021 年 9 月,这意味着它对该日期之后发生的事件一无所知;此外,用于训练 LLM 的数据不包含机密信息,比如个人笔记或公司的产品手册。
RAG(Retrieval Augmented Generation,检索增强生成)正是为解决这一限制而生:它把"检索"(从你自己的知识库中找到相关内容)与"生成"(LLM 基于检索内容作答)两个步骤组合起来,让模型回答不再只依赖训练时的静态知识。
RAG 的工作流程
RAG 的完整链路可以拆解为四个环节:
- 知识库(Knowledge base):在检索发生之前,文档需要被摄取并预处理,通常把大文档切分成小块(chunk),转换成文本嵌入(text embedding),再存储到数据库中。
- 用户查询(User query):用户提出问题。
- 检索(Retrieval):当用户提问时,嵌入模型(embedding model)从知识库中检索相关信息,为提示词补充更多上下文。
- 增强生成(Augmented generation):LLM 基于检索到的数据增强其回答,让回复不只建立在预训练数据上,还包含新增的相关上下文,最后把答案返回给用户。
RAG 的架构基于 transformer 模型实现,包含两部分:编码器(encoder)和解码器(decoder)。当用户提问时,输入文本被"编码"为捕获词语含义的向量,这些向量被"解码"进我们的文档索引,并基于用户查询生成新文本。LLM 正是使用编码器-解码器模型来产生输出的。
依据论文《Retrieval-Augmented Generation for Knowledge intensive NLP Tasks》,实现 RAG 有两种主流方案:
- RAG-Sequence:使用检索到的文档来预测用户查询的最佳答案;
- RAG-Token:使用文档生成下一个 token,再重新检索以回答用户查询。
为什么使用 RAG
- 信息丰富度:确保文本回复是即时、最新的,通过访问内部知识库提升领域特定任务的性能;
- 减少虚构(fabrication):利用知识库中可验证的数据为用户查询提供上下文,降低模型"编造"答案的概率;
- 成本效益:相比微调(fine-tuning)一个 LLM,RAG 更加经济。
二、课程场景:用自有数据增强 LLM
第 15 课的场景设定在一个教育创业公司中:我们想把自己的笔记加入聊天机器人,让它对各个学科拥有更多信息,学生可以借助这些笔记更好地学习、理解不同主题,并为考试复习做准备。构建该场景需要以下组件:
- Azure OpenAI:用于创建聊天机器人的 LLM;
- AI for Beginners 的神经网络课程笔记:作为给 LLM "接地"(grounding)的数据;
- Azure AI Search 与 Azure Cosmos DB:向量数据库,用于存储数据并创建搜索索引。
用户可以基于笔记创建练习测验(practice quizzes)、复习闪卡(revision flash cards),并把内容总结成简洁的概述。对应上述场景的完整代码在仓库的 notebook-rag-vector-databases.ipynb 中,示例数据位于 data/ 目录下,包含三篇文档:
- frameworks.md:神经网络框架;
- own_framework.md:多层感知机与自建框架;
- perceptron.md:感知机模型与训练。
三、创建知识库:向量数据库与文本嵌入
向量数据库
与传统数据库不同,向量数据库是专门用来存储、管理和搜索嵌入向量的特殊数据库。它存储的是文档的数值表示。把数据拆解为数值嵌入,可以让 AI 系统更容易理解和处理数据。
之所以要把嵌入存储在向量数据库中,是因为LLM 对输入的 token 数量有上限。我们无法把全部嵌入一次性传给 LLM,因此需要把它们切分成块,当用户提问时,只把与问题最相似的嵌入连同提示词一起返回。分块还能降低 LLM 传递的 token 数量,从而降低成本。
常见的向量数据库包括:Azure Cosmos DB、Clarifyai、Pinecone、Chromadb、ScaNN、Qdrant 和 DeepLake。使用 Azure CLI 可以这样创建 Azure Cosmos DB:
az login az group create -n <resource-group-name> -l <location> az cosmosdb create -n <cosmos-db-name> -r <resource-group-name> az cosmosdb list-keys -n <cosmos-db-name> -g <resource-group-name>创建完成后,可以在数据资源管理器中新建数据库和容器。Notebook 中对应使用azure-cosmosPython SDK 初始化客户端(见 notebook-rag-vector-databases.ipynb 中CosmosClient(url, credential=key)的用法),并通过环境变量COSMOS_DB_ENDPOINT、COSMOS_DB_KEY读取连接信息,数据库名rag-cosmos-db、容器名data。
从文本到嵌入:分块策略
在存储数据之前,需要先把它转换为向量嵌入。如果处理的是大文档或长文本,可以按预期的查询方式分块。分块可以在句子级别或段落级别进行。由于块的含义来自其周围的词,我们可以为块补充额外上下文,比如加上文档标题,或在块前后包含部分文本。
def split_text(text, max_length, min_length): words = text.split() chunks = [] current_chunk = [] for word in words: current_chunk.append(word) if len(' '.join(current_chunk)) < max_length and len(' '.join(current_chunk)) > min_length: chunks.append(' '.join(current_chunk)) current_chunk = [] # 如果最后一个块未达到最小长度,仍然加入 if current_chunk: chunks.append(' '.join(current_chunk)) return chunks在 Notebook 中,实际调用为split_text(x, 400, 300),即每个块控制在 300~400 字符之间,然后用splitted_df.explode('chunks')把每行的块列表展开成独立行,形成后续用于检索的flattened_df(列包含path、text、chunks,后续还会追加embeddings、indices、distances列)。
分块完成后,可以用不同的嵌入模型把文本转换为嵌入,例如 word2vec、OpenAI 的 ada-002、Azure Computer Vision 等。选择模型取决于:使用的语言、编码内容的类型(文本/图像/音频)、可编码的输入大小以及嵌入输出的长度。
下图展示了使用 OpenAItext-embedding-ada-002模型对单词 "cat" 生成的嵌入示例:
Notebook 中的实现封装了嵌入函数(见 notebook-rag-vector-databases.ipynb):
from openai import OpenAI endpoint = os.getenv("AZURE_OPENAI_ENDPOINT") client = OpenAI( api_key=os.getenv("AZURE_OPENAI_API_KEY"), base_url=f"{endpoint.rstrip('/')}/openai/v1/", ) embeddings_deployment = os.getenv("AZURE_OPENAI_EMBEDDINGS_DEPLOYMENT") chat_deployment = os.getenv("AZURE_OPENAI_DEPLOYMENT") def create_embeddings(text, model=None): # 使用你的 embeddings 部署为每个文档块创建嵌入 model = model or embeddings_deployment embeddings = client.embeddings.create(input=text, model=model).data[0].embedding return embeddings然后对flattened_df中所有块批量生成嵌入并写回数据框:
embeddings = [] for chunk in flattened_df['chunks']: embeddings.append(create_embeddings(chunk)) flattened_df['embeddings'] = embeddings四、检索与向量搜索
当用户提问时,检索器(retriever)使用查询编码器把问题转换为向量,然后在文档搜索索引中查找与输入相关的向量;完成后,把输入向量与文档向量转换回文本,一并传给 LLM。
检索的三种方式
检索发生在系统从索引中快速找出满足搜索条件的文档时。检索器的目标是把用于提供上下文、让 LLM "接地"到数据的文档找出来。数据库内的搜索方式有:
- 关键词搜索(Keyword search):用于文本搜索;
- 向量搜索(Vector search):使用嵌入模型把文档从文本转换为向量表示,支持基于词语含义的语义搜索(semantic search),检索时查询与用户问题向量表示最接近的文档;
- 混合搜索(Hybrid):关键词与向量搜索的组合。
检索的挑战在于:当数据库中没有与查询相似的答案时,系统只能返回它能拿到的最佳信息。对此可以采取策略,例如设置相关性的最大距离阈值,或使用同时包含关键词与向量搜索的混合搜索。本课采用混合搜索思路,数据存储在 DataFrame 中,列包含分块与嵌入。
向量相似度度量
检索器会在知识库中寻找彼此接近的嵌入(最近的邻居),因为它们是语义相似的文本。用户查询先被嵌入,再与相似的嵌入匹配。衡量向量相似度最常用的指标是余弦相似度(cosine similarity),它基于两个向量之间的夹角。其他备选方案还有:
- 欧几里得距离(Euclidean distance):两个向量端点之间的直线距离;
- 点积(dot product):两个向量对应元素乘积之和。
用 NearestNeighbors 构建本地搜索索引
执行检索前,需要为知识库构建搜索索引。索引存储嵌入,即使数据库很大也能快速返回最相似的块。本地可以用 sklearn 构建:
from sklearn.neighbors import NearestNeighbors embeddings = flattened_df['embeddings'].to_list() # 创建搜索索引 nbrs = NearestNeighbors(n_neighbors=5, algorithm='ball_tree').fit(embeddings) # 查询索引可使用 kneighbors 方法 distances, indices = nbrs.kneighbors(embeddings)Notebook 中还会把indices与distances写回 DataFrame(flattened_df['indices'] = indices.tolist()、flattened_df['distances'] = distances.tolist()),便于后续展示与调试。
重排序(Re-ranking)
查询完数据库后,可能需要按相关性对结果排序。重排序 LLM 利用机器学习改进搜索结果的相关性,把最相关的结果排在最前。使用 Azure AI Search 时,语义重排序器会自动完成重排序。下面是一个基于最近邻的重排序示例:
# 找出最相似的文档 distances, indices = nbrs.kneighbors([query_vector]) index = [] # 打印最相似的文档 for i in range(3): index = indices[0][i] for index in indices[0]: print(flattened_df['chunks'].iloc[index]) print(flattened_df['path'].iloc[index]) print(flattened_df['distances'].iloc[index]) else: print(f"Index {index} not found in DataFrame")在 Notebook 的检索演示中,提问 "what is a perceptron?" 后返回的正是data/perceptron.md中关于感知机二分类模型的内容块,验证了检索链路与数据接地(grounding)的有效性。
五、把一切整合起来:RAG 聊天机器人
最后一步是把 LLM 加入流程,得到基于我们数据的回答。核心实现如下(注意仓库 Notebook 中已采用 OpenAI 的 Responses API 与部署名称):
user_input = "what is a perceptron?" def chatbot(user_input): # 将问题转换为查询向量 query_vector = create_embeddings(user_input) # 找出最相似的文档 distances, indices = nbrs.kneighbors([query_vector]) # 将文档加入查询以提供上下文 history = [] for index in indices[0]: history.append(flattened_df['chunks'].iloc[index]) # 合并历史与用户输入 history.append(user_input) # 创建消息对象 messages=[ {"role": "system", "content": "You are an AI assistant that helps with AI questions."}, {"role": "user", "content": history[-1]} ] # 使用 Responses API 生成回答 response = client.responses.create( model=chat_deployment, temperature=0.7, max_output_tokens=800, input=messages, store=False, ) return response.output_text chatbot(user_input)流程可概括为:问题 → 嵌入为查询向量 → 最近邻检索相似文档块 → 拼接上下文与问题 → 构造 system/user 消息 → 调用 LLM 生成回答。Notebook 中实际运行结果返回了关于感知机(perceptron)的完整解释,说明模型能够基于课程笔记而非仅凭预训练知识作答。
说明:本课英文版 README(15-rag-and-vector-databases/README.md)中保留了基于
chat.completions的旧版写法(model="gpt-4"),而 Notebook 已迁移到 Responses API(client.responses.create,模型使用chat_deployment,例如gpt-4o-mini)。实际运行请以 Notebook 为准,并确认AZURE_OPENAI_ENDPOINT、AZURE_OPENAI_API_KEY、AZURE_OPENAI_EMBEDDINGS_DEPLOYMENT、AZURE_OPENAI_DEPLOYMENT环境变量已正确配置。
六、评估你的 RAG 应用
评估指标
- 回答质量(Quality):回答是否听起来自然、流畅、像人说的话;
- 数据接地(Groundedness):评估回答是否源自提供的文档;
- 相关性(Relevance):评估回答是否与所提问题匹配、相关;
- 流畅度(Fluency):回答在语法上是否有意义。
用 MAP 做定量评估
除了上述定性指标,Notebook 还提供了一个基于平均精确率均值(Mean Average Precision, MAP)的定量评估示例(见 notebook-rag-vector-databases.ipynb 的 "Testing and evaluation" 部分):定义若干测试用例(如 "What is a perceptron?"、"What is machine learning?" 等),每个用例包含相关回答与不相关回答,用sklearn.metrics.average_precision_score计算单查询的平均精确率,再对所有用例取平均:
from sklearn.metrics import average_precision_score total_average_precision = 0 for test_case in test_cases: query = test_case["query"] response = chatbot(query) all_responses = test_case["relevant_responses"] + test_case["irrelevant_responses"] true_labels = [1] * len(test_case["relevant_responses"]) + [0] * len(test_case["irrelevant_responses"]) predicted_scores = [1 if resp == response else 0 for resp in all_responses] average_precision = average_precision_score(true_labels, predicted_scores) total_average_precision += average_precision mean_average_precision = total_average_precision / len(test_cases)七、RAG 与向量数据库的应用场景
RAG 与向量数据库可以改进很多应用,例如:
- 问答系统:把公司数据接地到聊天应用,员工可以用它提问;
- 推荐系统:创建匹配最相似值的系统,例如电影、餐厅推荐等;
- 聊天机器人服务:存储聊天历史,基于用户数据个性化对话;
- 图像搜索:基于向量嵌入的图片搜索,在图像识别与异常检测中很有用。
八、总结与进阶
本课覆盖了 RAG 的基础领域:从把数据加入应用、用户查询到最终输出。若要简化 RAG 的构建,可以使用 Semantic Kernel、LangChain 或 AutoGen 等框架(本仓库第 17 课 AI Agents 对 Agent 框架有进一步介绍)。
完成本课学习后,可以通过以下练习继续深入:
- 用你选择的框架为应用构建前端;
- 使用 LangChain 或 Semantic Kernel 等框架,重建你的 RAG 应用。
完整代码与可运行环境见 notebook-rag-vector-databases.ipynb,数据文件位于 data/ 目录,英文原版讲义见 15-rag-and-vector-databases/README.md。
【免费下载链接】generative-ai-for-beginners21 Lessons, Get Started Building with Generative AI项目地址: https://gitcode.com/GitHub_Trending/ge/generative-ai-for-beginners
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考