LangChain Go + PGVector 向量存储实战:基于 OpenAI Embeddings 构建语义相似度搜索
发布时间:2026/9/16 14:46:57 锦皓数字建站

LangChain Go PGVector 向量存储实战基于 OpenAI Embeddings 构建语义相似度搜索【免费下载链接】langchaingoLangChain for Go, the easiest way to write LLM-based programs in Go项目地址: https://gitcode.com/GitHub_Trending/la/langchaingo导读本文围绕 examples/pgvector-vectorstore-example 这个官方示例展开完整讲解如何在 Go 应用中集成 pgvectorPostgreSQL 的向量相似度搜索扩展与 OpenAI Embeddings实现文档入库、相似度检索、分数阈值过滤和元数据过滤等能力。读完本文你将掌握 langchaingo 中vectorstores/pgvector包的完整使用链路并能直接复制示例代码搭建一套可运行的语义搜索服务。示例整体目标从「数据库」到「向量数据库」pgvector 是 PostgreSQL 的扩展它让传统关系型数据库具备了存储和检索高维向量的能力从而支撑语义搜索semantic search与相似度匹配similarity matching类 AI/机器学习应用。本示例展示的是一条完整的生产级路径用 Docker 启动内置 pgvector 扩展的 PostgreSQL容器启动时自动创建并启用vector扩展初始化 OpenAI Embeddings 客户端依赖环境变量OPENAI_API_KEY创建 PGVector Store连接 PostgreSQL完成表结构与集合collection的自动初始化写入带元数据的示例文档以全球城市为样本每条文档包含城市名、人口population与面积area执行多种相似度检索基础检索、带分数阈值检索、分数阈值 元数据过滤组合检索。这套流程对应 示例源码三个运行步骤在 README 中均有明确说明docker compose up -d启动数据库、export OPENAI_API_KEYyour key配置密钥、go run pgvector_vectorstore_example.go运行程序。第一步用 Docker 搭建带 pgvector 的 PostgreSQL示例目录中的 docker-compose.yml 定义了一个名为db的服务将容器 5432 端口映射到宿主机并预设了三组连接凭据version: 3.9 services: db: build: dockerfile: postgres.Dockerfile restart: always ports: - 5432:5432 environment: POSTGRES_PASSWORD: testpass POSTGRES_USER: testuser POSTGRES_DB: testdbPOSTGRES_USER/POSTGRES_PASSWORD/POSTGRES_DB对应示例代码连接串中的testuser:testpasslocalhost:5432/testdbrestart: always保证容器异常退出后自动重启适合本地开发与演示场景。扩展是如何被「自动启用」的关键在于 postgres.Dockerfile 的两步设计FROM postgres:16 RUN apt-get update \ apt-get install -y --no-install-recommends postgresql-16-pgvector \ apt-get clean \ rm -rf /var/lib/apt/lists/* RUN mkdir -p /docker-entrypoint-initdb.d ADD create_extension.sql /docker-entrypoint-initdb.d基础镜像为postgres:16通过 apt 安装与 PostgreSQL 16 匹配的postgresql-16-pgvector扩展包create_extension.sql 内容只有一行create extension if not exists vector;被放入官方镜像约定的初始化目录/docker-entrypoint-initdb.d因此在容器首次启动时 Postgres 会自动执行它完成vector扩展的创建。需要说明的是即使容器初始化阶段未创建该扩展示例程序运行时也不会因此失败——pgvector.New内部会自动执行CREATE EXTENSION IF NOT EXISTS vector详见下文源码解析双重保障保证了开箱即用。启动命令为docker compose up -d第二步初始化 OpenAI Embeddings示例使用 llms/openai 包创建 OpenAI 客户端再包装为 Embedderllm, err : openai.New() if err ! nil { log.Fatal(err) } e, err : embeddings.NewEmbedder(llm) if err ! nil { log.Fatal(err) }openai.New()会从环境变量读取OPENAI_API_KEY因此运行前必须执行export OPENAI_API_KEYyour keyembeddings.NewEmbedder将 LLM 适配为embeddings.Embedder接口该接口提供EmbedDocuments与EmbedQuery两个方法分别服务于「批量文档向量化」和「单条查询向量化」。第三步创建 PGVector Store使用pgvector.New创建向量存储核心参数是数据库连接串与 Embedderctx : context.Background() store, err : pgvector.New( ctx, pgvector.WithConnectionURL(postgres://testuser:testpasslocalhost:5432/testdb?sslmodedisable), pgvector.WithEmbedder(e), ) if err ! nil { log.Fatal(err) }从 vectorstores/pgvector/options.go 的源码可以看出除了示例用到的两个选项Store 还支持更丰富的自定义配置Option作用默认值WithConnectionURL(url)指定 Postgres 连接串与WithConn二选一无二者必填其一WithConn(conn)直接传入pgx.Conn/pgxpool.Pool等连接对象适合复用连接池无WithEmbedder(e)设置 Embedder必填无WithCollectionName(name)指定集合collection名称langchainWithEmbeddingTableName(name)指定向量表名langchain_pg_embeddingWithCollectionTableName(name)指定集合表名langchain_pg_collectionWithPreDeleteCollection(bool)初始化前是否清空同名集合便于重复实验falseWithCollectionMetadata(map)为集合附加元数据无WithVectorDimensions(size)显式指定向量维度如 1536否则建表时不约束维度0不限定WithHNSWIndex(m, efConstruction, distanceFunction)创建 HNSW 近似最近邻索引参数与 pgvector 官方语义一致不创建 HNSW 索引以示例代码为准时WithConnectionURL中的连接串与 docker-compose 中预设的testuser/testpass/testdb完全对应且通过sslmodedisable关闭 TLS 校验以简化本地连接。第四步向向量库写入带元数据的文档AddDocuments接收[]schema.Document每条文档由正文PageContent与键值对元数据Metadata组成_, err store.AddDocuments(context.Background(), []schema.Document{ { PageContent: Tokyo, Metadata: map[string]any{ population: 38, area: 2190, }, }, { PageContent: Paris, Metadata: map[string]any{ population: 11, area: 105, }, }, // London、Santiago、Buenos Aires、Rio de Janeiro、Sao Paulo 等城市 })示例共写入 7 个全球城市其中南美城市Santiago、Buenos Aires、Rio de Janeiro、Sao Paulo在后续检索中被作为「南美城市」语义组的样本。写入成功后函数返回每条文档生成的 UUID 列表。写入路径的源码细节查看 vectorstores/pgvector/pgvector.go 中AddDocuments的实现可以发现先调用s.deduplicate应用可选的去重器通过vectorstores.WithDeduplicater传入用embedder.EmbedDocuments批量生成向量并校验向量数量与文档数量一致否则返回ErrEmbedderWrongNumberVectors使用pgx.Batch批量执行INSERT INTO langchain_pg_embedding (uuid, document, embedding, cmetadata, collection_id) VALUES($1,$2,$3,$4,$5)每条记录通过pgvector.NewVector包装向量数据需要留意的是AddDocuments不支持分数阈值、过滤与命名空间选项传入会返回ErrUnsupportedOptions见 pgvector.go。第五步三种相似度检索模式示例依次演示了三种检索能力均通过store.SimilaritySearch(ctx, query, k, options...)完成1. 基础相似度检索docs, err : store.SimilaritySearch(ctx, japan, 1) fmt.Println(docs)查询文本japan会被 Embedder 向量化与库中所有文档的向量计算余弦相似度embedding $2即余弦距离算子返回最相似的一条。由于示例文本均为城市名语义上japan最接近的自然是 Tokyo。2. 带分数阈值的检索docs, err store.SimilaritySearch(ctx, only cities in south america, 10, vectorstores.WithScoreThreshold(0.80)) fmt.Println(docs)vectorstores.WithScoreThreshold(0.80)表示只返回相似度得分不低于 0.80 的结果。在 pgvector.go 中分数阈值会被转换为 SQL 条件data.distance 1 - scoreThreshold即「余弦距离小于 0.20」。阈值必须是 [0, 1] 之间的浮点数否则返回ErrInvalidScoreThreshold。3. 分数阈值 元数据过滤组合检索filter : map[string]any{area: 1523} // Sao Paulo docs, err store.SimilaritySearch(ctx, only cities in south america, 10, vectorstores.WithScoreThreshold(0.80), vectorstores.WithFilters(filter), ) fmt.Println(docs)vectorstores.WithFilters接收元数据过滤条件。从源码看pgvector 的过滤目前仅支持简单的键值对模式pgvector.go 中注释明确说明每条键值对被转换为(data.cmetadata - area) 1523的 JSON 字段提取比较语句多个条件用AND连接。示例中以area1523精确定位到 Sao Paulo配合「南美城市」语义查询与 0.80 阈值最终精准返回该条文档。当前实现尚不支持{key: {key2: value2}}嵌套或{key: [v1,v2]}多值过滤源码中留有 TODO。源码级解析Store 初始化与底层数据模型为了让读者对示例背后的机制有完整认知这里深入剖析 vectorstores/pgvector/pgvector.go 中New的初始化流程连接建立后调用store.init在单个事务内依次执行createVectorExtensionIfNotExists先通过pg_advisory_xact_lockadvisor lock加锁再执行CREATE EXTENSION IF NOT EXISTS vector。加锁目的是防止多个实例并发建扩展时互相干扰锁 ID 与 Python langchain 的 pgvector 实现保持一致见 pgvector.gocreateCollectionTableIfNotExists创建集合表langchain_pg_collection字段包括name varchar、cmetadata json、uuid主键name唯一createEmbeddingTableIfNotExists创建向量表langchain_pg_embedding字段包括collection_id外键关联集合表ON DELETE CASCADE、embedding vector、document varchar、cmetadata json、uuid主键若通过WithVectorDimensions指定了维度则建表时以vector(维度数)形式限定随后还会创建 collection_id 索引以及可选的 HNSW 索引createOrGetCollection以ON CONFLICT (name) DO UPDATE SET cmetadata $3的 upsert 方式插入或更新集合记录并查询出该集合的 UUID 供后续写入使用。这套「集合表 向量表」的双表模型与 Python langchain 的 pgvector 实现同源保证了多集合隔离与按集合检索的能力。SimilaritySearch最终生成的 SQL 会先用vector_dims(embedding) $1过滤出维度匹配的记录再按余弦距离升序排序并LIMIT返回同时将距离换算为相似度分数(1 - data.distance) AS score填充到文档的Score字段。如何验证配套测试与运行环境仓库在 vectorstores/pgvector/pgvector_test.go 中提供了完整的集成测试可作为理解与验证示例行为的参考TestPgvectorStoreRest写入 tokyo / potato 两条文档检索japan断言返回 tokyo 且元数据country为japan与示例「查询 japan 命中 Tokyo」的逻辑一致TestPgvectorStoreRestWithScoreThreshold、TestSimilaritySearchWithInvalidScoreThreshold分别验证分数阈值检索与非法阈值报错测试若未设置PGVECTOR_CONNECTION_STRING环境变量会自动通过 Testcontainers 拉起docker.io/pgvector/pgvector:pg16容器见 pgvector_test.go与示例的 Docker 方案殊途同归测试目录 testdata 中还沉淀了去重TestDeduplicater、检索器TestPgvectorAsRetriever与各组合检索模式的 httprr 录制数据。依赖方面示例 go.mod 显示核心依赖为github.com/tmc/langchaingov0.1.14-pre.4、jackc/pgx/v5与pgvector/pgvector-goGo 版本要求 1.24.3 及以上。示例作为独立模块运行不依赖仓库根 go.mod。运行前检查清单与常见注意事项数据库就绪先执行docker compose up -d并等待容器初始化完成首次启动会执行create_extension.sql密钥配置OPENAI_API_KEY必须已导出否则openai.New()会报错连接串一致性连接串中的用户名、密码、库名必须与 docker-compose 的环境变量一致示例默认三者为testuser/testpass/testdb且sslmodedisable重复运行示例未开启WithPreDeleteCollection(true)因此重复执行会不断向同一集合追加文档若希望每次运行前清空集合可参考 pgvector_test.go 的做法为 Store 增加该选项元数据过滤限制目前仅支持map[string]any形式的键值对等值过滤值会被拼接进 SQL因此应使用程序内部可控的元数据避免拼接不可信输入引入注入风险。延伸从示例走向真实应用示例虽小却完整覆盖了 RAG检索增强生成类应用的向量化基础设施将 pgvector 与 langchaingo 的 retrievers 结合即可让SimilaritySearch的结果直接作为上下文喂给 LLM构成「文档入库 → 语义检索 → 生成回答」的闭环。示例目录中的 postgres.Dockerfile 与 docker-compose.yml 亦可直接复用到开发环境甚至生产镜像构建实现向量检索能力的快速落地。【免费下载链接】langchaingoLangChain for Go, the easiest way to write LLM-based programs in Go项目地址: https://gitcode.com/GitHub_Trending/la/langchaingo创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。