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

纲要

  • RAG (Retrieval-Augmented Generation) 系统架构
    • 用户查询输入
    • 文本向量化 (Embedding)
    • 向量相似度检索 (Vector Similarity Search)
    • 大模型答案生成 (LLM Generation)
  • PostgreSQL + pgvector 的技术选型优势
    • 一库多用:关系型数据与向量数据融合
    • 扩展生态:pgvector, pgcrypto, pgAudit
    • 企业级特性:Row Level Security (RLS), 事务一致性, 权限控制
  • 数据库层设计
    • 表结构设计:知识片段存储与元数据管理
    • 向量索引:IVFFlat 与 HNSW 的选择与创建
    • 相似度检索:距离函数 (<->, <=>, <#>) 与 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 相关知识片段 将问题与知识片段作为上下文发送 生成最终答案 返回答案

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

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

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

PostgreSQL + pgvector 的技术优势

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

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

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

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

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

丰富的扩展生态

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

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

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-postgres、psycopg2、JDBC 等),即可完成所有数据库操作,包括向量的插入与检索。这大大降低了系统集成的复杂度。

完整的 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 使用方式上有所差异。开发者可根据自身技术栈选择合适的实现参考。

官方文档

参考链接

总结

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

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

相关推荐
白帽攻防录9 分钟前
SRC 挖洞:Roundcube 预认证 SQL 注入深度复盘,CVE-2026-48842 preg_replace 转义绕过怎么打穿邮件系统
网络·数据库·sql·网络安全·sql注入
꯭自꯭闭꯭36 分钟前
达梦SQL优化相关
linux·运维·数据库·sql
对空六课1 小时前
支持注意力分析的热力图工具有哪些?
前端·数据库·数据分析
南京码讯光电技术有限公司1 小时前
How to Design an Antenna System for an Industrial WiFi Module
数据库·人工智能
lusklusklusk2 小时前
Oracle数据库基础之2_体系结构
数据库·oracle
FakeOccupational3 小时前
【电路笔记 信号】DBPSK 波形查找表+脉冲成形(升余弦+根升余弦滤波)
开发语言·笔记
可乐ea3 小时前
从第一性原理构建 AI Agent:提示词、工具、技能与记忆全解剖
数据库·人工智能·工具调用·ai智能体·提示词工程·agent开发·智能体记忆
东方护航数据恢复(深圳)3 小时前
医疗案例:HIS/PACS 数据库页损坏修复,医院不停诊完成恢复【东方护航数据恢复深圳店】
数据库·数据恢复·医疗·二次开盘
lusklusklusk4 小时前
Oracle数据库基础之11_Duplicate和Dataguard和RAC
数据库·oracle
PHP实战开发录5 小时前
MySQL字段加索引为什么没变快
数据库·mysql·php·开发