纲要
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 承担了核心的向量存储与检索职责:
- 向量化:应用层将用户问题转换为向量表示。
- 语义检索 :
PostgreSQL在数据库中执行向量相似度搜索,返回最相似的TOP-K个知识片段。 - 答案生成 :应用层将检索到的知识片段与原始问题组合,构造
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 查询的完整流程。
运行说明
-
安装依赖:
bashnpm init -y npm install pg openai dotenv -
准备 PostgreSQL 数据库(版本 14+),并安装 pgvector 扩展:
bashpsql -U postgres -c "CREATE DATABASE rag_db;" psql -U postgres -d rag_db -c "CREATE EXTENSION IF NOT EXISTS vector;" -
执行表结构创建脚本(见上文
CREATE TABLE语句)。 -
设置环境变量(
.env文件):envOPENAI_API_KEY=your_openai_api_key PG_HOST=localhost PG_PORT=5432 PG_DATABASE=rag_db PG_USER=app_user PG_PASSWORD=your_password -
运行以下 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 语言示例
运行说明
-
安装 Go 1.18+ 和 PostgreSQL 14+(已安装 pgvector 扩展)。
-
创建数据库并启用扩展(参考前文)。
-
初始化 Go 模块并安装依赖:
bashgo mod init rag-demo go get github.com/lib/pq go get github.com/sashabaranov/go-openai -
设置环境变量(或直接修改代码):
bashexport 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 -
运行示例:
bashgo 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 语言示例
运行说明
-
安装 Python 3.9+ 和 PostgreSQL 14+(已安装 pgvector 扩展)。
-
安装依赖:
bashpip install psycopg2-binary openai python-dotenv -
设置环境变量(
.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 -
运行示例:
bashpython 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 语言示例
运行说明
-
安装 JDK 17+ 和 PostgreSQL 14+(已安装 pgvector 扩展)。
-
使用 Maven 或 Gradle 管理依赖,添加以下核心依赖:
org.postgresql:postgresql(42.6+)com.openai:openai-java(或使用 OkHttp 自行封装,此处使用官方 beta 库)com.google.code.gson:gson(用于 JSON 处理)
-
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> -
设置环境变量(可通过 IDE 或命令行):
bashexport 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 -
编译并运行:
bashmvn 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将 JavaList<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 示例演示了从向量化存储到检索生成的全流程,可作为实际项目落地的参考起点。