PostgreSQL笔记51: 基于 pgvector 构建企业级智能问答系统

纲要

  • RAG (Retrieval-Augmented Generation) 系统架构
    • 用户查询输入
    • 文本向量化 (Embedding)
    • 向量相似度检索 (Vector Similarity Search)
    • 大模型答案生成 (LLM Generation)
  • PostgreSQL + pgvector 的技术选型优势
    • 一库多用:关系型数据与向量数据融合
    • 扩展生态:pgvector, pgcrypto, pgAudit
    • 企业级特性:Row Level Security (RLS), 事务一致性, 权限控制
  • 数据库层设计
    • 表结构设计:知识片段存储与元数据管理
    • 向量索引:IVFFlatHNSW 的选择与创建
    • 相似度检索:距离函数 (<->, <=>, <#>) 与 TOP-K 查询
  • 系统集成
    • 应用层通过 PostgreSQL 客户端直连
    • LLM 服务协同完成 RAG 闭环

项目背景与业务场景

企业在运营过程中积累了大量分散的知识资产,包括技术文档、产品手册、FAQ、白皮书以及各类案例。当员工或客户提出问题时,如何从这些海量文档中快速、准确地定位答案,是一个普遍存在的痛点。

传统的解决方案依赖于关系型数据库的全文检索或关键词匹配(如 LIKE 查询)。这类方法的效果有限,因为它无法理解语义。例如,用户搜索"如何提升数据库写入性能",系统可能无法匹配到内容为"优化插入速度"、"减少索引维护"或"降低外键约束"的文档,尽管这些内容在语义上高度相关。

向量数据库技术的引入改变了这一局面。它将文本转换为高维向量,通过计算向量间的距离来衡量语义相似度,从而实现基于语义的检索,而非简单的关键词匹配。PostgreSQL 通过 pgvector 扩展,原生支持了这种语义检索能力。

本项目旨在构建一个面向特定知识库的智能问答系统,即 RAG (Retrieval-Augmented Generation) 系统。RAG 是一种架构模式,它将大语言模型(LLM)与外部知识检索系统相结合。系统根据用户查询从知识库中检索相关信息,并将这些信息作为上下文提供给 LLM,从而生成更准确、更具时效性的答案。

系统架构与核心链路

一个完整的 RAG 问答系统遵循以下核心链路:
大语言模型 PostgreSQL (pgvector) 应用服务 用户 大语言模型 PostgreSQL (pgvector) 应用服务 用户 #mermaid-svg-x3UDlYhdAWXpeczU{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-x3UDlYhdAWXpeczU .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-x3UDlYhdAWXpeczU .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-x3UDlYhdAWXpeczU .error-icon{fill:#552222;}#mermaid-svg-x3UDlYhdAWXpeczU .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-x3UDlYhdAWXpeczU .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-x3UDlYhdAWXpeczU .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-x3UDlYhdAWXpeczU .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-x3UDlYhdAWXpeczU .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-x3UDlYhdAWXpeczU .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-x3UDlYhdAWXpeczU .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-x3UDlYhdAWXpeczU .marker{fill:#333333;stroke:#333333;}#mermaid-svg-x3UDlYhdAWXpeczU .marker.cross{stroke:#333333;}#mermaid-svg-x3UDlYhdAWXpeczU svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-x3UDlYhdAWXpeczU p{margin:0;}#mermaid-svg-x3UDlYhdAWXpeczU .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-x3UDlYhdAWXpeczU text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-x3UDlYhdAWXpeczU .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-x3UDlYhdAWXpeczU .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-x3UDlYhdAWXpeczU .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-x3UDlYhdAWXpeczU .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-x3UDlYhdAWXpeczU #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-x3UDlYhdAWXpeczU .sequenceNumber{fill:white;}#mermaid-svg-x3UDlYhdAWXpeczU #sequencenumber{fill:#333;}#mermaid-svg-x3UDlYhdAWXpeczU #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-x3UDlYhdAWXpeczU .messageText{fill:#333;stroke:none;}#mermaid-svg-x3UDlYhdAWXpeczU .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-x3UDlYhdAWXpeczU .labelText,#mermaid-svg-x3UDlYhdAWXpeczU .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-x3UDlYhdAWXpeczU .loopText,#mermaid-svg-x3UDlYhdAWXpeczU .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-x3UDlYhdAWXpeczU .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-x3UDlYhdAWXpeczU .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-x3UDlYhdAWXpeczU .noteText,#mermaid-svg-x3UDlYhdAWXpeczU .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-x3UDlYhdAWXpeczU .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-x3UDlYhdAWXpeczU .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-x3UDlYhdAWXpeczU .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-x3UDlYhdAWXpeczU .actorPopupMenu{position:absolute;}#mermaid-svg-x3UDlYhdAWXpeczU .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-x3UDlYhdAWXpeczU .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-x3UDlYhdAWXpeczU .actor-man circle,#mermaid-svg-x3UDlYhdAWXpeczU line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-x3UDlYhdAWXpeczU :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 输入问题 文本向量化 (Embedding) 执行向量相似度检索 返回 TOP-K 相关知识片段 将问题与知识片段作为上下文发送 生成最终答案 返回答案

在上述流程中,PostgreSQLpgvector 承担了核心的向量存储与检索职责:

  1. 向量化:应用层将用户问题转换为向量表示。
  2. 语义检索PostgreSQL 在数据库中执行向量相似度搜索,返回最相似的 TOP-K 个知识片段。
  3. 答案生成 :应用层将检索到的知识片段与原始问题组合,构造 Prompt 发送给 LLM,生成最终答案。

数据库层的质量(召回率与响应速度)直接决定了整个系统的最终表现。

PostgreSQL + pgvector 的技术优势

选择 PostgreSQLpgvector 作为 RAG 系统的知识库底座,基于以下核心优势:

一库多用,消除数据库孤岛

PostgreSQL 本身是一个功能强大的关系型数据库。通过 pgvector 扩展,它获得了向量存储与检索能力,成为一个融合型数据平台。这意味着在同一个数据库中,可以同时存储:

  • 结构化数据(如用户信息、文档元数据)
  • 向量数据(如文本嵌入向量)
  • 半结构化数据(如 JSON/JSONB

这种设计减少了系统组件的数量,降低了架构复杂度与维护成本。

丰富的扩展生态

在向量检索领域,pgvectorPostgreSQL 社区最主流的扩展之一。它支持:

  • 多种向量数据类型(vector, halfvec, sparsevec
  • 多种距离函数:欧几里得距离(<->)、余弦相似度(<=>)、内积(<#>
  • 近似最近邻(ANN)索引:IVFFlatHNSW

PostgreSQL 庞大的扩展生态(如 pgcrypto 用于加密、pgAudit 用于审计)可以与 pgvector 无缝协同,满足企业级应用的各种需求。

企业级特性

  • 事务一致性 :向量数据的插入与更新遵循 ACID 事务原则,确保数据一致性。
  • 权限控制 :依托 PostgreSQL 成熟的角色与权限管理体系。
  • 行级安全(RLS):可以针对不同用户或角色,控制其对表中特定行的可见性。例如,高权限用户可以看到完整的文档内容,而普通用户只能看到脱敏后的版本。
  • 集成简单 :应用层只需使用标准的 PostgreSQL 客户端驱动即可完成所有操作,无需引入额外的向量数据库组件。

数据库层设计

表结构设计

以下是一个用于存储知识片段及其向量的核心表结构设计:

sql 复制代码
-- 启用 pgvector 扩展 (注意:扩展名是 vector,不是 pgvector)
CREATE EXTENSION IF NOT EXISTS vector;

-- 创建知识片段表
CREATE TABLE knowledge_chunks (
    id BIGSERIAL PRIMARY KEY,
    -- 知识片段的原始文本内容
    content TEXT NOT NULL,
    -- 内容的向量表示,维度取决于所使用的 Embedding 模型(例如 768 或 1536)
    embedding VECTOR(1536),
    -- 元数据字段
    source VARCHAR(255),          -- 来源,如 "技术手册"、"FAQ"
    title VARCHAR(255),           -- 所属文档标题
    document_id VARCHAR(64),      -- 所属文档 ID
    created_at TIMESTAMPTZ DEFAULT NOW(),
    updated_at TIMESTAMPTZ DEFAULT NOW()
);

-- 为元数据创建普通索引,用于过滤查询
CREATE INDEX idx_knowledge_chunks_source ON knowledge_chunks (source);
CREATE INDEX idx_knowledge_chunks_document_id ON knowledge_chunks (document_id);

向量索引的选择与创建

pgvector 提供了两种近似最近邻(ANN)索引:

索引类型 特点 适用场景
IVFFlat 将向量划分为多个聚类桶。查询时先定位最近的桶,再在桶内搜索。 数据集较大且数据分布相对稳定。索引创建后,建议在数据积累到一定规模后再构建,以获得更好的分区效果。
HNSW 构建多层图结构。查询时从顶层开始逐层向下搜索。 追求极致的查询速度,且数据会频繁增量插入。HNSW 索引可以在空表上创建,并随着数据插入动态更新。

根据实际需求选择索引类型并创建:

sql 复制代码
-- 创建 IVFFlat 索引 (需指定 lists 参数,即聚类数量)
-- 建议在表中已有一定量数据后创建
CREATE INDEX idx_knowledge_chunks_embedding_ivfflat 
ON knowledge_chunks 
USING ivfflat (embedding vector_cosine_ops) 
WITH (lists = 100);

-- 创建 HNSW 索引 (需指定 m 和 ef_construction 参数)
-- 可以在空表上直接创建
CREATE INDEX idx_knowledge_chunks_embedding_hnsw 
ON knowledge_chunks 
USING hnsw (embedding vector_cosine_ops) 
WITH (m = 16, ef_construction = 64);

向量相似度检索

执行 TOP-K 语义检索的典型查询语句:

sql 复制代码
-- 假设已通过应用层生成查询向量 (嵌入向量)
-- 使用余弦距离 (<=>) 进行相似度排序,返回最相似的 5 条记录
SELECT 
    id,
    content,
    source,
    title,
    1 - (embedding <=> '[0.1, 0.2, ...]'::vector) AS similarity
FROM knowledge_chunks
ORDER BY embedding <=> '[0.1, 0.2, ...]'::vector
LIMIT 5;

-- 结合元数据过滤的查询
SELECT 
    id,
    content,
    source,
    title,
    1 - (embedding <=> '[0.1, 0.2, ...]'::vector) AS similarity
FROM knowledge_chunks
WHERE source = '技术手册' AND document_id = 'DOC-001'
ORDER BY embedding <=> '[0.1, 0.2, ...]'::vector
LIMIT 5;

系统集成与应用层实现

应用层集成模式

应用层只需使用标准的 PostgreSQL 客户端驱动(如 node-postgrespsycopg2JDBC 等),即可完成所有数据库操作,包括向量的插入与检索。这大大降低了系统集成的复杂度。

完整的 RAG 交互流程(Node.js 示例)

以下是一个使用 Node.js + pg 驱动 + openai 嵌入模型的完整 RAG 查询示例:

js 复制代码
const { Client } = require('pg');
const { OpenAI } = require('openai');

// 初始化 PostgreSQL 客户端
const pgClient = new Client({
    host: 'localhost',
    port: 5432,
    database: 'rag_db',
    user: 'app_user',
    password: 'secure_password'
});
await pgClient.connect();

// 初始化 OpenAI 客户端(用于生成嵌入向量)
const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });

// 用户查询
const userQuery = '如何提升数据库写入性能?';

// 1. 将用户问题向量化
const embeddingResponse = await openai.embeddings.create({
    model: 'text-embedding-ada-002',
    input: userQuery,
});
const queryVector = embeddingResponse.data[0].embedding;

// 2. 在 PostgreSQL 中执行向量相似度检索
const topK = 5;
const sql = `
    SELECT 
        id,
        content,
        source,
        title,
        1 - (embedding <=> $1::vector) AS similarity
    FROM knowledge_chunks
    ORDER BY embedding <=> $1::vector
    LIMIT $2
`;
const result = await pgClient.query(sql, [JSON.stringify(queryVector), topK]);
const retrievedChunks = result.rows;

// 3. 构造 Prompt 并调用 LLM 生成答案
const context = retrievedChunks.map(row => row.content).join('\n\n');
const prompt = `
基于以下参考资料回答用户的问题。如果参考资料中没有相关信息,请明确告知用户。

参考资料:
${context}

用户问题:${userQuery}

请给出准确、简洁的回答。
`;

const completion = await openai.chat.completions.create({
    model: 'gpt-4',
    messages: [{ role: 'user', content: prompt }],
});
const answer = completion.choices[0].message.content;

console.log('最终答案:', answer);

// 关闭连接
await pgClient.end();

API 速览

本方案涉及的核心 API 包括:

pgvector 操作符与函数

API 类型 说明 示例
<-> 操作符 欧几里得距离(L2) embedding <-> '[1,2,3]'
<=> 操作符 余弦距离(1 - 余弦相似度) embedding <=> '[1,2,3]'
<#> 操作符 负内积(适用于内积相似度) embedding <#> '[1,2,3]'
vector_cosine_ops 操作符类 用于创建余弦距离索引 USING ivfflat (embedding vector_cosine_ops)
vector_l2_ops 操作符类 用于创建欧几里得距离索引 USING ivfflat (embedding vector_l2_ops)
vector_ip_ops 操作符类 用于创建内积索引 USING ivfflat (embedding vector_ip_ops)

OpenAI Embeddings API

  • 方法openai.embeddings.create()
  • 参数model (字符串),input (字符串或字符串数组)
  • 返回值 :包含 data 数组,每个元素有 embedding 向量(浮点数数组)

OpenAI Chat Completions API

  • 方法openai.chat.completions.create()
  • 参数model (字符串),messages (消息对象数组)
  • 返回值 :包含 choices 数组,每个元素有 message.content(生成的文本)

完整 Demo 示例

本 Demo 基于 Node.js,演示了从知识库插入向量数据到执行 RAG 查询的完整流程。

运行说明

  1. 安装依赖:

    bash 复制代码
    npm init -y
    npm install pg openai dotenv
  2. 准备 PostgreSQL 数据库(版本 14+),并安装 pgvector 扩展:

    bash 复制代码
    psql -U postgres -c "CREATE DATABASE rag_db;"
    psql -U postgres -d rag_db -c "CREATE EXTENSION IF NOT EXISTS vector;"
  3. 执行表结构创建脚本(见上文 CREATE TABLE 语句)。

  4. 设置环境变量(.env 文件):

    env 复制代码
    OPENAI_API_KEY=your_openai_api_key
    PG_HOST=localhost
    PG_PORT=5432
    PG_DATABASE=rag_db
    PG_USER=app_user
    PG_PASSWORD=your_password
  5. 运行以下 Demo 脚本(demo.js):

代码说明

js 复制代码
// demo.js - 完整 RAG 示例:插入 + 检索 + 生成
require('dotenv').config();
const { Client } = require('pg');
const { OpenAI } = require('openai');

async function runRAG() {
    // 1. 初始化客户端
    const pgClient = new Client({
        host: process.env.PG_HOST,
        port: process.env.PG_PORT,
        database: process.env.PG_DATABASE,
        user: process.env.PG_USER,
        password: process.env.PG_PASSWORD,
    });
    await pgClient.connect();
    const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });

    // 2. 准备示例知识片段并插入(向量化后存储)
    const knowledgeDocs = [
        { content: '使用批量插入(COPY)可以显著提升 PostgreSQL 的写入性能。', source: '性能优化手册', title: '批量插入技巧' },
        { content: '减少索引数量并合理使用覆盖索引有助于加快 INSERT 操作。', source: '性能优化手册', title: '索引策略' },
        { content: '降低外键约束的检查频率(例如延迟约束)可以提高批量写入速度。', source: 'DBA 经验集', title: '约束管理' },
    ];

    for (const doc of knowledgeDocs) {
        const emb = await openai.embeddings.create({
            model: 'text-embedding-ada-002',
            input: doc.content,
        });
        const vector = emb.data[0].embedding;
        await pgClient.query(
            `INSERT INTO knowledge_chunks (content, embedding, source, title) 
             VALUES ($1, $2::vector, $3, $4)`,
            [doc.content, JSON.stringify(vector), doc.source, doc.title]
        );
    }
    console.log('知识片段插入完成。');

    // 3. 用户查询
    const userQuery = '如何加快数据库的写入速度?';

    // 4. 向量化查询
    const qEmb = await openai.embeddings.create({
        model: 'text-embedding-ada-002',
        input: userQuery,
    });
    const queryVec = qEmb.data[0].embedding;

    // 5. 检索 TOP-2 相关片段
    const topK = 2;
    const sql = `
        SELECT content, source, title
        FROM knowledge_chunks
        ORDER BY embedding <=> $1::vector
        LIMIT $2
    `;
    const res = await pgClient.query(sql, [JSON.stringify(queryVec), topK]);
    const chunks = res.rows;

    // 6. 生成答案
    const context = chunks.map(r => r.content).join('\n');
    const prompt = `基于以下资料回答问题:\n${context}\n问题:${userQuery}`;
    const completion = await openai.chat.completions.create({
        model: 'gpt-3.5-turbo',
        messages: [{ role: 'user', content: prompt }],
    });
    const answer = completion.choices[0].message.content;

    console.log('检索到的知识片段:', chunks);
    console.log('生成的答案:', answer);

    await pgClient.end();
}

runRAG().catch(console.error);

技术点总结

  • 演示了 pgvector 扩展的启用与向量列的创建。
  • 展示了 IVFFlat / HNSW 索引的创建语法。
  • 实现了基于余弦距离的 TOP-K 向量相似度检索。
  • 集成了外部嵌入模型(OpenAI)与 LLM 生成,完成完整的 RAG 链路。
  • 演示了应用层通过标准 PostgreSQL 驱动进行所有向量操作,无需额外组件。

多语言示例

Go 语言示例

运行说明
  1. 安装 Go 1.18+ 和 PostgreSQL 14+(已安装 pgvector 扩展)。

  2. 创建数据库并启用扩展(参考前文)。

  3. 初始化 Go 模块并安装依赖:

    bash 复制代码
    go mod init rag-demo
    go get github.com/lib/pq
    go get github.com/sashabaranov/go-openai
  4. 设置环境变量(或直接修改代码):

    bash 复制代码
    export OPENAI_API_KEY=your_openai_api_key
    export PG_HOST=localhost
    export PG_PORT=5432
    export PG_DATABASE=rag_db
    export PG_USER=app_user
    export PG_PASSWORD=your_password
  5. 运行示例:

    bash 复制代码
    go run main.go
代码说明

以下 Go 程序完成完整的 RAG 流程:插入知识片段(向量化存储)、执行向量检索、调用 LLM 生成答案。使用标准库 database/sql 配合 lib/pq 驱动连接 PostgreSQL,使用 go-openai 客户端调用 OpenAI API。

go 复制代码
// main.go
package main

import (
    "database/sql"
    "encoding/json"
    "fmt"
    "log"
    "os"

    "github.com/lib/pq"
    "github.com/sashabaranov/go-openai"
)

func main() {
    // 1. 初始化 PostgreSQL 连接
    connStr := fmt.Sprintf(
        "host=%s port=%s user=%s password=%s dbname=%s sslmode=disable",
        os.Getenv("PG_HOST"), os.Getenv("PG_PORT"),
        os.Getenv("PG_USER"), os.Getenv("PG_PASSWORD"),
        os.Getenv("PG_DATABASE"),
    )
    db, err := sql.Open("postgres", connStr)
    if err != nil {
        log.Fatal("连接数据库失败:", err)
    }
    defer db.Close()
    if err := db.Ping(); err != nil {
        log.Fatal("Ping 失败:", err)
    }

    // 2. 初始化 OpenAI 客户端
    openaiClient := openai.NewClient(os.Getenv("OPENAI_API_KEY"))

    // 3. 准备并插入知识片段(向量化)
    docs := []struct {
        content string
        source  string
        title   string
    }{
        {"使用批量插入(COPY)可以显著提升 PostgreSQL 的写入性能。", "性能优化手册", "批量插入技巧"},
        {"减少索引数量并合理使用覆盖索引有助于加快 INSERT 操作。", "性能优化手册", "索引策略"},
        {"降低外键约束的检查频率(例如延迟约束)可以提高批量写入速度。", "DBA 经验集", "约束管理"},
    }

    for _, doc := range docs {
        // 生成嵌入向量
        resp, err := openaiClient.CreateEmbeddings(
            openai.EmbeddingRequest{
                Model: openai.AdaEmbeddingV2,
                Input: []string{doc.content},
            },
        )
        if err != nil {
            log.Fatal("嵌入生成失败:", err)
        }
        vector := resp.Data[0].Embedding
        // 将 []float32 转换为 pgvector 可接受的字符串格式
        vecStr, _ := json.Marshal(vector) // pgvector 接受 JSON 数组字符串
        _, err = db.Exec(
            `INSERT INTO knowledge_chunks (content, embedding, source, title) 
             VALUES ($1, $2::vector, $3, $4)`,
            doc.content, string(vecStr), doc.source, doc.title,
        )
        if err != nil {
            log.Fatal("插入失败:", err)
        }
    }
    fmt.Println("知识片段插入完成。")

    // 4. 用户查询
    userQuery := "如何加快数据库的写入速度?"

    // 5. 生成查询向量
    qResp, err := openaiClient.CreateEmbeddings(
        openai.EmbeddingRequest{
            Model: openai.AdaEmbeddingV2,
            Input: []string{userQuery},
        },
    )
    if err != nil {
        log.Fatal("查询嵌入生成失败:", err)
    }
    queryVec := qResp.Data[0].Embedding
    queryVecStr, _ := json.Marshal(queryVec)

    // 6. 执行 TOP-2 相似度检索
    rows, err := db.Query(
        `SELECT content, source, title 
         FROM knowledge_chunks 
         ORDER BY embedding <=> $1::vector 
         LIMIT $2`,
        string(queryVecStr), 2,
    )
    if err != nil {
        log.Fatal("检索失败:", err)
    }
    defer rows.Close()

    var chunks []string
    for rows.Next() {
        var content, source, title string
        if err := rows.Scan(&content, &source, &title); err != nil {
            log.Fatal("扫描行失败:", err)
        }
        chunks = append(chunks, content)
        fmt.Printf("检索到: [%s] %s - %s\n", source, title, content)
    }

    // 7. 构造 Prompt 并调用 LLM 生成答案
    context := ""
    for _, c := range chunks {
        context += c + "\n"
    }
    prompt := fmt.Sprintf("基于以下资料回答问题:\n%s\n问题:%s", context, userQuery)
    chatResp, err := openaiClient.CreateChatCompletion(
        openai.ChatCompletionRequest{
            Model: openai.GPT3Dot5Turbo,
            Messages: []openai.ChatCompletionMessage{
                {Role: openai.ChatMessageRoleUser, Content: prompt},
            },
        },
    )
    if err != nil {
        log.Fatal("LLM 调用失败:", err)
    }
    answer := chatResp.Choices[0].Message.Content
    fmt.Println("生成的答案:", answer)
}
技术点总结
  • 使用 lib/pq 作为 PostgreSQL 驱动,支持 pgvector 的向量类型(通过字符串转换)。
  • 使用 go-openai 库调用嵌入和聊天补全 API。
  • 通过 json.Marshal[]float32 转换为 pgvector 可接受的数组字符串格式。
  • 在 SQL 中使用 <=> 操作符计算余弦距离,通过 ORDER BY ... LIMIT 实现 TOP-K 检索。
  • 完整演示了数据插入、向量检索与 LLM 生成的闭环。

Python 语言示例

运行说明
  1. 安装 Python 3.9+ 和 PostgreSQL 14+(已安装 pgvector 扩展)。

  2. 安装依赖:

    bash 复制代码
    pip install psycopg2-binary openai python-dotenv
  3. 设置环境变量(.env 文件或系统环境):

    复制代码
    OPENAI_API_KEY=your_openai_api_key
    PG_HOST=localhost
    PG_PORT=5432
    PG_DATABASE=rag_db
    PG_USER=app_user
    PG_PASSWORD=your_password
  4. 运行示例:

    bash 复制代码
    python demo.py
代码说明

Python 示例使用 psycopg2(支持 pgvector 的向量适配器)和 OpenAI 官方 SDK。代码清晰简洁,适合快速原型开发。

python 复制代码
# demo.py
import os
import json
import psycopg2
from psycopg2.extras import Json
from openai import OpenAI
from dotenv import load_dotenv

load_dotenv()

def main():
    # 1. 初始化 PostgreSQL 连接
    conn = psycopg2.connect(
        host=os.getenv("PG_HOST"),
        port=os.getenv("PG_PORT"),
        dbname=os.getenv("PG_DATABASE"),
        user=os.getenv("PG_USER"),
        password=os.getenv("PG_PASSWORD")
    )
    conn.autocommit = True
    cur = conn.cursor()

    # 2. 初始化 OpenAI 客户端
    openai_client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))

    # 3. 准备并插入知识片段
    docs = [
        ("使用批量插入(COPY)可以显著提升 PostgreSQL 的写入性能。", "性能优化手册", "批量插入技巧"),
        ("减少索引数量并合理使用覆盖索引有助于加快 INSERT 操作。", "性能优化手册", "索引策略"),
        ("降低外键约束的检查频率(例如延迟约束)可以提高批量写入速度。", "DBA 经验集", "约束管理"),
    ]

    for content, source, title in docs:
        # 生成嵌入向量
        resp = openai_client.embeddings.create(
            model="text-embedding-ada-002",
            input=content
        )
        vector = resp.data[0].embedding
        # psycopg2 可自动适配列表为 vector 类型(需使用 Json 或直接传递列表)
        cur.execute(
            """INSERT INTO knowledge_chunks (content, embedding, source, title)
               VALUES (%s, %s::vector, %s, %s)""",
            (content, json.dumps(vector), source, title)
        )
    print("知识片段插入完成。")

    # 4. 用户查询
    user_query = "如何加快数据库的写入速度?"

    # 5. 生成查询向量
    q_resp = openai_client.embeddings.create(
        model="text-embedding-ada-002",
        input=user_query
    )
    query_vec = q_resp.data[0].embedding

    # 6. 执行 TOP-2 相似度检索
    cur.execute(
        """SELECT content, source, title
           FROM knowledge_chunks
           ORDER BY embedding <=> %s::vector
           LIMIT %s""",
        (json.dumps(query_vec), 2)
    )
    rows = cur.fetchall()
    chunks = [row[0] for row in rows]
    for content, source, title in rows:
        print(f"检索到: [{source}] {title} - {content}")

    # 7. 构造 Prompt 并调用 LLM
    context = "\n".join(chunks)
    prompt = f"基于以下资料回答问题:\n{context}\n问题:{user_query}"
    chat_resp = openai_client.chat.completions.create(
        model="gpt-3.5-turbo",
        messages=[{"role": "user", "content": prompt}]
    )
    answer = chat_resp.choices[0].message.content
    print("生成的答案:", answer)

    cur.close()
    conn.close()

if __name__ == "__main__":
    main()
技术点总结
  • 使用 psycopg2 连接 PostgreSQL,支持将 Python 列表通过 json.dumps 转换为向量文本。
  • 使用 OpenAI 官方 Python SDK 调用嵌入和聊天补全接口。
  • 利用 %s 占位符和 ::vector 类型转换完成向量参数传递。
  • 演示了完整的 RAG 流程,代码简洁且易于扩展。

Java 语言示例

运行说明
  1. 安装 JDK 17+ 和 PostgreSQL 14+(已安装 pgvector 扩展)。

  2. 使用 Maven 或 Gradle 管理依赖,添加以下核心依赖:

    • org.postgresql:postgresql (42.6+)
    • com.openai:openai-java (或使用 OkHttp 自行封装,此处使用官方 beta 库)
    • com.google.code.gson:gson (用于 JSON 处理)
  3. Maven pom.xml 关键依赖片段:

    xml 复制代码
    <dependency>
        <groupId>org.postgresql</groupId>
        <artifactId>postgresql</artifactId>
        <version>42.6.0</version>
    </dependency>
    <dependency>
        <groupId>com.openai</groupId>
        <artifactId>openai-java</artifactId>
        <version>0.12.0</version>
    </dependency>
    <dependency>
        <groupId>com.google.code.gson</groupId>
        <artifactId>gson</artifactId>
        <version>2.10.1</version>
    </dependency>
  4. 设置环境变量(可通过 IDE 或命令行):

    bash 复制代码
    export OPENAI_API_KEY=your_openai_api_key
    export PG_HOST=localhost
    export PG_PORT=5432
    export PG_DATABASE=rag_db
    export PG_USER=app_user
    export PG_PASSWORD=your_password
  5. 编译并运行:

    bash 复制代码
    mvn compile exec:java -Dexec.mainClass="com.example.RagDemo"
代码说明

Java 示例使用官方 PostgreSQL JDBC 驱动,结合 OpenAI Java SDK(beta)实现嵌入生成和聊天补全。由于 pgvector 在 JDBC 中没有原生向量类型,采用字符串传递 JSON 数组的方式。

java 复制代码
// RagDemo.java
package com.example;

import com.google.gson.Gson;
import com.openai.client.OpenAIClient;
import com.openai.client.okhttp.OpenAIOkHttpClient;
import com.openai.models.*;
import java.sql.*;
import java.util.*;

public class RagDemo {
    public static void main(String[] args) throws Exception {
        // 1. 初始化 PostgreSQL 连接
        String url = String.format("jdbc:postgresql://%s:%s/%s",
                System.getenv("PG_HOST"), System.getenv("PG_PORT"), System.getenv("PG_DATABASE"));
        Properties props = new Properties();
        props.setProperty("user", System.getenv("PG_USER"));
        props.setProperty("password", System.getenv("PG_PASSWORD"));
        Connection conn = DriverManager.getConnection(url, props);

        // 2. 初始化 OpenAI 客户端
        OpenAIClient openai = OpenAIOkHttpClient.builder()
                .apiKey(System.getenv("OPENAI_API_KEY"))
                .build();
        Gson gson = new Gson();

        // 3. 准备并插入知识片段
        String[][] docs = {
            {"使用批量插入(COPY)可以显著提升 PostgreSQL 的写入性能。", "性能优化手册", "批量插入技巧"},
            {"减少索引数量并合理使用覆盖索引有助于加快 INSERT 操作。", "性能优化手册", "索引策略"},
            {"降低外键约束的检查频率(例如延迟约束)可以提高批量写入速度。", "DBA 经验集", "约束管理"}
        };

        for (String[] doc : docs) {
            // 生成嵌入
            EmbeddingCreateParams params = EmbeddingCreateParams.builder()
                    .model(EmbeddingModel.TEXT_EMBEDDING_ADA_002)
                    .input(Collections.singletonList(doc[0]))
                    .build();
            EmbeddingCreateResponse resp = openai.embeddings().create(params);
            List<Double> vector = resp.data().get(0).embedding();
            String vecJson = gson.toJson(vector); // 转换为 [0.1, 0.2, ...] 字符串

            PreparedStatement stmt = conn.prepareStatement(
                "INSERT INTO knowledge_chunks (content, embedding, source, title) VALUES (?, ?::vector, ?, ?)"
            );
            stmt.setString(1, doc[0]);
            stmt.setString(2, vecJson);
            stmt.setString(3, doc[1]);
            stmt.setString(4, doc[2]);
            stmt.executeUpdate();
        }
        System.out.println("知识片段插入完成。");

        // 4. 用户查询
        String userQuery = "如何加快数据库的写入速度?";

        // 5. 生成查询向量
        EmbeddingCreateParams qParams = EmbeddingCreateParams.builder()
                .model(EmbeddingModel.TEXT_EMBEDDING_ADA_002)
                .input(Collections.singletonList(userQuery))
                .build();
        EmbeddingCreateResponse qResp = openai.embeddings().create(qParams);
        List<Double> qVec = qResp.data().get(0).embedding();
        String qVecJson = gson.toJson(qVec);

        // 6. 执行 TOP-2 检索
        PreparedStatement queryStmt = conn.prepareStatement(
            "SELECT content, source, title FROM knowledge_chunks ORDER BY embedding <=> ?::vector LIMIT ?"
        );
        queryStmt.setString(1, qVecJson);
        queryStmt.setInt(2, 2);
        ResultSet rs = queryStmt.executeQuery();

        List<String> chunks = new ArrayList<>();
        while (rs.next()) {
            String content = rs.getString("content");
            String source = rs.getString("source");
            String title = rs.getString("title");
            chunks.add(content);
            System.out.printf("检索到: [%s] %s - %s\n", source, title, content);
        }

        // 7. 构造 Prompt 并调用 LLM
        String context = String.join("\n", chunks);
        String prompt = "基于以下资料回答问题:\n" + context + "\n问题:" + userQuery;

        ChatCompletionCreateParams chatParams = ChatCompletionCreateParams.builder()
                .model(ChatCompletionModel.GPT_3_5_TURBO)
                .addMessage(ChatCompletionMessage.builder()
                    .role(ChatCompletionMessage.Role.USER)
                    .content(prompt)
                    .build())
                .build();
        ChatCompletionCreateResponse chatResp = openai.chat().completions().create(chatParams);
        String answer = chatResp.choices().get(0).message().content().orElse("");
        System.out.println("生成的答案:" + answer);

        conn.close();
    }
}
技术点总结
  • 使用 JDBC 驱动,通过字符串 JSON 数组传递向量数据,配合 ::vector 类型转换。
  • 使用 OpenAI Java SDK(beta)调用嵌入和聊天补全接口。
  • 使用 Gson 将 Java List<Double> 序列化为 JSON 数组字符串。
  • 完整实现了 RAG 链路,代码结构清晰,适合企业级 Java 应用集成。

多语言实现对比

维度 Go Python Java
PostgreSQL 驱动 github.com/lib/pq psycopg2-binary org.postgresql:postgresql
向量传递方式 json.Marshal → 字符串,使用 ::vector json.dumps → 字符串,使用 ::vector Gson.toJson → 字符串,使用 ::vector
OpenAI SDK go-openai openai 官方库 com.openai:openai-java (beta)
依赖管理 Go Modules pip / requirements.txt Maven / Gradle
编译/运行 go run python mvn exec:java
代码行数(核心逻辑) ~100 行 ~75 行 ~120 行
错误处理 显式 if err != nil 异常捕获(可改进) 抛出 Exception
类型安全 强类型,结构体清晰 动态类型,灵活 强类型,冗长但严谨
环境变量读取 os.Getenv os.getenv System.getenv
适用场景 高性能微服务、云原生 快速原型、数据科学 企业级后端、大型系统

以上三种语言示例均基于相同的 RAG 逻辑,仅在语法、驱动和 SDK 使用方式上有所差异。开发者可根据自身技术栈选择合适的实现参考。

官方文档

参考链接

总结

本文围绕使用 PostgreSQLpgvector 构建企业级 RAG 智能问答系统,详细阐述了系统架构、核心链路、技术选型优势以及数据库层的具体设计。pgvector 扩展使 PostgreSQL 成为一个融合型数据平台,能够同时处理结构化数据与向量数据,消除数据库孤岛,降低系统复杂度和维护成本。通过创建合理的表结构和向量索引(IVFFlatHNSW),可以高效地执行基于语义的相似度检索。

结合应用层的嵌入模型与大语言模型,即可快速搭建一个完整的 RAG 系统,为企业知识库赋能智能问答能力。此外,PostgreSQL 原生的事务一致性、行级安全(RLS)及丰富的扩展生态,使得该系统天然具备企业级的数据安全与合规特性。本文提供的完整 Node.js 示例演示了从向量化存储到检索生成的全流程,可作为实际项目落地的参考起点。

相关推荐
我星期八休息2 小时前
Linux—五种IO模型与非阻塞IO
linux·运维·服务器·网络·数据库·网络协议
自小吃多2 小时前
Capture软件原理图添加差分属性笔记
笔记·嵌入式硬件
SQL-First布道者2 小时前
全面解构传统持久层框架,拥抱真正的 SQL-First
java·数据库·spring boot·sql·spring·mybatis·spring jdbc
翼龙云_cloud2 小时前
阿里云国际版代理商:OSS自定义域名绑定与HTTPS配置排障教程
数据库·阿里云·https·云计算
日常筹谋记2 小时前
ATS选不对运维两行泪:数据中心市电与柴发切换场景的双电源自动转换开关选型笔记
运维·数据库·笔记
疯狂打码的少年2 小时前
【数据结构】哈希表:构造与冲突处理
数据结构·笔记·哈希算法·散列表
小蒜学长2 小时前
vue旅游攻略网站(代码+数据库+LW)
java·数据库·vue.js·spring boot·后端·旅游
Wang's Blog3 小时前
PostgreSQL笔记50: 基于PGVECTOR的实时推荐与搜索系统构建
笔记·postgresql
山甫aa3 小时前
JavaWeb后端开发学习手册
java·开发语言·数据库·学习·mysql·springboot·web