ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

用 pgai Vectorizer 自动化 AI 嵌入:从文本列到语义搜索的声明式向量管理

用 pgai Vectorizer 自动化 AI 嵌入:从文本列到语义搜索的声明式向量管理 用 pgai Vectorizer 自动化 AI 嵌入从文本列到语义搜索的声明式向量管理【免费下载链接】pgaiA suite of tools to develop RAG, semantic search, and other AI applications more easily with PostgreSQL项目地址: https://gitcode.com/GitHub_Trending/pg/pgai本文基于 pgai 仓库中的 Vectorizer 概览文档编写完整讲解如何用一条 SQL 声明式地为 PostgreSQL 表的文本列建立、维护并查询向量嵌入embedding涵盖嵌入提供商与 API Key 配置、ai.create_vectorizer各参数loading、embedding、formatting、chunking、indexing、destination、scheduling的实操用法、语义搜索查询写法、嵌入存储表结构与状态监控。读完后你可以直接在自己的数据库中落地一个自动同步的向量索引并能结合仓库源码理解触发器、队列表与后台 worker 的底层实现。一、Vectorizer 解决什么问题向量嵌入vector embedding把文本压缩成富含语义的数值向量使意思相近但用词完全不同的内容也能被检索到这是传统关键词搜索做不到的。PostgreSQL 这类向量数据库擅长存储和查询这些向量但真正的痛点在于让向量与源数据保持同步源行插入、更新、删除时对应的嵌入需要被生成、更新和清理而这通常要靠开发者自己写应用层代码维护。pgai Vectorizer 把嵌入当成一种声明式的、类似 DDL 的数据库特性来处理类比索引但更灵活可以只对行中的一部分数据建立嵌入。你在数据库里执行一条SELECT ai.create_vectorizer(...)系统就帮你指定任意文本列通过可定制的规则作为嵌入来源如果是嵌入 PDF 等二进制文档则参考文档中的文档嵌入指南自动生成并维护可搜索的嵌入表让嵌入与源数据异步持续保持同步提供一个把基表与嵌入无缝 JOIN 起来的视图。为了让嵌入生成高效且能扛住 LLM 端点偶发故障嵌入生成本身由一个后台 worker执行在托管服务如 Timescale Cloud的数据库中创建 Vectorizer 后worker 自动运行并在后台创建和同步嵌入在自托管 Postgres或其他云厂商的 RDS 等上则需要你自己运行vectorizer worker 来处理各 vectorizer。从源码结构看整个数据流是ai.create_vectorizer定义在 012-vectorizer-api.sql在源表上创建触发器和队列表行变更事件写入队列workerPython 侧实现在 worker.py轮询队列执行读取 → 分块 → 格式化 → 嵌入 → 写入流水线。触发器与队列表的构建逻辑见 011-vectorizer-int.sql 中的_vectorizer_build_trigger_definition和_vectorizer_create_queue_tableINSERT/UPDATE 时把主键值插入队列表UPDATE 时仅当相关列发生变化才入队DELETE 时直接从目标嵌入表删除对应行TRUNCATE 时同时清空目标表和队列表。这也解释了为什么嵌入与源数据能自动保持一致——同步的记账全部在数据库内完成worker 只负责消耗队列。要快速体验可参考Vectorizer quick start提供预置的 Docker 开发环境完整的技术规格见 Vectorizer API reference。二、选择嵌入提供商并配置 API KeyVectorizer 目前以一等公民身份支持以下嵌入提供商OllamaVoyage AIOpenAI此外通过 LiteLLM 提供商还支持CohereHuggingFace Inference EndpointsMistralAzure OpenAIAWS BedrockVertex AI使用外部嵌入服务需要配置 API Key。可以存储多个 Key给每个 Key 起一个名字然后在 Vectorizer 配置的embedding段中引用它。默认 Key 名与提供商默认名一致ProviderKey 名OpenAIOPENAI_API_KEYVoyage AIVOYAGE_API_KEY源码印证了这些默认值004-embedding.sql 中ai.embedding_openai的api_key_name参数默认即OPENAI_API_KEYai.embedding_voyageai默认VOYAGE_API_KEYai.embedding_litellm则允许通过api_key_name自定义默认 null由 LiteLLM 自身解析。四种嵌入配置构造器为ai.embedding_openai(model, dimensions, chat_user, api_key_name, base_url) ai.embedding_ollama(model, dimensions, base_url, options, keep_alive) ai.embedding_voyageai(model, dimensions, input_type, api_key_name) -- input_type 只能是 query 或 document ai.embedding_litellm(model, dimensions, api_key_name, extra_options)Key 的具体设置方式取决于部署形态Timescale Cloud在 Timescale Console 的项目设置页点击AI Model API Keys点击Add AI Model API Keys填入 Key 后点击Add API key。此时 API Key 安全地存储在 Timescale Cloud 侧而不是你的数据库里。自托管 Postgres设置一个与 Key 名相同的环境变量供 vectorizer worker 进程读取例如export OPENAI_API_KEYYour OpenAI API key三、定义 Vectorizer先看一个示例源表CREATE TABLE blog( id SERIAL PRIMARY KEY, title TEXT, authors TEXT, contents TEXT, metadata JSONB );让系统为这张表自动生成并更新嵌入只需一条 SQLSELECT ai.create_vectorizer( blog::regclass, name blog_embeddings, -- 可选的自定义名称便于引用 loading ai.loading_column(contents), embedding ai.embedding_ollama(nomic-embed-text, 768), destination ai.destination_table(blog_contents_embeddings) );该示例使用本地 Ollama 实例上的nomic-embed-text模型。其他提供商的嵌入配置写法见 API reference 的 embedding 配置一节。各配置段的作用与底层细节均对应 idempotent SQL 文件 中的函数定义loading008-loading.sql指定嵌入数据来源。ai.loading_column(contents)从contents列读取文本ai.loading_uri则从 S3 等本地或远程桶加载外部文档。注意loading_column还有一个retries参数默认值为 6加载失败重试次数源码中的类型校验要求列类型为text、varchar、char、bpchar或bytealoading_uri另支持aws_role_arn参数用于以指定 IAM 角色访问 S3校验格式必须为arn:aws:iam::%:role/%。更完整的文档嵌入方案见文档嵌入指南。embedding004-embedding.sql嵌入模型与维度。chunking001-chunking.sql分块策略。默认实现为ai.chunking_character_text_splitter参数默认值为chunk_size 800、chunk_overlap 400、separator E\n\n另有ai.chunking_recursive_character_text_splitter按[\n\n, \n, ., ?, !, , ]逐级分隔符递归切分和ai.chunking_none。indexing、scheduling、destination见后文各节。分块的意义如果contents字段很长它会被拆成多个 chunk一篇博客文章就会产生多条嵌入。分块保证每条嵌入语义自洽——可以把它理解为一次嵌入一个段落。但切分也可能丢失上下文缓解办法是用formatting参数把行数据重新注入每个 chunk见下一节。命名规则源码 010-destination.sql 的_evaluate_destination函数当destination_table只传一个位置参数时系统将其作为destination自动派生出目标存储表名destination_store本例即blog_contents_embeddings_store和同名视图blog_contents_embeddingstarget_schema默认为源表所在 schema。调度行为在 Timescale Cloud 上vectorizer 由 TimescaleDB 后台任务自动创建并按每 5 分钟一次的周期调度运行自托管场景下需要手动运行 vectorizer-worker 来创建并驱动 vectorizer。四、向 Vectorizer chunk 注入上下文分块会丢失上下文比如你可能希望每个 chunk 都带上文章的标题和作者。formatting参数使用Python 模板字符串实现这一点模板可以访问源行的所有列外加一个包含 chunk 文本的特殊变量$chunk。SELECT ai.create_vectorizer( blog::regclass, loading ai.loading_column(contents), embedding ai.embedding_ollama(nomic-embed-text, 768), formatting ai.formatting_python_template($title - by $author - $chunk), destination ai.destination_table(blog_contents_embeddings) );默认格式字符串就是$chunk。注意如果注入了标题等前缀格式化后的文本变长可能需要调小chunk_size以确保格式化后的文本仍在 token 限制内。源码层面002-formatting.sql有两处值得知道的硬校验模板必须包含$chunk占位符否则create_vectorizer直接抛异常源表不能存在名为chunk的列因为视图会把源表的chunk列与嵌入侧的chunk字段混淆。五、查询嵌入语义搜索create_vectorizer会生成一个与view_name同名的视图本例为blog_contents_embeddings其中包含 blog 表的全部嵌入。由于通常每条源文档对应多个 chunk因此每篇博客在视图里会有多行。视图包含 blog 表的所有列另加以下列列名类型说明embedding_uuidUUID嵌入的唯一标识chunkTEXT被嵌入的文本片段embeddingVECTOR该 chunk 的向量表示chunk_seqINTchunk 在文档内的序号从 0 开始找与查询最接近的嵌入用这条标准 SQLSELECT chunk, embedding query embedding as distance FROM blog_contents_embeddings ORDER BY distance LIMIT 10;运算符计算查询向量与每行嵌入向量的距离这是最简的语义搜索写法。也可以在数据库内部为查询文本生成嵌入pgai 的 PostgreSQL extension 提供了ai.ollama_embed等函数。结合元数据过滤视图携带源表所有列任意加 WHERE 条件即可。例如按部门过滤假设metadata为 JSONBSELECT chunk, embedding query embedding as distance FROM blog_contents_embeddings WHERE metadata-department finance ORDER BY distance LIMIT 10;按作者搜索同理SELECT chunk, embedding query embedding as distance, author FROM blog_contents_embeddings WHERE author Bulgakov ORDER BY distance LIMIT 10;SQLAlchemy 用法如果应用侧用 SQLAlchemy 映射模型可以用vectorizer_relationship声明嵌入关系例如class Wiki(Base): __tablename__ wiki id: Mapped[int] mapped_column(primary_keyTrue) url: Mapped[str] title: Mapped[str] text: Mapped[str] # 为 text 字段添加向量嵌入关系 text_embeddings vectorizer_relationship( target_tablewiki_embeddings, dimensions384 )然后按余弦距离排序做语义检索async def _find_relevant_chunks(client: ollama.AsyncClient, query: str, limit: int 2) - WikiSearchResult: response await client.embed(modelall-minilm, inputquery) embedding response.embeddings[0] with Session(engine) as session: result session.query( Wiki, Wiki.text_embeddings.embedding.cosine_distance(embedding).label(distance) ).join(Wiki.text_embeddings).order_by( distance ).limit(limit).all() return result查询上还可以叠加任意其他过滤条件。六、提升查询性能为嵌入列建向量索引在嵌入列上建向量索引可显著改善查询性能。在 Timescale Cloud 上当向量数据达到 10 万行后会自动创建 vectorscale 索引该行为可配置也可以显式指定其他索引类型。例如使用 HNSW 索引SELECT ai.create_vectorizer( blog::regclass, loading ai.loading_column(contents), embedding ai.embedding_ollama(nomic-embed-text, 768), formatting ai.formatting_python_template($title - by $author - $chunk), indexing ai.indexing_hnsw(min_rows 100000, opclass vector_l2_ops), destination ai.destination_table(blog_contents_embeddings) );源码层面005-indexing.sql可用的索引配置函数有ai.indexing_none()不建向量索引ai.indexing_default()默认策略由 GUC 参数ai.indexing_default解析为 diskann 或 hnswai.indexing_hnsw(min_rows 100000, opclass vector_cosine_ops, m, ef_construction, create_when_queue_empty true)min_rows表示目标表达到该行数才建索引默认 100000opclass默认vector_cosine_ops。需要注意当前源码中opclass的校验白名单为vector_ip_ops、vector_cosine_ops、vector_l1_ops选用其他 opclass如vector_l2_ops时请以你所安装版本的支持情况为准ai.indexing_diskann(min_rows, storage_layout, num_neighbors, search_list_size, ...)storage_layout只接受memory_optimized或plain。索引由后台任务周期性触发创建具体决策逻辑在 011-vectorizer-int.sql 的_vectorizer_should_create_vector_index先检查索引是否已存在再看create_when_queue_empty默认 true——队列非空则跳过避免在数据还在大量写入时建索引随后统计目标表行数用LIMIT min_rows的高效计数达到min_rows才执行建索引。真正建索引的_vectorizer_create_vector_index还会先抢一个针对目标表 OID 的事务级 advisory lock防止并发进程重复建索引。注意索引依赖周期性运行的后台任务因此若调度被禁用自托管安装的默认状态索引不会自动创建需要手动建立。七、控制 Vectorizer 的运行时机在 Timescale Cloud 上用**调度scheduling**控制 vectorizer 何时运行一个计划任务定期检查是否有待处理工作有则触发云函数去嵌入数据。默认使用 TimescaleDB 后台任务周期为 5 分钟。当表足够大后调度还会顺带处理嵌入列的索引创建。自托管时vectorizer worker 用轮询机制检查是否有工作因此调度既不需要也被默认禁用。源码印证003-scheduling.sql 提供ai.scheduling_none()、ai.scheduling_default()由 GUCai.scheduling_default解析默认落到 none和ai.scheduling_timescaledb(schedule_interval 5m, initial_start, fixed_schedule, timezone)。再次强调调度禁用时索引不会自动创建需要手动建。八、嵌入存储表视图基于一张存储嵌入的表上例中名为blog_contents_embeddings_store。直接查询这张表而不是视图在某些场景下更高效。表结构为CREATE TABLE blog_contents_embeddings_store( embedding_uuid UUID NOT NULL PRIMARY KEY DEFAULT gen_random_uuid(), id INT, -- 引用 blog 表的主键 chunk_seq INT NOT NULL, chunk TEXT NOT NULL, embedding VECTOR(768) NOT NULL, UNIQUE (id, chunk_seq), FOREIGN KEY (id) REFERENCES public.blog(id) ON DELETE CASCADE );这与源码_vectorizer_create_target_table011-vectorizer-int.sql动态生成的 DDL 一致embedding_uuid主键默认gen_random_uuid()、源表主键列本例为id支持复合主键、chunk_seq、chunk、embedding vector(维度)以及(主键列, chunk_seq)的唯一约束——它保证了同一源行的同一 chunk 不会产生重复嵌入重放队列也不会出错。源行删除由触发器从目标表级联清除。两种嵌入存储目的地Vectorizer 支持两种方式存储嵌入选择依据是因为分块需要每行多个嵌入最常见→ 选table目的地只需要每行一个嵌入源文本很短或上游已完成分块、源表存的就是 chunk→ 选column目的地。1. Table 目的地默认创建独立的嵌入表 与源表 JOIN 的视图SELECT ai.create_vectorizer( blog::regclass, name blog_vectorizer, loading ai.loading_column(contents), embedding ai.embedding_ollama(nomic-embed-text, 768), destination ai.destination_table( target_schema public, target_table blog_embeddings_store, view_name blog_embeddings ), );适用场景每行需要多个嵌入分块需要拆分的大文本字段正在向量化文档文档通常必须分块。2. Column 目的地直接把嵌入列加到源表上。这只适用于 vectorizer 不做分块的场景——要求源数据与嵌入一一对应适合源文本较短例如上游管道已分好块的情况。工作流程应用往表里插入数据时嵌入列为 NULLvectorizer 读行、生成嵌入后回填该列。SELECT ai.create_vectorizer( product_descriptions::regclass, name product_descriptions_vectorizer, loading ai.loading_column(description), embedding ai.embedding_openai(text-embedding-3-small, 768), chunking ai.chunking_none(), -- column 目的地必须 destination ai.destination_column(description_embedding) );适用场景严格需要每行恰好一个嵌入不需要分块的短文本应用入库前已完成分块不想创建额外的数据库对象。注意column 目的地必须配合ai.chunking_none()。源码 010-destination.sql 的_validate_destination函数会强制这一约束否则抛出chunking must be none for column destination且_vectorizer_add_embedding_column会先检查列是否已存在已存在则只发 NOTICE 跳过重复执行create_vectorizer不会报错。九、监控 Vectorizer由于嵌入是异步创建的在写入源数据到嵌入可用之间存在延迟。用vectorizer_status视图查看状态SELECT * FROM ai.vectorizer_status;示例输出idsource_tabletarget_tableviewpending_items1public.blogpublic.blog_contents_embeddings_storepublic.blog_contents_embeddings1pending_items表示仍等待生成嵌入的条目数。为避免在大积压时逐行计数拖慢查询当积压超过 10,000 时该值直接返回 bigint 最大值9223372036854775807而不是精确计数。源码印证012-vectorizer-api.sql 的ai.vectorizer_queue_pending非精确模式下用SELECT count(*) FROM (SELECT 1 FROM 队列表 LIMIT 10001)结果若为 10001 即替换为9223372036854775807另外视图对pending_items做了权限保护——当前用户无队列表 SELECT 权限时该列显示为 NULL。如需精确值调用ai.vectorizer_queue_pending函数并传exact_count true默认falseselect ai.vectorizer_queue_pending(1, exact_counttrue);该函数还有按 vectorizer 名称的重载exact_count参数同上。另外worker 进程的运行状态心跳计数、成功/错误计数、最后错误信息等记录在ai.vectorizer_worker_process与ai.vectorizer_worker_progress表中相关函数定义见 013-worker-tracking.sql可用于排查 worker 层面的故障。十、小结与延伸阅读pgai Vectorizer 的核心价值在于把嵌入与源数据同步这一工程负担从应用层收进了数据库内声明一条create_vectorizer之后触发器负责记账INSERT/UPDATE/DELETE/TRUNCATE 全覆盖、队列负责解耦、worker 负责调用嵌入模型你只需面对一张 JOIN 视图写语义搜索 SQL。进一步学习建议按以下顺序Vectorizer quick startDocker 环境下的快速上手Vectorizer API reference全部配置段与函数的完整规格vectorizer worker自托管部署下 worker 的安装与配置文档嵌入指南PDF 等二进制文档的嵌入流程配合ai.loading_uri。【免费下载链接】pgaiA suite of tools to develop RAG, semantic search, and other AI applications more easily with PostgreSQL项目地址: https://gitcode.com/GitHub_Trending/pg/pgai创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表