搭建检索增强生成RAG系统

当企业拥有大量非结构化的文档(如 PDF、Word、Markdown)时,传统的数据库查询无法有效利用这些信息。用户难以通过简单的提问,从海量文档中快速获得精准答案。PolarDB PostgreSQL通过polar_ai扩展,在数据库内部集成了一站式的检索增强生成(RAG)能力。您无需复杂的外部服务集成,仅通过SQL接口,即可将私有文档数据转化为智能问答知识库,实现对非结构化数据的高效、精准查询,快速构建企业级智能问答应用。

适用范围

开始前,请确保已完成以下准备:

  • 引擎:PostgreSQL 16(内核小版本2.0.16.10.11.0及以上)。

  • 节点:增加GPU规格的AI节点,并设置AI节点的连接数据库账号添加与管理AI节点

    说明
    • 若您在购买集群时已添加AI节点,则可以直接为AI节点设置连接数据库的账号。

    • AI节点的连接数据库账号需具有读写权限,以确保能够顺利读取和写入目标数据库。

  • 访问地址:使用集群地址连接PolarDB集群。

工作原理

PolarDB中实现RAG的核心流程是将外部文档处理后存入数据库,并通过结合向量检索与大语言模型(LLM)的能力,最终生成问题的答案。

image
  1. 数据准备与加载:将您的原始文档加载到数据库的数据表中。

  2. 文档分片(Chunking):调用polar_ai.ai_rag_chunking函数,将长文档切分为更小的、语义完整的文本片段(Chunk)。这有助于后续的精准检索。

  3. 向量化(Embedding):调用polar_ai.ai_rag_text_embedding函数,将每个文本片段转换为高维向量(Vector),并与文本内容一同存储。向量是文本在数学空间中的表示,能够捕捉其语义信息。

  4. 创建向量索引:在存储向量的列上创建HNSW索引,以极大地加速后续的相似性搜索。

  5. 检索:当用户提出问题时,首先将问题本身也向量化,然后在数据库中执行向量相似性搜索,快速召回与问题最相关的若干个文本片段。

  6. 重排序(Rerank):为进一步提升相关性,可以调用polar_ai.ai_rag_rerank函数对初步召回的文本片段进行二次排序,筛选出最关键的信息。

  7. 生成回答:将经过重排序的、最相关的文本片段作为背景知识(Context),连同用户的原始问题一起,提交给大语言模型,由模型基于提供的背景知识生成最终的、精准的自然语言答案。

准备环境

本节将指导您完成RAG功能的全部前置准备工作。

  1. 使用高权限账号登录集群。

  2. 获取节点Token。

    请前往PolarDB控制台,目标集群详情页中数据库节点区域,找到AI节点,并单击查看记录节点Tokenimage

  3. 创建polar_ai扩展并配置节点Token。

    在数据库中执行以下命令,创建功能扩展并设置访问大模型服务所需的密钥。

    重要

    请确认您已为AI节点设置了连接数据库账号

    -- 创建扩展
    CREATE EXTENSION polar_ai;
    
    -- 设置您的密钥
    SELECT polar_ai._ai_nl2sql_alter_token('sk-xxx');
  4. 创建vector扩展。

    -- 提供向量数据类型、向量索引及相似度计算能力
    CREATE EXTENSION vector;
  5. 部署RAG服务:

    AI节点上加载并启动RAG所需的模型和服务。执行以下命令来部署RAG服务。

    SELECT polar_ai.ai_nl2sql_deployModel('RAG');

准备数据(导入、分片与向量化)

本节将以一个PDF文档为例,演示如何将其导入数据库,并高效地完成分片和向量化处理。

  1. 登录集群:您可以继续使用高权限账号或使用AI节点的数据库账号。

  2. 创建用于存储原始文档和处理后数据的数据表,执行以下SQL创建两张表。

    • file_content:用于存储上传的原始文档二进制内容。

    • file_chunk:用于存储文档分片后的文本内容及其对应的向量。

    -- 存储原始文档
    CREATE TABLE file_content (
      id SERIAL PRIMARY KEY, -- 文档的唯一标识
      name TEXT,             -- 文档名称
      content BYTEA          -- 文档二进制内容
    );
    
    -- 存储分片和向量
    CREATE TABLE file_chunk (
      file_id INTEGER,       -- 关联的文档ID
      chunk_id INTEGER,      -- 在文档内的切片ID
      chunk_content TEXT,    -- 切片文本内容
      embedding VECTOR(1024) -- 切片向量,维度需与模型输出保持一致
    );
  3. 将本地PDF文档加载到file_content表中。推荐使用以下两种方式之一导入文档。

    • 方案一(推荐):使用polar_ai_util插件,从OSS中加载文件的二进制内容。

      CREATE EXTENSION polar_ai_util; -- 用于从oss上加载文档
      
      INSERT INTO file_content(name, content)
      SELECT 'PolarAI.pdf', polar_ai.AI_LOADFILE('oss://<access-key-id>:<access-key-secret>@oss-cn-beijing-internal.aliyuncs.com/your-bucket/path/to/PolarAI.pdf');
    • 方案二:使用客户端程序写入,您可以使用任何支持PostgreSQL的编程语言(如Pythonpsycopg2库)编写脚本,读取本地文件内容,并将其以bytea格式INSERTfile_content 表中。

      单击查看Python示例脚本

      //  需要先安装psycopg2-binary扩展库
      
      import psycopg2
      import os
      
      # --- 请在此处修改为您的配置 ---
      # 数据库连接信息
      DB_CONFIG = {
          "host": "your_database_host",  # 例如: "localhost" 或 IP 地址
          "dbname": "your_database_name",
          "user": "your_username",
          "password": "your_password",
          "port": "5432"  # 默认是 5432
      }
      
      # 要上传的本地文件路径
      LOCAL_FILE_PATH = "/your_path/to/PolarAI.pdf"
      # --- 配置结束 ---
      
      
      def upload_file_to_db(file_path, db_params):
          """
          连接到 PostgreSQL,读取本地文件并将其作为 BYTEA 插入数据库。
          """
          # 检查文件是否存在
          if not os.path.exists(file_path):
              print(f"错误:文件 '{file_path}' 不存在。")
              return
      
          conn = None
          try:
              # 1. 连接到 PostgreSQL 数据库
              print("正在连接到 PostgreSQL 数据库...")
              conn = psycopg2.connect(**db_params)
              cursor = conn.cursor()
              print("连接成功!")
      
              # 2. 以二进制模式('rb')读取文件内容
              file_name = os.path.basename(file_path)
              print(f"正在读取文件 '{file_name}' 的二进制内容...")
              with open(file_path, 'rb') as f:
                  file_content = f.read()
              print(f"文件读取完毕,大小为 {len(file_content)} 字节。")
      
              # 3. 准备 SQL 插入语句
              # 使用参数化查询 (%s) 来防止 SQL 注入
              sql_insert = "INSERT INTO file_content (name, content) VALUES (%s, %s) RETURNING id;"
      
              # 4. 执行插入操作
              print("正在将文件内容插入到 'file_content' 表中...")
              cursor.execute(sql_insert, (file_name, file_content))
      
              # 获取新插入记录的 ID
              inserted_id = cursor.fetchone()[0]
      
              # 5. 提交事务
              conn.commit()
              print("---" * 10)
              print(f"成功!文件 '{file_name}' 已上传到数据库。")
              print(f"新记录的 ID 是: {inserted_id}")
              print("---" * 10)
      
          except psycopg2.Error as e:
              print(f"数据库错误: {e}")
              if conn:
                  conn.rollback()  # 如果发生错误,回滚事务
          except Exception as e:
              print(f"发生未知错误: {e}")
          finally:
              # 6. 关闭数据库连接
              if conn:
                  cursor.close()
                  conn.close()
                  print("数据库连接已关闭。")
      
      
      # --- 脚本主入口 ---
      if __name__ == "__main__":
          upload_file_to_db(LOCAL_FILE_PATH, DB_CONFIG)
  4. 对导入的文档进行分片,并将结果存入file_chunk表。

    INSERT INTO file_chunk(file_id, chunk_id, chunk_content)
    SELECT 1,  -- 文档id
      (polar_ai.ai_rag_chunking(content, 'pdf')).* -- 分片函数
    FROM file_content;
  5. 将内容向量化,以方便进行检索。

    UPDATE file_chunk SET embedding = polar_ai.ai_rag_text_embedding(chunk_content);
  6. 为向量检索创建高性能索引,在embedding列上创建HNSW索引。HNSW是一种高效的近似最近邻搜索算法,适用于高维向量数据。

    -- 使用 HNSW 索引和 L2 欧氏距离
    CREATE INDEX ON file_chunk USING HNSW (embedding vector_l2_ops);

构建RAG查询

整合向量检索、重排序和LLM调用,实现完整的RAG问答流程。

RAG执行步骤

  1. 将问题转换为向量。

  2. 进行向量检索或混合检索找到需要的文档分片(Chunk)。

  3. 将召回的文档分片(Chunk)进行重排序(Rerank),找出相关性最大的一些文档分片(Chunk)。

  4. 将相关性最大的文档分片(Chunk)提交给大模型,生成回答。

操作步骤

  1. 创建自定义函数ai_summarize:创建一个自定义 SQL 函数ai_summarize,用于封装调用大语言模型生成最终答案的逻辑。这个函数负责将问题和检索到的背景知识拼接成一个结构化的提示(Prompt),并调用底层的文本生成函数。

    CREATE OR REPLACE FUNCTION public.ai_summarize(question text, contents text[])
    RETURNS text
        LANGUAGE plpgsql
        AS $function$
        DECLARE
           body text;
        BEGIN
           body = format('根据已有的材料回答问题: %I 已有的材料包括: %I', question, contents::text);
           RETURN polar_ai.ai_text_generation(body, '_polar4ai/_polar4ai_nl2sql_tongyi');
        END;
        $function$;
  2. 执行端到端的RAG查询:您可以执行以下SQL来提问。这个查询清晰地展示了RAG的每一步:

    -- RAG
    SELECT public.ai_summarize('PolarAI有哪些优势?', array_agg(chunks))
    FROM (
      -- rerank
      SELECT unnest(polar_ai.ai_rag_rerank('PolarAI有哪些优势?', array_agg(chunk_content))) as chunks
      FROM (
          -- vector search
          SELECT chunk_content
          FROM 
            file_chunk
          ORDER BY 
            -- vectorize question
            polar_ai.ai_rag_text_embedding('PolarAI有哪些优势?')::vector(1024) <-> embedding ASC
          LIMIT 10
      ) t
      LIMIT 5 
    );

相关SQL

SQL

说明

polar_ai.ai_rag_chunking

对二进制文档内容进行解析和分片。

polar_ai.ai_rag_text_embedding

将文本内容转换为向量。

polar_ai.ai_rag_rerank

对检索到的文本片段列表根据与问题的相关性进行重排序。