跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客我的书AI学习GitHub 精选镜像AI 生图工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
PythonAI算法

构建个性化 RAG 应用:从零开始搭建 AI 助手

检索增强生成(RAG)是一种结合外部知识库与大语言模型的技术,旨在解决模型知识滞后和幻觉问题。详细阐述了 RAG 的核心原理,包括非结构化数据处理、文本分割、向量嵌入及索引构建。重点讲解了如何实现语义搜索与 BM25 混合搜索策略,并通过 Python 代码演示了从知识库构建到最终答案生成的完整流程。此外,还补充了提示词工程技巧及生产环境下的优化建议,帮助用户从零开始搭建个性化的 AI 助手应用。

Qiny01发布于 2025/2/6更新于 2026/9/970 浏览
构建个性化 RAG 应用:从零开始搭建 AI 助手

什么是 RAG

RAG is an AI framework for retrieving facts from an external knowledge base to ground large language models (LLMs) on the most accurate, up-to-date information and to give users insight into LLMs' generative process. —— from IBM Research.

RAG 是一个人工智能框架,用于从外部知识库中检索事实,使大型语言模型(LLM)基于最准确的最新信息,并让用户深入了解 LLM 的生成过程。

大语言模型训练完后,其内部知识库就已经确定了,所以它无法回答你超过其知识库内容的问题。除非你有能力对其进行微调,否则最简单的方法就是使用 RAG 检索外部知识库。

有人可能会认为,RAG 是不是就是让模型在回答问题前先去指定的外部知识库检索一下知识,然后再回答?这里只对了一半,模型没有那么智能,它不能主动去'检索'知识库,而是需要我们把检索到的知识'喂'给模型,让它结合'知识'和'问题',做出合理解答。

上图展示了没有使用 RAG 和使用 RAG 后的两种工作流程。黑色箭头就是没有使用 RAG 的工作流程。那这里为什么要引入一个嵌入模型呢?别急,我们这里还需要再了解一些基础知识。

非结构化数据和向量搜索

什么是结构化数据?二维表格(行和列)是最常见的结构化数据形式。它具有非常良好的关键字查询功能,只要找到对应的单元格,就能定位到行和列。它就好比传统的 SQL 搜索,能进行精确查询和模糊查询,但不能进行相关性查询。

那什么是非结构化数据呢?答案是任何文本、图片、音频、视频文件等。将这些数据进行多维展开,实现空间上的向量化。任何两个数据向量化后,如果在空间上具有相似性,就可以认为这两个数据相关联。比如一张小孩一个人荡秋千的照片和单词'孤独'可能在空间中非常接近,这样一来,使用关键词'孤独'进行向量搜索,能把这张图片查询出来。这在传统数据库加结构化数据中是很难做到的。

现在你知道什么是向量搜索了吧?向量搜索是一种基于向量空间模型的搜索技术。向量搜索的目标是在向量空间中找到与查询向量最相似的向量,即与查询向量距离最近的文档向量。

向量搜索的最常见算法是'余弦相似度',这个我们不做展开。

到了这里,你一定了解了为什么 RAG 中要引入一个嵌入模型的原因了吧。

构建知识库

知识库可以是任意非结构化数据组成的,我们这里用一个 txt 文本做示例。

文本分割

首先第一步需要将文本分割,这一步很好理解,就是将大文本分割成多个小块。不管是 pdf 还是 word,分割成小块后就可以用于检索了。因此这一步很重要,分割后的文本质量影响检索的准确性。这里给一个通用的算法,根据指定的文本长度进行分割。

def split_document_direct(document, chunk_size):  
    separator = "\n"  
    splits = [s for s in document.split(separator) if s != ""]  
    chunks = []  
    length = 0  
    start = 0  
    for i, split in enumerate(splits):  
        if (length + len(split)) > chunk_size:  
            chunks.append("\n".join(splits[start: i]))  
            start = i  
            length = 0  
        if i == len(splits) - :  
            chunks.append(.join(splits[start: i + ]))  
        length += (split)  
     chunks
1
"\n"
1
len
return

你可以将分割后的文本保存至磁盘或任何你喜欢的数据库。

文本嵌入

接下来将分割后的文本嵌入成向量。我这里使用的模型是 m3e-base,它对中文支持良好。

from sentence_transformers import SentenceTransformer
import torch

embedding_model = SentenceTransformer("path/m3e-base",  
                                      device="cpu",  
                                      local_files_only=True)
embeddings = embedding_model.encode(chunks, convert_to_tensor=True)
构建索引

不用现成向量数据库是为了让大家更深的理解 RAG 的整个原理。

对向量进行索引以提高搜索效率,使用 ANN 算法构建索引。

import faiss
import numpy as np

EMBEDDING_DIMENSION = 768  # 假设维度为 768

def build_faiss_index(embeddings: np.ndarray) -> faiss.IndexFlatIP:
    index = faiss.IndexFlatIP(EMBEDDING_DIMENSION)
    index.add(embeddings)
    return index

# 使用示例
faiss_index = build_faiss_index(embeddings.numpy())

你可以将生成的索引保存至磁盘或任何你喜欢的数据库。

至此知识库构建完毕。

实现 RAG

当知识库就位后,接下来就是提问了。

问题嵌入

非常简单的一步,将用户的提问转换成向量。

user_input = "中印两国的发展模式不同表现在哪些方面?其原因是什么?"
query_embedding = embedding_model.encode(user_input, convert_to_tensor=True)
语义搜索

将嵌入后的数据在索引中搜索,根据语义查找相关度最高的几 (top_k) 条数据。

import torch

top_k = 5
D, I = faiss_index.search(query_embedding.unsqueeze(0).cpu().numpy(), top_k * 2)  
semantic_scores = torch.tensor(D[0])  
semantic_indices = torch.tensor(I[0])

semantic_scores 语义得分,是一个语义相似度得分数组,类似于 [333.7204, 320.6714, 303.9030, 301.7744],得分越高,相似度越高。

semantic_indices 与语义得分相对应,对应的是上面 chunks 的下标,类似于 [1, 3, 2, 0]。

混合搜索

本步骤可选。

语义搜索可以找到相似文本。但为了提高准确度,往往需要结合一种其它的相似度算法来重新计算相似度得分。接下来我们使用 BM25 算法来举例。注意,下面这段代码,需要加到'构建知识库'的步骤中。

import jieba
from rank_bm25 import BM25Okapi
from nltk.corpus import stopwords
import re
from typing import List

nltk.download("stopwords")

def preprocess_text(text: str) -> List[str]:  
    tokens = jieba.lcut(text.lower().replace("\n", ""))  
    stop_words = set(stopwords.words("chinese"))  
    return [token for token in tokens if token.isalnum() and token not in stop_words]

tokenized_corpus = [preprocess_text(chunk) for chunk in chunks]  
bm25 = BM25Okapi(tokenized_corpus)

稍微解释一下,这段代码的核心步骤是预处理文本并构建 BM25 索引:

  • 下载 nltk 库中的停用词数据集,停用词是指在文本处理中需要过滤掉的一些高频但无意义的词语,如'的'、'是'、'在'等。
  • 用 jieba 分词对中文进行分词,并过滤掉停用词,使得剩下的词更具备实际意义。
  • chunks 是上文'文本分割'后的小段文本,对每段文本进行索引构建。

接下来结合 BM25 得分和语义得分来重新计算最终得分,选出排名靠前的文档。

tokenized_query = preprocess_text(user_input)  
bm25_scores = torch.tensor(bm25.get_scores(tokenized_query))
bm25_scores_rescored = bm25_scores[semantic_indices]
semantic_scores = (semantic_scores - semantic_scores.min()) / (semantic_scores.max() - semantic_scores.min() + 1e-8)  
bm25_scores_rescored = (bm25_scores_rescored - bm25_scores_rescored.min()) / (  
            bm25_scores_rescored.max() - bm25_scores_rescored.min() + 1e-8)
combined_scores = 2 / (1 / semantic_scores + 1 / bm25_scores_rescored)  
top_indices = semantic_indices[torch.argsort(combined_scores, descending=True)][:top_k]  
top_scores = combined_scores[torch.argsort(combined_scores, descending=True)][:top_k]
result = [(idx.item(), score.item()) for idx, score in zip(top_indices, top_scores)]
  • 用 jieba 分词对问题进行分词,并过滤掉停用词。
  • 计算 BM25 搜索得分。
  • 使用 semantic_indices 对 BM25 得分进行重排序或筛选,得到与语义得分相关的 BM25 得分 bm25_scores_rescored。
  • 标准化得分:将语义得分和 BM25 得分标准化到 [0, 1] 范围内。
  • 计算调和平均得分:将标准化后的语义得分和 BM25 得分结合起来,计算它们的调和平均数作为最终的组合得分 combined_scores。
  • 排序和选取前 K 个文档

最终的 result 是类似于这样的结果:[(3, 0.7433483727390585), (2, 0.11336547324137035)]。这意味着查询到两条数据,下标分别是 3 和 2,得分分别是 0.7433483727390585 和 0.11336547324137035。

构建提示词

这一步也很简单,假设用户的问题是 question1,查询到的结果是 result1 和 result2。那么构建的提示词如下(仅供参考):

context = "\n\n".join([chunks[idx] for idx, _ in result])
prompt = f"""Answer the question based only on the following context:\n---------------------\n{context}\n---------------------\nQuestion: {user_input}\nAnswer:"""

这里要注意的是不同模型的上下文长度,不要超过它的最大长度。

生成最终回答

有了上下文和提示词后,最后一步是将提示词发送给大语言模型 API 获取答案。

import requests

def call_llm(prompt, api_url, api_key):
    headers = {
        "Authorization": f"Bearer {api_key}",
        "Content-Type": "application/json"
    }
    payload = {
        "model": "your-model-name",
        "messages": [{"role": "user", "content": prompt}],
        "temperature": 0.7
    }
    response = requests.post(api_url, json=payload, headers=headers)
    return response.json()["choices"][0]["message"]["content"]

# 调用示例
final_answer = call_llm(prompt, "https://api.example.com/v1/chat/completions", "YOUR_API_KEY")
print(final_answer)

在实际生产中,建议增加重试机制和超时控制,以应对网络波动或模型服务不可用的情况。

总结与优化建议

本文介绍了如何从零构建一个基础的 RAG 系统。主要涵盖了以下几个关键点:

  1. 数据准备:非结构化数据的清洗与分块策略直接影响检索效果。
  2. 向量化:选择合适的 Embedding 模型至关重要,需考虑领域适配性和语言支持。
  3. 检索策略:纯向量检索可能丢失关键词匹配精度,混合搜索(Hybrid Search)通常表现更好。
  4. 提示工程:合理的 Prompt 结构能有效引导模型利用检索到的上下文,减少幻觉。

进一步优化方向包括:

  • 使用更高级的向量数据库(如 Milvus, Pinecone)替代本地 Faiss 索引以提升扩展性。
  • 引入重排序(Re-rank)模型进一步提升 Top-K 结果的准确性。
  • 针对特定业务场景对 Embedding 模型进行微调。

通过上述步骤,你可以搭建一个能够基于私有知识库回答问题的 AI 助手,有效解决大模型知识滞后和幻觉问题。

目录

  1. 什么是 RAG
  2. 非结构化数据和向量搜索
  3. 构建知识库
  4. 文本分割
  5. 文本嵌入
  6. 构建索引
  7. 使用示例
  8. 实现 RAG
  9. 问题嵌入
  10. 语义搜索
  11. 混合搜索
  12. 构建提示词
  13. 生成最终回答
  14. 调用示例
  15. 总结与优化建议

更多推荐文章

查看全部
  • FLUX.1-dev与Stable Diffusion对比评测:图像质量与生成速度
  • OpenClaw 汉化版部署常见问题排查手册
  • 使用 AI 辅助开发 Java 电商系统核心模块实战
  • 单片机四舍五入算法原理与嵌入式边界处理实践
  • Boltz-2 安装及用法:结构与亲和力预测模型
  • 无人机路径规划核心算法解析与实战对比
  • GitPuk 代码管理工具安装配置与入门实战
  • 谷歌 Gemini 的 6 种免费使用渠道与方法指南
  • 高效邮件发送系统设计与实现:基于Python和SQLAlchemy的实践
  • LlamaFactory v0.9.4 正式发布:LLM 微调框架全面升级
  • Java HashMap 底层原理深度解析
  • Web 前端核心 API 入门:变量声明、DOM 操作与定时器
  • OpenClaw 实战调优:5 步让 AI 助手真正“能干活”
  • ROS1 全局参数:定义、获取与修改实战
  • C 语言指针与数组的深度关联及实战应用
  • Linux lsof 命令常用用法与实战指南
  • PyCharm Copilot 插件 Claude 模型不可用问题修复
  • Java 面向对象入门:类与对象及封装核心
  • Higress MCP Server 插件:REST API 转 AI 工具配置指南
  • Python 实现 SMZDM 数据处理系统:从爬虫到数据分析

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

  • RSA密钥对生成器

    生成新的随机RSA私钥和公钥pem证书。 在线工具,RSA密钥对生成器在线工具,online

  • Mermaid 预览与可视化编辑

    基于 Mermaid.js 实时预览流程图、时序图等图表,支持源码编辑与即时渲染。 在线工具,Mermaid 预览与可视化编辑在线工具,online

  • 随机西班牙地址生成器

    随机生成西班牙地址(支持马德里、加泰罗尼亚、安达卢西亚、瓦伦西亚筛选),支持数量快捷选择、显示全部与下载。 在线工具,随机西班牙地址生成器在线工具,online

  • Gemini 图片去水印

    基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online

  • curl 转代码

    解析常见 curl 参数并生成 fetch、axios、PHP curl 或 Python requests 示例代码。 在线工具,curl 转代码在线工具,online