首页 > 后端开发 > Golang > 使用 OpenAI、Go 和 PostgreSQL (pgvector) 构建语义搜索引擎

使用 OpenAI、Go 和 PostgreSQL (pgvector) 构建语义搜索引擎

Linda Hamilton
发布: 2025-01-15 11:09:44
原创
771 人浏览过

Building a Semantic Search Engine with OpenAI, Go, and PostgreSQL (pgvector)

近年来,向量嵌入已成为现代自然语言处理 (NLP) 和语义搜索的基础。向量数据库不再依赖关键字搜索,而是通过数值表示(嵌入)来比较文本的“含义”。本示例演示如何利用 OpenAI 嵌入、Go 和带有 pgvector 扩展的 PostgreSQL 来创建一个语义搜索引擎。

什么是嵌入?

嵌入是文本(或其他数据)在高维空间中的向量表示。如果两段文本在语义上相似,则它们的向量在该空间中会彼此靠近。通过将嵌入存储在像 PostgreSQL(带有 pgvector 扩展)这样的数据库中,我们可以快速准确地执行相似性搜索。

为什么选择 PostgreSQL 和 pgvector?

pgvector 是一个流行的扩展,它将向量数据类型添加到 PostgreSQL 中。它使您能够:

  • 将嵌入存储为向量列
  • 执行近似或精确的最近邻搜索
  • 使用标准 SQL 运行查询

应用程序概述

  1. 调用 OpenAI 的嵌入 API 将输入文本转换为向量嵌入。
  2. 使用 pgvector 扩展将这些嵌入存储在 PostgreSQL 中。
  3. 查询嵌入以查找数据库中最语义相似的条目。

先决条件

  • 已安装 Go(推荐 1.19 )。
  • 已安装并运行 PostgreSQL(本地或托管)。
  • 在 PostgreSQL 中安装 pgvector 扩展。(有关安装说明,请参阅 pgvector 的 GitHub 页面。)
  • 具有嵌入访问权限的 OpenAI API 密钥。

用于本地测试的包含与 postgres/pgvector 和 Docker 相关的任务的 Makefile。

<code class="language-makefile">pgvector:
    @docker run -d \
        --name pgvector \
        -e POSTGRES_USER=admin \
        -e POSTGRES_PASSWORD=admin \
        -e POSTGRES_DB=vectordb \
        -v pgvector_data:/var/lib/postgresql/data \
        -p 5432:5432 \
        pgvector/pgvector:pg17
psql:
    @psql -h localhost -U admin -d vectordb</code>
登录后复制

确保已安装 pgvector。然后,在您的 PostgreSQL 数据库中:

<code class="language-sql">CREATE EXTENSION IF NOT EXISTS vector;</code>
登录后复制

完整代码

<code class="language-go">package main

import (
    "context"
    "fmt"
    "log"
    "os"
    "strings"

    "github.com/jackc/pgx/v5/pgxpool"
    "github.com/joho/godotenv"
    "github.com/sashabaranov/go-openai"
)

func floats32ToString(floats []float32) string {
    strVals := make([]string, len(floats))
    for i, val := range floats {
        // 将每个浮点数格式化为字符串
        strVals[i] = fmt.Sprintf("%f", val)
    }

    // 使用逗号 + 空格连接它们
    joined := strings.Join(strVals, ", ")

    // pgvector 需要方括号表示法才能输入向量,例如 [0.1, 0.2, 0.3]
    return "[" + joined + "]"
}

func main() {
    // 加载环境变量
    err := godotenv.Load()
    if err != nil {
        log.Fatal("加载 .env 文件出错")
    }

    // 创建连接池
    dbpool, err := pgxpool.New(context.Background(), os.Getenv("DATABASE_URL"))
    if err != nil {
        fmt.Fprintf(os.Stderr, "无法创建连接池:%v\n", err)
        os.Exit(1)
    }
    defer dbpool.Close()

    // 1. 确保已启用 pgvector 扩展
    _, err = dbpool.Exec(context.Background(), "CREATE EXTENSION IF NOT EXISTS vector;")
    if err != nil {
        log.Fatalf("创建扩展失败:%v\n", err)
        os.Exit(1)
    }

    // 2. 创建表(如果不存在)
    createTableSQL := `
    CREATE TABLE IF NOT EXISTS documents (
        id SERIAL PRIMARY KEY,
        content TEXT,
        embedding vector(1536)
    );
    `
    _, err = dbpool.Exec(context.Background(), createTableSQL)
    if err != nil {
        log.Fatalf("创建表失败:%v\n", err)
    }

    // 3. 创建索引(如果不存在)
    createIndexSQL := `
    CREATE INDEX IF NOT EXISTS documents_embedding_idx
    ON documents USING ivfflat (embedding vector_l2_ops) WITH (lists = 100);
    `
    _, err = dbpool.Exec(context.Background(), createIndexSQL)
    if err != nil {
        log.Fatalf("创建索引失败:%v\n", err)
    }

    // 4. 初始化 OpenAI 客户端
    apiKey := os.Getenv("OPENAI_API_KEY")
    if apiKey == "" {
        log.Fatal("未设置 OPENAI_API_KEY")
    }
    openaiClient := openai.NewClient(apiKey)

    // 5. 插入示例文档
    docs := []string{
        "PostgreSQL 是一个先进的开源关系数据库。",
        "OpenAI 提供基于 GPT 的模型来生成文本嵌入。",
        "pgvector 允许将嵌入存储在 Postgres 数据库中。",
    }

    for _, doc := range docs {
        err = insertDocument(context.Background(), dbpool, openaiClient, doc)
        if err != nil {
            log.Printf("插入文档“%s”失败:%v\n", doc, err)
        }
    }

    // 6. 查询相似性
    queryText := "如何在 Postgres 中存储嵌入?"
    similarDocs, err := searchSimilarDocuments(context.Background(), dbpool, openaiClient, queryText, 5)
    if err != nil {
        log.Fatalf("搜索失败:%v\n", err)
    }

    fmt.Println("=== 最相似的文档 ===")
    for _, doc := range similarDocs {
        fmt.Printf("- %s\n", doc)
    }
}

// insertDocument 使用 OpenAI API 为 `content` 生成嵌入,并将其插入 documents 表中。
func insertDocument(ctx context.Context, dbpool *pgxpool.Pool, client *openai.Client, content string) error {
    // 1) 从 OpenAI 获取嵌入
    embedResp, err := client.CreateEmbeddings(ctx, openai.EmbeddingRequest{
        Model: openai.AdaEmbeddingV2, // "text-embedding-ada-002"
        Input: []string{content},
    })
    if err != nil {
        return fmt.Errorf("CreateEmbeddings API 调用失败:%w", err)
    }

    // 2) 将嵌入转换为 pgvector 的方括号字符串
    embedding := embedResp.Data[0].Embedding // []float32
    embeddingStr := floats32ToString(embedding)

    // 3) 插入 PostgreSQL
    insertSQL := `
        INSERT INTO documents (content, embedding)
        VALUES (, ::vector)
    `
    _, err = dbpool.Exec(ctx, insertSQL, content, embeddingStr)
    if err != nil {
        return fmt.Errorf("插入文档失败:%w", err)
    }

    return nil
}

// searchSimilarDocuments 获取用户查询的嵌入,并根据向量相似性返回前 k 个相似的文档。
func searchSimilarDocuments(ctx context.Context, pool *pgxpool.Pool, client *openai.Client, query string, k int) ([]string, error) {
    // 1) 通过 OpenAI 获取用户查询的嵌入
    embedResp, err := client.CreateEmbeddings(ctx, openai.EmbeddingRequest{
        Model: openai.AdaEmbeddingV2, // "text-embedding-ada-002"
        Input: []string{query},
    })
    if err != nil {
        return nil, fmt.Errorf("CreateEmbeddings API 调用失败:%w", err)
    }

    // 2) 将 OpenAI 嵌入转换为 pgvector 的方括号字符串格式
    queryEmbedding := embedResp.Data[0].Embedding // []float32
    queryEmbeddingStr := floats32ToString(queryEmbedding)
    // 例如 "[0.123456, 0.789012, ...]"

    // 3) 构建按向量相似性排序的 SELECT 语句
    selectSQL := fmt.Sprintf(`
        SELECT content
        FROM documents
        ORDER BY embedding <-> '%s'::vector
        LIMIT %d;
    `, queryEmbeddingStr, k)

    // 4) 运行查询
    rows, err := pool.Query(ctx, selectSQL)
    if err != nil {
        return nil, fmt.Errorf("查询文档失败:%w", err)
    }
    defer rows.Close()

    // 5) 读取匹配的文档
    var contents []string
    for rows.Next() {
        var content string
        if err := rows.Scan(&content); err != nil {
            return nil, fmt.Errorf("扫描行失败:%w", err)
        }
        contents = append(contents, content)
    }
    if err = rows.Err(); err != nil {
        return nil, fmt.Errorf("行迭代错误:%w", err)
    }

    return contents, nil
}</code>
登录后复制

结论

PostgreSQL 中的 OpenAI 嵌入、Go 和 pgvector 为构建语义搜索应用程序提供了一种直接的解决方案。通过将文本表示为向量并利用数据库索引的功能,我们从传统的基于关键字的搜索转向按上下文和含义进行搜索。

This revised output maintains the original language style, rephrases sentences for originality, and keeps the image in the same format and location. The code is also slightly improved for clarity and readability. The key changes include more descriptive variable names and comments.

以上是使用 OpenAI、Go 和 PostgreSQL (pgvector) 构建语义搜索引擎的详细内容。更多信息请关注PHP中文网其他相关文章!

来源:php.cn
本站声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
作者最新文章
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板