ARTICLE DETAIL

资讯详情

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

PostgreSQL + pgvector 实现 RAG 语义搜索:从零搭建与调优

PostgreSQL + pgvector 实现 RAG 语义搜索:从零搭建与调优 最近几个月我几乎每周都会被同一个问题打扰“我想给知识库接一个 RAG检索增强生成能力语义搜索是不是得上专门的向量数据库好多教程都在推 pgvector这玩意儿到底行不行”我的答案一直很明确如果你项目里已经有 PostgreSQL而且没有专职 DBA 团队去运维一套独立的向量库那先别折腾新组件。你完全可以在 PostgreSQL 上装一个 pgvector 扩展把向量当成一种普通字段来存、来查、来跟业务 SQL 无缝 join。我这一年里用这套方案做了好几个 RAG 项目从内部文档问答到客户工单智能检索都跑过踩过不少坑也总结了一套从零搭建的完整方法。这篇文章就把整个过程拆开讲清楚覆盖语义搜索的原理、pgvector 的安装选型、RAG 管线的落地、知识库形态的选择以及生产环境调优时最容易翻车的几个点。如果你是第一次接触语义搜索或者正准备给自己的 PostgreSQL 数据库加一个“会理解语义”的检索能力这篇文章可以直接照着操作。已经跑通过基础 RAG 的人也可以重点看第 6 章和第 7 章那部分都是我在真实业务里被数据教做人的经验。1. 为什么先别急着换向量库语义搜索和 RAG 的真实瓶颈1.1 你遇到的其实是“关键词搜索搜不到东西”的问题传统的 PostgreSQL 搜索最常用的就是LIKE、ILIKE和全文检索tsvector。这些方案有一个共同的死穴它们只能做字面匹配做不了意图匹配。举个例子。你在一个菜谱知识库里搜“番茄炒蛋的做法”全文检索能召回所有包含“番茄”“炒蛋”字样的菜谱。但如果你换一种问法比如“西红柿鸡蛋怎么做好吃”这时候你用的是“西红柿”不是“番茄”全文检索大概率就抓瞎了因为它的倒排索引里没有“西红柿”和“番茄”之间的语义关联。再比如企业内部知识库“我们的报销流程是什么”和“费用申请走什么手续”字面上只有“报销”和“费用”这两个词可能沾边但语义上指的是同一件事。这类问题用传统搜索做召回率会低到让人怀疑人生。这就是语义搜索要解决的问题让检索系统理解输入文本的语义相似度而不是字符重合度。而实现这一点的技术底座就是把文本转换成向量。1.2 “嵌入向量”是什么一句话版本嵌入向量embedding本质上是一个浮点数组比如[0.012, -0.034, 0.092, ...]通常有 768 维、1024 维甚至 1536 维。这个数组是由一个嵌入模型把输入文本压缩成的“语义坐标”。你可以把它理解为“把一句话压成一张地图上的坐标点”。语义接近的句子坐标点靠近语义无关的句子坐标点离得远。检索时不需要理解语言只需要计算两个坐标点的距离找到离查询点最近的几个点就行了。而 pgvector 做的事情非常简单粗暴它让 PostgreSQL 多了一种vector数据类型同时提供了一套高效计算向量距离的算法还有加速检索的索引结构。你原来怎么用 JSONB、数组字段就怎么用 vector 字段完全不需要把一个表搬到另一个系统里。1.3 pgvector 与专用向量数据库的真实取舍这几年专用向量数据库确实火Milvus、Weaviate、Qdrant 各有拥趸。但它们在带来检索速度的同时也带来了一整套新问题数据同步、双写一致性、运维监控、故障恢复每一项都是成本。我拿我实际项目的体验做一个对比说实话如果你的数据量在千万级以内pgvector 的差距没有想象中那么大维度pgvector PostgreSQL专用向量数据库运维成本复用既有 PostgreSQL不用引入新组件需要单独部署、监控、备份数据一致性业务数据和向量天然在同一事务里需要处理两个系统之间的同步延迟查询能力向量检索 关系过滤 JOIN 一条 SQL 搞定向量检索强复杂关系查询偏弱大规模能力千万级以内表现稳定亿级需要更多调优分布式能力强适合超大吞吐学习成本会 PostgreSQL 就会用需要学习新 API 和新生态所以我现在的选型原则很固定数据量不大、延迟要求不是极端严格的场景一律 pgvector 起步如果未来真到了亿级向量、需要水平扩展的时候再考虑迁到专用向量库也不迟因为你的语义搜索逻辑、嵌入模型、检索接口是一样的迁移成本可控。2. 环境准备PostgreSQL 版本选择和不同平台安装 pgvector2.1 PostgreSQL 版本怎么选这事别纠结先回答一个高频问题“PostgreSQL 下载哪个版本好”从支持和性能表现看我建议直接用你所在操作系统软件源里能提供的最新稳定版。写这篇文章时PostgreSQL 16 和 17 都是不错的选择。版本太老的两个主要风险一是 pgvector 对旧版本 PG 的支持会逐渐减弱二是新版本在并行查询、WAL 写入、COPY导入上的改进相当明显对向量库这种偏 IO 密集的业务来说收益不小。我见过有些生产库还在用 PG 11 或 PG 12想着“稳”。说实话如果只是普通业务表PG 11 再顶几年也没问题。但你要上 pgvector 做语义搜索我建议至少 PG 13 以上因为 pgvector 的 HNSW 索引在较新版本上配合得更好你之后调试执行计划时也会省事很多。2.2 Windows预编译扩展和 Docker 两条路都给你Windows 上装 PostgreSQL 本身不难官网下载安装器一路下一步就行。真正麻烦的是 pgvector 扩展。它有对应的原生 DLL必须与你安装的 PostgreSQL 主版本号、位数64 位完全匹配不然会报出找不到扩展控制文件的错误。如果你不想折腾最省心的方案是用 Dockerdocker run -d \ --name pgvector-demo \ -e POSTGRES_PASSWORDpostgres \ -p 5432:5432 \ pgvector/pgvector:pg16这个官方镜像已经把 pgvector 编译进去了容器起来之后直接用。日常开发调试我强烈推荐这条路线因为你本地装的 PostgreSQL 和线上版本很容易不一致而镜像可以精确锁定版本号避免了“本地能跑、线上不能跑”的魔幻问题。如果你因为公司网络或安全策略不能跑 Docker那就走预编译二进制方案。去 pgvector 的 GitHub Releases 页面找对应你 PostgreSQL 版本的 zip 包里面会有vector.dll和相关的.control、.sql文件把它们复制到 PostgreSQL 安装目录的share/extension和lib目录下然后在 psql 里执行CREATE EXTENSION vector就能验证是否成功。注意Windows 下 DLL 版本不匹配是最常见的报错来源一定要逐个核对。2.3 Ubuntu 和 Linux 环境apt 和源码编译都要掌握Ubuntu 下最简单的方式是直接用 PostgreSQL 官方提供的 apt 仓库sudo apt install postgresql-16 postgresql-server-dev-16 git clone --branch v0.8.0 https://github.com/pgvector/pgvector.git cd pgvector make sudo make install这里postgresql-server-dev-16是很多人会漏掉的一个包。make编译 pgvector 时需要用到 PostgreSQL 的头文件和pg_config工具如果没装这个开发包编译时会出现找不到postgres.h之类的错误。还有一类场景是离线环境。我在客户现场遇到过完全断网的 Linux 服务器这时候有两个办法一是找一台能联网的同架构机器把 PostgreSQL 的安装包和 pgvector 源码仓库一起打包拷过去。编译时如果系统缺少依赖库也是同样的思路先下载依赖的.deb包拷贝到目标机上用dpkg -i安装。二是直接用 PostgreSQL 的二进制包加扩展的完整目录整体拷贝到同版本、同架构的机器上复用。这个办法走的是“目录级移植”操作更快但对系统库版本有要求搞不好会因为 glibc 版本不一致出问题。我个人更推荐第一种老老实实在离线机器上源码编译虽然多花十分钟但成功率高得多。2.4 macOS 安装步骤macOS 上如果你用 Homebrew先把 PostgreSQL 装好brew install postgresql16 git clone --branch v0.8.0 https://github.com/pgvector/pgvector.git cd pgvector make sudo make install装完之后记得确认一下链接路径。Homebrew 的 PostgreSQL 是 keg-only 的如果你的 PATH 里没有pg_config编译时要用PATH/opt/homebrew/opt/postgresql16/bin:$PATH make这种方式显式指定工具链。还有个更快的办法macOS 上直接用brew install pgvector也可以Homebrew 官方已经收录了 pgvector 的 formula一条命令就搞定适合不想碰源码编译的人。安装完成后进入任意一个数据库执行CREATE EXTENSION vector; SELECT extversion FROM pg_extension WHERE extname vector;能查出版本号就说明环境没问题了。3. 跑通第一次语义搜索建表、嵌入、检索3.1 建表和启用扩展语义搜索本质上仍然是一张表、一个字段、一条查询。我们先创建一张最基础的文档表CREATE EXTENSION IF NOT EXISTS vector; CREATE TABLE docs ( id bigserial PRIMARY KEY, content text NOT NULL, embedding vector(768) );这里的768是我用的本地嵌入模型nomic-embed-text的输出维度。如果你的嵌入模型是 OpenAI 的text-embedding-3-small那就是1536如果是text-embedding-ada-002也是1536。建表时维度必须和模型输出严格一致否则插入时会直接报错。3.2 嵌入向量从哪来这是新手最容易卡住的一环。你需要一个嵌入模型把文本转成向量有两条路线一条是调用 OpenAI、通义等在线服务的 Embedding 接口优点是用起来简单缺点是数据要出网、每次调用要花钱、还要维护密钥。另一条是用本地模型我主力用的是 Ollama 加nomic-embed-text。它跑在自己机器上离线可用嵌入效果在中文短文本上的表现也够用。用 Python 生成向量非常简单import requests def get_embedding(text: str) - list[float]: resp requests.post( http://localhost:11434/api/embeddings, json{model: nomic-embed-text, prompt: text} ) resp.raise_for_status() return resp.json()[embedding]这里值得提醒一句嵌入模型的稳定性和可复现性很重要。一旦你选定了某个模型做数据入库后面查询阶段也要用同一个模型。如果你上线后把模型从nomic-embed-text换成另一个老数据的向量空间和新查询的向量空间不兼容语义检索会直接变成随机检索这是个隐蔽但致命的坑。3.3 插入向量数据有了嵌入函数插入就变得很朴素了。我一般用psycopg2批量插入import psycopg2 conn psycopg2.connect( hostlocalhost dbnameyourdb userpostgres passwordpostgres ) texts [ 番茄炒蛋是一道家常菜做法简单适合新手。, 西红柿鸡蛋汤需要在最后撒上葱花提香。, 报销差旅费需要提交发票和行程单。, 员工请假流程先在系统里提交申请再由主管审批。, ] with conn.cursor() as cur: for text in texts: embedding get_embedding(text) cur.execute( INSERT INTO docs (content, embedding) VALUES (%s, %s), (text, embedding) ) conn.commit()数据量大的时候同样可以用COPY或INSERT INTO ... SELECT ...的高效方式导入。小规模学习阶段executemany就足够了不用一开始就把工程优化做过头。3.4 三种距离度量选哪个pgvector 提供三种距离运算这是初学者最容易搞混的地方距离算法运算符特点与适用场景欧氏距离-范数差异敏感适合图像向量等幅度有意义的场景余弦距离只看方向、不管幅度文本语义匹配首选内积距离#通常配合向量归一化使用对高维稀疏数据有效文本语义搜索领域我建议直接用余弦距离。因为它把“频率”和“语义”解耦了一段长文本和一段短文本只要语义方向一致得分就不会因为长度差异被压得太低。一条语义搜索查询长这样SELECT id, content, 1 - (embedding $1::vector) AS similarity FROM docs ORDER BY embedding $1::vector LIMIT 5;返回的是“距离”距离越小越相似。如果你想让数值直观一点可以像上面这样转成“相似度”1 - 距离。3.5 建索引不然表一大会死给你看没有索引时pgvector 会老老实实地做全表扫描把每一行的向量都算一遍距离再排序取前 5。数据量小的时候看不出来一旦到几十万行一次查询可能就是数百毫秒甚至秒级。所以要在检索之前先把索引建好。pgvector 支持两种索引我推荐优先考虑 HNSWCREATE INDEX ON docs USING hnsw (embedding vector_cosine_ops);注意这里vector_cosine_ops要和查询时使用的距离运算符匹配。你用就必须配vector_cosine_ops用-就配vector_l2_ops用#就配vector_ip_ops。配错会导致索引无法使用。4. 用 pgvector 组装一套真实可用的 RAG 管线4.1 文档预处理拆分粒度决定了检索质量的上限很多 RAG 项目效果不好问题不在大模型也不在向量库而是文档没有拆好。我见过两种极端一种是把整个 PDF 当成一条记录存进去结果向量维度平均化了查询向量只跟其中最接近的一小段相似由于其他部分拉远了距离整体相似度得分偏低另一种是把文档切成两三句话的小段结果每个 chunk 携带的信息量太小上下文不完整召回的段落看起来相关但缺少前因后果。我的经验是通用文档按 500 到 800 字切分重叠区域设 100 字左右这样既有足够的上下文又不会因为 chunk 太大降低检索精度。具体公式是chunk_size取 2 到 3 倍的嵌入模型最大输入 token 数的三分之一然后再根据你实际业务的句子密度微调。另外拆分之后要记得把原始文档 ID、段落序号、标题路径一起存进表里方便以后追溯答案来源。RAG 系统一定要能回答“这段内容出自哪个文档”否则没法过审也没法审计。4.2 入库把切好的段落写进 docs 表下面是我在实际项目里常用的建表结构CREATE TABLE doc_chunks ( id bigserial PRIMARY KEY, doc_id bigint NOT NULL, chunk_index int NOT NULL, title text, content text NOT NULL, embedding vector(768), created_at timestamptz DEFAULT now() ); CREATE INDEX ON doc_chunks USING hnsw (embedding vector_cosine_ops);入库脚本和上一章的插入逻辑几乎一样只是多循环一层先对文档做分块再逐块生成向量最后批量写入。这一步建议放到离线任务里跑不阻塞用户的实时查询。4.3 检索向量召回加元数据过滤RAG 的检索不只有一个向量条件。生产环境里我们通常还会加过滤条件比如“只查 2024 年之后的文档”“只查技术类文档”等。pgvector 在这里特别顺手因为它归根结底还是 SQL。你可以把向量相似度、关系条件、全文检索等写进同一条查询SELECT doc_id, title, content, 1 - (embedding $1::vector) AS similarity FROM doc_chunks WHERE doc_id IN (SELECT doc_id FROM docs WHERE doc_type manual) AND created_at 2024-01-01 ORDER BY embedding $1::vector LIMIT 8;这套组合拳是专用向量数据库很难给到的既有向量语义检索又有传统结构化查询还不用把数据复制到另一个系统里做 join。这就是我坚持用 pgvector 的根本原因。4.4 生成把召回的段落拼进 Prompt 交给大模型检索只是前半场RAG 的后半场是把召回结果组合起来喂给大模型生成答案。一个最简单的完整 RAG 函数如下def answer_question(question: str) - str: # 1. 将问题向量化 question_emb get_embedding(question) # 2. 从 PostgreSQL 检索 top_k 内容 with conn.cursor() as cur: cur.execute( SELECT title, content FROM doc_chunks ORDER BY embedding %s::vector LIMIT 5 , (question_emb,)) chunks cur.fetchall() # 3. 拼装上下文 context \n\n.join( f[来源{title}]\n{content} for title, content in chunks ) # 4. 组装 prompt 并调用本地大模型 prompt f请根据以下资料回答问题。 如果资料内容不足以回答请直接说明“资料中没有相关信息”不要编造。 问题{question} 资料 {context} resp requests.post( http://localhost:11434/api/generate, json{model: qwen2.5:7b, prompt: prompt, stream: False} ) return resp.json()[response]这个函数虽然短但已经是完整的 RAG 闭环。核心思想是“先检索到证据再让模型根据证据回答”。你不需要让大模型硬记所有知识也不需要每次重新训练知识更新只需要重新清洗、拆分、嵌入、入库即可。4.5 用框架折腾还是自己写我的看法是分阶段如果你刚入门我的建议是先用上面这种原生写法跑通一个最小流程把概念搞清楚。不要一上来就引 LangChain、LlamaIndex因为这些框架的抽象层级很高一个问题背后会牵扯到好几个概念出了问题你根本不知道是嵌入模型的错还是检索器的错还是 prompt 模板的错。当你理解整个链路之后再决定要不要引框架。真入了生产环境LangChain 和 LlamaIndex 都提供了 pgvector 作为向量存储的集成可以直接对接省去不少样板代码。但从可控性角度讲我至今还是偏向自己维护一层简单的检索接口因为框架升级时最容易把向量配置参数悄悄改掉那才是生产事故的温床。5. 知识库形态的边界向量 RAG、结构知识库和知识图谱5.1 三类知识库的能力差异很多人会把“RAG 知识库”理解成一个筐什么都能装。实际上知识库的形态直接影响检索方式和答案质量。我按实战用途做了个对比形态检索方式典型场景优势短板向量 RAG 知识库向量相似度检索文档问答、客服、知识库搜索非结构化文本无需人工整理精度有限需要辅助手段结构知识库SQL、精确匹配财务数据、订单、人员信息高精度、强事务性数据结构化成本高知识图谱KG知识库图查询、多跳推理多实体关系的问答比如供应链推理路径清晰可解释性好构建工作量很大最典型的错误是把结构化的数据比如订单金额、库存数量硬塞进向量库里然后问“上个月销售额是多少”向量库根本算不出精确聚合结果。这种场景就应该走 SQL或者用 Text-to-SQL 把自然语言翻译成 SQL 查询。向量库负责非结构化的语义召回SQL 负责精确数字计算两者是配合关系不是替代关系。5.2 图片到底能不能存进 RAG 知识库这是我在后台收到的高频问题“RAG 知识库能存储图片吗”能但要分清楚存什么。图片本身是二进制大文件你不该把图片内容直接当作vector塞进 PostgreSQL。更合理的做法有两种第一种是存图片的“语义描述”。把图片交给多模态模型生成一段文字描述再把这段描述转成向量入库图片文件本身放到对象存储里数据库里只存 URL 路径。查询时通过文字描述来召回返回结果时给用户图片链接。这是成本最低、最稳的路线也是市面上大多数“看图搜索”产品的实际实现。第二种是直接存多模态向量。如果你用的是支持图文对齐的模型比如 CLIP 类图片和文本可以编码到同一个向量空间那就可以把图片的向量直接存在vector字段里和文本向量一起检索。但这种方案对模型的要求更高维护成本也更高一般场景用第一种就够了。5.3 什么时候值得做知识图谱 RAG纯向量 RAG 在实体关系复杂、需要多跳推理的场景下会明显力不从心。举个例子“有哪些供应商同时给 A 项目和 B 项目供货”这个问题如果你把所有合同文本切块做向量检索大概率召回的段落只有涉及 A 项目或 B 项目的片段模型要跨段落推理 A 和 B 的公共供应商这是向量检索不擅长的。这种场景适合用知识图谱 RAG先把供应商、项目、合同抽成实体和关系存入图数据库回答问题时先用向量召回相关实体再沿着图谱做多跳查询把路径证据交给大模型生成答案。pgvector 在这个架构里的位置仍然是“召回入口”负责把自然语言问题映射到图谱中的候选实体但真正的推理由图查询完成。我这里再说清楚知识图谱 RAG 不是替代向量 RAG而是补位。图谱构建成本很高只有当你确定高频问题需要多跳推理时才值得建设。6. 性能与质量调优把语义搜索从“能跑”变成“好用”6.1 HNSW 和 IVFFlat到底选谁pgvector 提供两种索引HNSW 是近些年主流向量索引它基于“小世界图”的思想检索时从图中随机找入口节点逐步收敛到最近邻区域。它构建慢、占用空间大但查询延迟低、召回率高不需要预先聚类适合大部分生产场景。IVFFlat 则是先对全量数据做聚类把向量分成若干 list 桶查询时只搜索距离最近的几个桶。它在小数据集上表现不错但有两个明显前提数据要相对稳定且要定期ANALYZE否则桶分布和实际数据脱节召回率会明显下跌。我给你的选型建议非常简单粗暴数据量几十万到千万级无脑用 HNSW。只有当你的 HNSW 内存占用实在扛不住或者检索延迟要求特别苛刻时再回头考虑 IVFFlat。HNSW 建索引语句CREATE INDEX ON doc_chunks USING hnsw (embedding vector_cosine_ops) WITH (m 16, ef_construction 64);m控制每个节点的最大连接数值越大精度越高但内存越多ef_construction控制建索引时的搜索范围越大索引质量越高但构建越慢。这两个参数不需要一上来就调得很大默认值在小数据集上就能跑得不错等数据量上来了再做针对性压测。6.2 查询速度慢先查执行计划别盲目调参我在帮朋友排查性能问题时最常见的情况是索引建了但查询根本没走索引因为执行计划选择了全表扫描。排查方式很简单EXPLAIN ANALYZE SELECT id, content FROM doc_chunks ORDER BY embedding $1::vector LIMIT 10;如果看到Seq Scan on doc_chunks说明你的查询条件让优化器认为全表扫描更便宜。常见原因有两个一是表里数据量太小优化器觉得走索引反而更慢二是你的距离算子和索引的ops不匹配比如你用-查却建了vector_cosine_ops的索引优化器用不上索引只能扫表。另外还有一个容易被忽略的点加了WHERE过滤条件后优化器可能先按过滤条件扫描再在结果集上做距离排序这时候如果过滤后的结果集比较大性能依然不行。我的做法是控制过滤条件的选择性如果一定要过滤给过滤字段也建上普通 B-tree 索引并让执行计划走“索引过滤 向量再排序”的路线。6.3 召回率不够怎么办混合检索和 rerank向量检索再强也会有犯傻的时候。比如你搜“如何申请发票”向量检索可能会召回大量讲“发票类型”“发票格式”的段落而没召回真正讲“申请流程”的那一段。这时候要解决的不是向量搜索引擎而是检索策略。我目前的检索策略是组合拳第一路向量相似度检索召回 top 20。第二路PostgreSQL 全文检索tsvector召回 top 20。两路结果做去重合并然后用一个重排序模型rerank model对合并结果打分取 top 5 作为最终上下文。全文检索负责字面匹配向量检索负责语义匹配两者互补。重排序模型模型量级一般较小但能把“表面上像、实际上偏题”的结果压下去。这一步对 RAG 回答质量的提升是肉眼可见的尤其是应用在客服问答和内部知识库时错误率能降一半以上。还有一个门槛更低但同样有效的方法召回后用小模型直接做一遍关键词过滤。比如先要求召回的段落必须包含问题里的核心实体词如果都不包含再退回到纯向量结果。这在冷启动阶段比引一个 rerank 模型更省事。7. 一点踩坑复盘与私人经验7.1 版本不匹配最常见的“CREATE EXTENSION”失败我见过太多人卡在CREATE EXTENSION vector这一步报错错误信息是could not open extension control file。十次里九次是 pgvector 的版本和 PostgreSQL 主版本不匹配你装的是 PG 16但下载的 pgvector 是编译给 PG 15 的或者你在 Windows 上复制 DLL 时装错了位数。解决思路很简单先把 PostgreSQL 的版本号记死再去对应版本号找扩展包。别用“大概是最新版”这种心态装环境编译型扩展对版本强敏感。7.2 向量字段别开得太大维度太多会撑爆 PostgreSQL嵌入模型的输出维度直接决定了表的存储膨胀速度。768 维比 1536 维少一半空间但模型能力强很多两者在检索效果上的差距并没有那么大。我在生产项目里已经不止一次把 OpenAI 的 1536 维向量换成轻量模型效果差不多但数据库的磁盘占用和构建索引时间明显下降。如果你确实需要 1536 维可以考虑 pgvector 0.7.0 之后的halfvec类型它是半精度浮点存储能把内存占用降一半。代价是精度略降适合对召回的细微差别不敏感的场景。7.3 切片策略不要照搬模板要按文档结构来最后一个经验是我花了最多学费换来的文档切片大小没有万能答案。在技术文档里标题、表格、代码块都是天然的语义边界把它硬按 500 字切往往会切断一个完整的表格召回的段落残缺不全。我现在的一般做法是先按文档结构提取标题层级以章节为单位切块超过上限的章节再往下按段落拆并保留所属标题的上下文。这样召回的 chunk 自带“出处”配合大模型也更不容易产生幻觉。另外强调一点插入之后如果数据更新频繁记得定时做ANALYZE让优化器看到新的统计信息。很多线上检索变慢的问题最后查出来的原因不是索引坏了而是统计信息太久没更新优化器做了错误的选择。这套方案从最开始搭建到现在我评估过不少替代品最终还是留在 PostgreSQL 加 pgvector 的组合上。它并不完美但它足够日常也足够让人把精力放在真正重要的事情上数据清洗、切片、召回策略和提示词设计。如果你正准备从一个简单的文档问答开始做语义搜索我建议你也从这个组合开始别一上来就把架子搭得太重。
返回列表