用 RAG 与向量数据库为 LLM 构建可检索的企业知识库:generative-ai-for-beginners 实战指南
【免费下载链接】generative-ai-for-beginners21 Lessons, Get Started Building with Generative AI项目地址: https://gitcode.com/GitHub_Trending/ge/generative-ai-for-beginners
生成式 AI 应用的价值上限,往往取决于 LLM 能访问多少你的数据。本文以开源仓库 generative-ai-for-beginners 第 15 课 translations/id/15-rag-and-vector-databases/README.md(英文原版位于 15-rag-and-vector-databases/README.md)为骨架,完整讲解检索增强生成(Retrieval Augmented Generation,RAG)与向量数据库的原理与落地:从理解 RAG 的检索-增强生成机制、向量数据库的设计动机,到动手完成"分块 → 向量化 → 建索引 → 检索 → 增强生成"的完整链路。读完本文,你将掌握一套可复用的最小 RAG 应用实现,并能在配套 notebook-rag-vector-databases.ipynb 中逐行验证。
为什么 LLM 需要"接地"(Grounding)
一个由 LLM 驱动的聊天机器人,其能力边界由两部分决定:上下文窗口里的内容,以及预训练数据。GPT-4 这类模型的知识截止于 2021 年 9 月,对截止之后的事件一无所知;同时,用于训练 LLM 的数据几乎不可能包含企业的私有信息——个人笔记、内部产品手册、专有文档都不会出现在公开语料中。
因此,当你希望机器人能回答"我们的产品手册第 3 章说了什么"这类问题时,就必须把自有数据以某种方式接入模型。这正是 RAG 要解决的问题:它把"检索(Retrieval)"和"生成(Generation)"组合起来,让模型在回答前先查证你的知识库。
RAG 如何工作:四条流水线
RAG 的运作可以拆解为四个阶段:
- 知识库(Knowledge base):在检索发生之前,文档需要先被摄入(ingest)并预处理——通常把大文档切分成小片段(chunk),将每个片段转换为文本嵌入(text embedding),再存入数据库。
- 用户查询(User query):用户提出一个问题。
- 检索(Retrieval):当用户提问时,嵌入模型(embedding model)从知识库中取回与问题相关的信息,作为附加上下文注入 prompt。
- 增强生成(Augmented generation):LLM 基于检索到的数据增强自己的回答。这样生成的响应不再只依赖预训练知识,而是融合了附加上下文中的相关信息。
从实现角度看,RAG 的架构由两部分 Transformer 组成:编码器(encoder)与解码器(decoder)。用户提问时,输入文本先被"编码"为捕获词义的向量;这些向量再被"解码"到我们的文档索引中,基于用户查询生成新文本。LLM 正是借助编码器-解码器模型完成这一输出的。
根据提出该方案的论文《Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks》(arxiv 2005.11401),RAG 有两种实现路径:
- _RAG-Sequence:使用检索到的文档直接预测用户查询的最佳回答;
- RAG-Token:先用文档生成下一个 token,再据此检索并回答用户的查询。
为什么要用 RAG
- 信息丰富(Information richness):通过访问内部知识库,回答保持最新、紧跟现状,在特定领域任务上的表现更好;
- 减少幻觉(Reduced fabrication):利用知识库中可验证的数据为查询提供上下文,显著降低编造内容的概率;
- 成本可控(Cost effective):相比对 LLM 做微调(fine-tuning),RAG 在多数场景下更经济。
课程场景:用神经网络笔记武装教育聊天机器人
本课构建的应用场景是:向教育类初创项目的聊天机器人注入我们自己的课程笔记,让学习者能围绕不同学科获得更准确的回答,并基于笔记生成练习测验(quiz)、复习闪卡(flash card)和简明概览。方案使用的组件包括:
- Azure OpenAI:用于创建聊天机器人的 LLM;
- AI for Beginners 的"神经网络"课程笔记:将被接地(ground)到 LLM 的数据;
- Azure AI Search 与 Azure Cosmos DB:用于存储数据并建立搜索索引的向量数据库。
仓库在 15-rag-and-vector-databases/data/ 目录下真实提供了三份知识库文档:frameworks.md(神经网络框架与过拟合)、own_framework.md(多层感知机与反向传播)、perceptron.md(感知机历史与训练)。notebook 正是以这三份文件作为知识库的原始素材。
创建知识库:向量数据库
向量数据库(vector database)与传统数据库不同,是专门用于存储、管理和搜索嵌入向量的数据库,保存的是文档的数值化表示。把数据拆解为数值嵌入,让 AI 系统更容易理解和处理这些数据。
为什么要把嵌入存进向量数据库?因为 LLM 对输入 token 数量有硬性限制——你无法把整篇文档的嵌入一次性传给 LLM,必须将其切分成小块;当用户提问时,与问题最相似的嵌入会连同 prompt 一起返回。分块(chunking)同时也降低了流过 LLM 的 token 数量,从而节省成本。
常见的向量数据库包括 Azure Cosmos DB、Clarifai、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>创建完成后,导航到 Data Explorer 新建数据库与容器。notebook 中对应的 Python 侧初始化代码如下(依赖azure-cosmos库,通过pip install azure-cosmos安装):
from azure.cosmos import CosmosClient # Initialize Cosmos Client url = os.getenv('COSMOS_DB_ENDPOINT') key = os.getenv('COSMOS_DB_KEY') client = CosmosClient(url, credential=key) # Select database database_name = 'rag-cosmos-db' database = client.get_database_client(database_name) # Select container container_name = 'data' container = database.get_container_client(container_name)可见端点与密钥均通过环境变量注入,数据库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 the last chunk didn't reach the minimum length, add it anyway if current_chunk: chunks.append(' '.join(current_chunk)) return chunks在 notebook 的完整流水线里,三份 Markdown 文档首先被读入一个path/text两列的 DataFrame,随后对每个文档应用split_text(text, 400, 300)(即片段长度落在 300~400 字符之间),最后用explode('chunks')把每个片断展开成独立行,得到flattened_df:
splitted_df = df.copy() splitted_df['chunks'] = splitted_df['text'].apply(lambda x: split_text(x, 400, 300)) flattened_df = splitted_df.explode('chunks')嵌入模型选择与调用
分块完成后,即可用不同的嵌入模型对文本做向量化。可选模型包括:word2vec、OpenAI 的ada-002(即text-embedding-ada-002)、Azure Computer Vision 等。模型的选择取决于:使用的语言、被编码内容的类型(文本/图像/音频)、模型可编码的输入规模,以及嵌入输出的长度。
notebook 中定义了统一的嵌入封装函数,并配置了 Azure OpenAI(Microsoft Foundry)v1 端点的客户端:
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): # Create embeddings for each document chunk using your embeddings deployment model = model or embeddings_deployment embeddings = client.embeddings.create(input=text, model=model).data[0].embedding return embeddings对create_embeddings("cat")的调用会返回一串数百维的浮点数向量——"cat" 这个词被编码为一个数值向量,这个向量即语义搜索的最小单元。随后为flattened_df中每个 chunk 生成嵌入,并存入embeddings列:
embeddings = [] for chunk in flattened_df['chunks']: embeddings.append(create_embeddings(chunk)) flattened_df['embeddings'] = embeddings检索与向量搜索
用户提问时,检索器(retriever)先用查询编码器(query encoder)把问题转成向量,然后在文档搜索索引中寻找与该输入相关的向量;完成后再把输入向量与文档向量转换回文本,一并送入 LLM。
三种搜索方式
- 关键词搜索(Keyword search):用于纯文本检索;
- 向量搜索(Vector search):用嵌入模型把文档从文本转为向量表示,实现基于词义的语义搜索——返回向量表示与用户问题最接近的文档;
- 混合搜索(Hybrid search):关键词与向量搜索的组合。
检索的挑战在于:当数据库中没有与查询相似的响应时,系统只能返回它能拿到的最佳信息。应对策略包括:为相关性设定最大距离阈值,或采用结合关键词与向量检索的混合搜索。本课采用混合搜索思路,数据统一存放在包含chunks与embeddings两列的 DataFrame 中。
向量相似度度量
检索器在知识库中寻找彼此距离最近(最近邻)的嵌入,因为相近即语义相似。用户发起查询时,查询先被嵌入,再与相似嵌入做匹配。最常用的相似度度量是余弦相似度(cosine similarity),基于两个向量之间的夹角。此外还有两个备选:
- 欧氏距离(Euclidean distance):向量端点之间的直线距离;
- 点积(dot product):两个向量对应元素乘积之和。
构建搜索索引
在执行检索前,需要为知识库构建搜索索引。索引存放全部嵌入,即使在很大的数据库中也能快速取回最相似的片段。notebook 用 scikit-learn 的NearestNeighbors在本地建立索引:
from sklearn.neighbors import NearestNeighbors embeddings = flattened_df['embeddings'].to_list() # Create the search index nbrs = NearestNeighbors(n_neighbors=5, algorithm='ball_tree').fit(embeddings) # To query the index, you can use the kneighbors method distances, indices = nbrs.kneighbors(embeddings)注意参数配置:n_neighbors=5表示每次返回 5 个最近邻,algorithm='ball_tree'指定球树算法用于高维近邻检索。查询结果(indices与distances)被写回 DataFrame 的对应列,方便后续重排序与拼装 prompt。notebook 的实际输出显示,每条 chunk 的distances中第一个值恒为0.0(与自身距离),其余为与其他片段的距离——这正是余弦距离/最近邻度量的直观体现。
重排序(Re-ranking)
查询数据库后,往往需要把结果按相关性从高到低排序。重排序 LLM 利用机器学习提升搜索结果相关性,把最相关的结果排在前面。使用 Azure AI Search 时,语义重排序器(semantic reranker)会自动完成这一工作。基于最近邻的重排序示例如下:
# Find the most similar documents distances, indices = nbrs.kneighbors([query_vector]) index = [] # Print the most similar documents 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")以提问"what is a perceptron?"为例,notebook 的运行结果显示检索命中的片段主要来自data/perceptron.md(感知机定义、Mark-1 历史等)以及data/own_framework.md中关于多层感知机的段落——证明向量检索确实把语义相关的文档排在了前面。
组装完整应用:把 LLM 加进流程
最后一步是把 LLM 接入管线,得到基于我们数据接地(grounded)的回答。完整实现如下:
user_input = "what is a perceptron?" def chatbot(user_input): # Convert the question to a query vector query_vector = create_embeddings(user_input) # Find the most similar documents distances, indices = nbrs.kneighbors([query_vector]) # add documents to query to provide context history = [] for index in indices[0]: history.append(flattened_df['chunks'].iloc[index]) # combine the history and the user input history.append(user_input) # create a message object messages=[ {"role": "system", "content": "You are an AI assistant that helps with AI questions."}, {"role": "user", "content": "\n\n".join(history) } ] # use the Responses API to generate a response 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)整个chatbot函数只做四件事:① 把问题向量化(create_embeddings);② 用索引找最近邻(kneighbors);③ 把命中的 chunk 与用户问题拼接成上下文(history);④ 通过 Responses API 调用chat_deployment指定的模型生成回答,temperature=0.7控制随机性,max_output_tokens=800限制输出长度。notebook 中运行该函数的输出是一段对感知机的准确解释——它融合了知识库中的定义,而不是模型凭预训练知识泛泛而谈。
评估 RAG 应用
评估指标
- 回答质量(Quality):回答听上去是否自然、流畅、像人;
- 数据接地性(Groundedness):回答是否真正来自所提供的文档;
- 相关性(Relevance):回答是否与所提问题匹配、相关;
- 流畅度(Fluency):回答在语法上是否合理。
用 MAP 做定量评测
notebook 还给出一个基于**平均精度均值(Mean Average Precision,MAP)**的定量评估示例:为每个测试查询准备"相关响应"与"不相关响应"两个列表,把聊天机器人生成的响应与全部候选响应比对,构造二值标签与预测分数,再用sklearn.metrics.average_precision_score逐查询计算平均精度,最后对所有查询取均值:
from sklearn.metrics import average_precision_score # Define your test cases test_cases = [ { "query": "What is a perceptron?", "relevant_responses": ["A perceptron is a type of artificial neuron.", "It's a binary classifier used in machine learning."], "irrelevant_responses": ["A perceptron is a type of fruit.", "It's a type of car."] }, # ... 更多测试用例 ] total_average_precision = 0 for test_case in test_cases: query = test_case["query"] relevant_responses = test_case["relevant_responses"] irrelevant_responses = test_case["irrelevant_responses"] response = chatbot(query) all_responses = relevant_responses + irrelevant_responses true_labels = [1] * len(relevant_responses) + [0] * len(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)该示例的运行结果为0.5——这是一个最小演示:候选列表里只有一条"相关/不相关"命中时得到的理想化分数,用于理解评测流程,而非真实生产系统的评估结论。它清晰地展示了"答案是否落在相关响应集合中"这一评测思想,可在此基础上替换为更真实的检索评测方法(如带人工标注的 MRR、nDCG)。
RAG 与向量数据库的典型应用场景
- 问答系统(Q&A):把公司数据接地到聊天应用,供员工提问;
- 推荐系统(Recommendation systems):匹配最相似的值,例如推荐电影、餐厅等;
- 聊天机器人服务(Chatbot services):存储聊天历史,基于用户数据个性化对话;
- 基于向量嵌入的图像搜索(Image search):适用于图像识别与异常检测。
小结与实践建议
本课覆盖了 RAG 的基础领域:从把数据加入应用、用户查询,到最终输出。为了让 RAG 的搭建更简单,可以使用框架如Semantic Kernel、LangChain或AutoGen简化组装。
延续学习的两个作业方向:
- 用你选择的框架为应用构建前端;
- 借助 LangChain 或 Semantic Kernel 等框架,重新实现本应用。
仓库中的配套素材可以让你立刻动手验证:完整可运行的 notebook-rag-vector-databases.ipynb 串联了全部代码单元,三份知识库文档位于 15-rag-and-vector-databases/data/。在 Azure 上完整跑通前,可先用本地索引(scikit-learn 的NearestNeighbors)+ 任意嵌入 API 复刻最小链路,理解每一步的数据形态变化,再平滑迁移到 Azure Cosmos DB 与 Azure AI Search 的生产方案。
【免费下载链接】generative-ai-for-beginners21 Lessons, Get Started Building with Generative AI项目地址: https://gitcode.com/GitHub_Trending/ge/generative-ai-for-beginners
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考