各位在折腾RAG(检索增强生成)应用、语义搜索或者企业内部知识库的朋友,应该都有过类似的纠结:Doc内容切好了,向量化模型选哪个?向量存哪里?网上搜索“bge-small-zh和pgvector配合使用”,出来一堆零散的文章,但能把整个链路串起来讲清楚的并不多。今天这篇就来填这个坑,从模型选型、环境部署,到建表SQL、索引调优,再到踩坑排错,一次性把这套组合方案聊透。
这套方案能解决的问题非常具体:把你手头的文档、FAQ、工单记录等文本内容,切成小块后交给bge-small-zh模型转成向量,再存进带pgvector扩展的PostgreSQL数据库里。之后用户提问时,同样用bge-small-zh把问题转成向量,在数据库里做相似度检索,把最相关的文本片段捞出来喂给大模型,大模型再基于这些材料组织回答。整个过程不需要单独部署一套Milvus或者Elasticsearch,就靠一个现成的PostgreSQL就能扛住千万级向量规模,对于中小型项目和个人开发者的知识库场景来说,性价比极高。
这篇文章适合谁?第一类是后端工程师,手里已经有业务系统在用PostgreSQL,想在最小改动的前提下给系统加上语义搜索能力;第二类是AI应用开发者,想找一个离线部署、不依赖外部API、数据可控的向量检索方案;第三类是还在各种方案之间观望的决策者,看完这篇文章你基本能判断这套组合是否适合你的业务场景。
1. 这套组合方案的整体设计思路
1.1 为什么偏偏是bge-small-zh?
bge系列是智源研究院开源的中文文本向量模型,目前在中文语义检索任务里基本属于第一梯队。bge-small-zh作为该系列的小尺寸版本,最大的优势是“够用且不贵”。
先看一组在业界广泛引用的对比数据:bge-small-zh的语义表征维度是512维,生成的向量占用空间是1024维模型(如bge-large)的四分之一。小尺寸模型带来的直接好处是推理速度快、内存占用低,在纯CPU环境下单条文本向量化延迟可以控制在20毫秒以内,这对实时问答场景非常重要。如果你用的是GPU(哪怕是一张消费级的RTX 3060),配合ONNX Runtime或者sentence-transformers框架,批量处理几千条文档的向量化任务分分钟就能跑完。
另外,bge-large-zh虽然精度上限更高,但很多人忽略了后面还有一句“效果提升约2%”。这2%的差距在真实业务场景里基本感知不到,但算力开销和存储成本却成倍上涨。我的习惯是:先用small版本把整个pipeline跑通,如果后续评估发现召回质量真的存在明显短板,再平滑切换到large版本,无非就是重跑一次向量化脚本、更新数据库里的向量字段而已。
还有一个重要细节:bge系列模型官方提供了中文词表优化和针对检索任务的对比学习训练,所以它对中文长文本和口语化表达的支持明显强于直接用多语言模型或者早期基于BERT蒸馏出来的向量模型。实测下来,bge-small-zh对中文近义词、同义句的判别能力相当能打,比如“怎么退货运费险”和“退货险如何理赔”这种表达方式差异很大但语义接近的句子,相似度得分依旧能稳定在0.8以上。
1.2 为什么选pgvector而不是单独部署向量数据库
用pgvector最核心的理由只有一个:少一个组件,就少一个故障点,少一份运维成本。
很多团队一提到语义搜索就想着上Milvus、Weaviate、Qdrant这些专业向量数据库,但这类系统通常需要独占一组机器资源,需要单独的监控告警体系,需要写专门的数据同步逻辑来保证向量库和业务库的业务字段保持一致。对于每天新增几百条文档的企业内部知识库,这完全是在给系统做加法。
pgvector在现有PostgreSQL体系内实现了向量类型的存储和检索,让向量数据跟普通业务数据存在同一个库里,天然解决了几个痛:第一,不用维护两套数据的一致性,业务字段和向量字段在同一个事务里提交,要么都成功,要么都失败;第二,可以直接用SQL做复杂的混合过滤,比如“先按部门字段过滤出某个团队的文档,再在结果集里做向量相似度排序”,这在独立向量数据库里实现起来非常麻烦,往往要把过滤条件拼进metadata里再走前置过滤,但在pgvector里就是一条JOIN语法的事;第三,PostgreSQL生态里现成的备份、权限、高可用方案全部可以直接复用,不用为向量库另起炉灶。
至于性能,pgvector在百万级向量规模下,配合HNSW索引,单次查询延迟能做到毫秒级。绝大多数知识库类应用的真实数据量也就是几十万到几百万条切分后的文本块,pgvector完全扛得住。真到了千万级以上规模,再考虑迁移到专业向量数据库也不迟。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 环境准备与核心配置
2.1 Docker方式安装PostgreSQL和pgvector
用Docker是起步最快的方式,一条命令就能拉起一个带pgvector扩展的实例,推荐这种方式做前期验证。如果你在正式环境里已经有了PostgreSQL 14或更高版本(15、16也支持),也可以直接用CREATE EXTENSION vector来启用扩展,不需要额外安装。
bash复制docker run -d \
--name pgvector-demo \
-e POSTGRES_USER=testuser \
-e POSTGRES_PASSWORD=testpass \
-e POSTGRES_DB=knowledge \
-p 5432:5432 \
pgvector/pgvector:pg16
这里有一个非常容易踩的坑:官方镜像的tag格式是pgvector/pgvector:pgXX,后面的数字必须跟你需要的PostgreSQL主版本号严格对应。如果项目里用的是PostgreSQL 15,就拉pgvector/pgvector:pg15,拉错版本会导致扩展安装后无法加载,报错信息类似could not open extension control file。
启动容器后,等几秒钟让数据库完成初始化,然后进入容器执行建扩展命令:
bash复制docker exec -it pgvector-demo psql -U testuser -d knowledge
sql复制CREATE EXTENSION IF NOT EXISTS vector;
执行成功后,可以用SELECT * FROM pg_available_extensions WHERE name = 'vector';检查状态,看到版本号就代表环境已经就绪。
2.2 使用Python环境安装依赖
向量化部分推荐用FlagEmbedding框架,它是智源官方维护的工具库,接口设计得非常顺手。也可以选择sentence-transformers,两个框架底层模型是同一个,按个人习惯来选。
bash复制pip install FlagEmbedding sentence-transformers pgvector psycopg2-binary
这里提一句,bge系列官方推荐在sentence-transformers中开启query_instruction_for_retrieval这个参数,也就是给query加上“为这个句子生成表示以用于检索相关文章:”这个指令前缀。在FlagEmbedding框架里,如果是做不对称检索(用短query去搜长文档),建议给query侧也拼接上这个指令,而文档侧不需要。这个细节容易被忽略,我专门放到后面第4节细讲,因为对检索效果的影响不是一点点。
安装完依赖后,建议先跑一段快速验证代码,确认模型能正常下载和推理:
python复制from FlagEmbedding import FlagModel
model = FlagModel("BAAI/bge-small-zh-v1.5")
sentences = ["如何申请退货", "退货流程是什么", "今天的天气很好"]
embeddings = model.encode(sentences)
print(embeddings.shape) # 期望输出: (3, 512)
第一次运行会从HuggingFace下载模型权重,国内网络如果下载不稳定,可以设置环境变量HF_ENDPOINT=https://hf-mirror.com切换国内镜像源。模型文件不大,大概100MB左右,下载完成后会缓存到本地目录,后续推理不需要联网。
2.3 验证模型和扩展的版本兼容性
在正式开始搭建之前,有两件事值得确认清楚:一是模型的输出维度必须是512,建表时字段类型要写vector(512),维度不匹配会在插入时报错;二是确认pgvector扩展能正常加载。
顺便说一个经验技巧:我们可以把模型加载逻辑封装成一个独立模块,这样后续在数据预处理脚本和应用服务里复用,不需要重复加载模型。尤其要注意的是,bge-small-zh是纯离线模型,数据不会上传到任何第三方接口,这在国内企业应用场景里很重要,毕竟文档内容很多时候属于敏感数据,离线推理意味着数据不出内网。
3. 从零到一实现一个完整的知识库检索链路
3.1 设计表结构:向量和业务字段怎么放
先设计一张名为doc_chunks的表,用于存储文档切分后的文本块。
sql复制CREATE TABLE doc_chunks (
id BIGSERIAL PRIMARY KEY,
doc_name TEXT NOT NULL, -- 来源文档名,比如"产品手册.pdf"
chunk_index INT NOT NULL, -- 文档内的块序号
chunk_text TEXT NOT NULL, -- 切分后的文本内容
chunk_embedding vector(512), -- 向量字段,维度512
created_at TIMESTAMP DEFAULT NOW(),
meta_info JSONB -- 自定义元数据,比如作者、部门、标签等
);
CREATE INDEX ON doc_chunks
USING hnsw (chunk_embedding vector_cosine_ops);
创建HNSW索引时有两个地方需要解释清楚。
第一行代码里用的是USING hnsw,对应的是pgvector 0.5.0版本之后引入的HNSW索引类型。在pgvector里做相似度检索的索引一共有两种选择:IVFFlat和HNSW。IVFFlat的思路是建索引时先把向量划分成多个聚类中心,查询时只搜索距离聚类中心最近的几个列表,速度快但精度会有损失,而且要求建索引前表里已经有足够多的数据来训练聚类中心。HNSW的思路是构建一个多层近邻图,从顶层开始逐层下探查找,查询速度快,召回率高,而且索引可以在数据插入的过程中动态构建,对数据量没有硬性要求。在2024年之后的pgvector版本里,在线业务场景无脑选HNSW就对了,除非你的数据量级到了亿级别且插入频率极高,IVFFlat的写性能和内存占用优势才值得重新考虑。
第二行代码的vector_cosine_ops指定了索引用的距离度量方式,这个必须跟查询SQL里的计算方式保持一致。pgvector支持三种距离:欧氏距离(L2)、余弦距离(Cosine)、内积(Inner Product)。对于文本向量检索,余弦距离是最经典的选择。cords之前也提过,这里再强调一遍:bge模型生成的向量虽然经过了归一化,但官方推荐用vector_cosine_ops或查询时用<=>运算符,实测下来稳定性最好。
另外要特别提醒一下,vector类型有个维度上限限制,默认最大是2000维。bge-small-zh是512维,完全没压力。但如果后续切换到某些开源大模型生成的输出向量(比如某些模型输出4096维),就需要在初始化时调整vector的维度上限了。目前用bge小模型的话不用操心这个问题。
3.2 文档切分:不同场景下的chunk策略
文档预处理是知识库建设中最容易被轻视的环节,但恰恰决定了检索召回质量的上限。如果文本切太碎,单块信息不完整,语义表达不清晰;如果切太大块,单块里包含多个主题,向量会被拉成一个模糊的平均向量,精度反而下降。
我的通用规则是:技术文档、维护手册这类结构化文本文档,按固定字符数切分,推荐块大小在500字左右,重叠(overlap)设置为50-100字。重叠部分能有效避免一句话被硬生生截断成两半导致两边都语义不完整的情况。对于问答对(FAQ)这类短文本,每条问答单独作为一个chunk即可,不需要跨条切分,因为每一条本身已经是语义完整的最小单元。
实际操作中建议直接使用LangChain的RecursiveCharacterTextSplitter,或者自行实现一个基于段落边界(换行符、句号、分号)的切分器。Python实现并不复杂:
python复制def split_text(text, chunk_size=500, overlap=50):
paragraphs = text.split("\n")
chunks = []
current = ""
for para in paragraphs:
if len(current) + len(para) < chunk_size:
current += "\n" + para
else:
chunks.append(current.strip())
current = current[-overlap:] + "\n" + para
if current.strip():
chunks.append(current.strip())
return chunks
这个实现简单但实用,至少比你直接用text.split("。")要科学得多。核心思想是“尽量在段落边界处断句,保持语义完整性”,别干那种硬生生把一句话截成两半的傻事。
切分完成后,给每一块文本编一个序号chunk_index,后面排查问题时可以快速定位是哪个切分块导致了检索错误。
3.3 批量向量化写入:任务进度和断点续跑
数据处理脚本需要遍历文档库里的所有文档,对每一块文本调用模型生成向量,然后插入数据库。下面给出一段完整的参考实现,融合了进度显示、错误重试和批量提交三个实用特性:
python复制from FlagEmbedding import FlagModel
from pgvector.psycopg2 import register_vector
import psycopg2
model = FlagModel("BAAI/bge-small-zh-v1.5")
conn = psycopg2.connect(
host="localhost",
port=5432,
user="testuser",
password="testpass",
database="knowledge"
)
register_vector(conn)
cursor = conn.cursor()
def embed_and_insert(doc_name, chunks):
vectors = model.encode(chunks, batch_size=32, normalize_embeddings=True)
for idx, (text, vec) in enumerate(zip(chunks, vectors)):
cursor.execute(
"""
INSERT INTO doc_chunks (doc_name, chunk_index, chunk_text, chunk_embedding)
VALUES (%s, %s, %s, %s)
ON CONFLICT DO NOTHING
""",
(doc_name, idx, text, vec)
)
conn.commit()
chunks = split_text(open("产品手册.md").read())
embed_and_insert("产品手册.md", chunks)
这里有几个细节要交代清楚。
register_vector(conn)这一行非常重要,它会在psycopg2连接上注册向量类型的适配器,否则把Python list写入vector字段会报数据类型不支持。
normalize_embeddings=True这个是bge官方特别强调的参数。模型在训练阶段做了向量归一化,推理时你最好也保持一致,这样余弦相似度计算等价于内积计算,影响的是后续距离度量的稳定性。
批量处理时建议把batch_size设为32或者64。我实测过,FlagEmbedding在CPU模式下batch_size设太大并不会显著加速,反而可能跑一段时间就内存溢出,在16GB内存的台式机上batch_size=32是稳定且效率较高的配置。如果你处理的数据量很大,建议在脚本里加个进度条并记录已处理的文档名,支持断点续跑,不然跑到一半进程崩了要重头再来,心态容易崩。
3.4 检索查询:写出一条语义搜索SQL
数据进库之后,查询就非常简单了。用户的问题转成向量,在SQL里做相似度排序,取前K条:
python复制def search(query, top_k=5):
query_vec = model.encode([query], normalize_embeddings=True)[0]
cursor.execute(
"""
SELECT doc_name, chunk_index, chunk_text,
1 - (chunk_embedding <=> %s) AS similarity
FROM doc_chunks
ORDER BY chunk_embedding <=> %s
LIMIT %s
""",
(query_vec, query_vec, top_k)
)
results = cursor.fetchall()
for row in results:
print(f"相似度: {row[3]:.4f}, 来源: {row[0]}#{row[1]}")
print(row[2][:100] + "...")
return results
<=>运算符在pgvector里表示余弦距离,注意是“距离”,值越小代表越相似,所以ORDER BY chunk_embedding <=> %s要升序排列。为了给业务方展示更直观的“得分”,我在SELECT里用1 - 距离把它转换成了相似度得分,这样在界面上显示“相似度0.87”这种数字时,运维团队不需要再做一次翻译。
如果你还需要“只搜索某个来源文档的子集”这种过滤需求,直接改WHERE条件即可:
sql复制WHERE doc_name = '产品手册.md'
ORDER BY chunk_embedding <=> %s
pgvector会先过滤再排序,而且在索引层面也做了优化,这种混合过滤的查询性能依旧不错。
3.5 接入大模型完成RAG闭环
检索出来了,最后一步就是把检索结果拼成Prompt,交给大模型生成回答。这里给出一个简洁但完整的RAG调用示例:
python复制def rag_answer(query):
results = search(query, top_k=5)
context = "\n\n".join([r[2] for r in results])
prompt = f"""基于以下资料回答问题,如果资料中找不到答案,请明确说明。
资料:
{context}
问题:{query}
回答:"""
# 这里换成你自己的大模型API调用
# response = openai.ChatCompletion.create(...)
# return response["choices"][0]["message"]["content"]
return prompt
值得留意的细节是:拼接参考资料时建议把相似度得分较高的放在前面。像ChatGPT这类大模型对上下文中的信息位置有敏感性,把更相关的资料往前放,能明显提升回答准确性。另外,如果某条检索结果的相似度低于0.5,我一般会选择直接丢弃而不放进上下文里,因为低相关度内容混进去反而会干扰大模型的判断,这个阈值可以根据实际数据微调。
4. 中文场景下的关键调优与问题排查
4.1 检索效果不给力,问题可能出在指令前缀
bge系列的README里提到,对于带有短查询(query)和长文档(document)的不对称检索场景,在query侧加上指令前缀“为这个句子生成表示以用于检索相关文章:”可以显著提升检索效果。需要特别注意的细节是:在sentence-transformers框架里,对query编码时要用model.encode(query, max_length=512),并且手动加上这个前缀字符串;对文档编码时则不加。
使用FlagEmbedding框架的话就更省事了,写query时直接手动拼接:
python复制query = "为这个句子生成表示以用于检索相关文章:" + user_query
query_vec = model.encode([query], normalize_embeddings=True)[0]
你可能会想,这个前缀真的能带来质的改变吗?我实测过一个内部客服知识库,不加前缀时检索的Top 5准确率约78%,加上前缀后提升到了84%,提升幅度不小。核心原因在于bge模型在训练阶段就使用该指令作为query侧的统一前缀,你推理时对齐了训练时的分布,自然效果更稳定。
4.2 向量维度不匹配导致插入失败
如果你在运行插入脚本时遇到这个报错:
code复制ERROR: column "chunk_embedding" is of type vector but expression is of type character varying
通常不是维度的问题,而是pgvector适配器没有注册成功。解决方案就一行:
python复制from pgvector.psycopg2 import register_vector
register_vector(conn)
如果你遇到的是维度错误,表现通常是different vector dimensions,说明模型输出的维度不是512。可能是下载的模型版本不对(比如误下载了bge-m3模型,它的输出维度是1024),或者代码里模型的加载路径写错了。用embeddings.shape确认一下当前模型的实际维度,跟表结构里的vector(n)对齐即可。
4.3 索引没有生效,查询走了全表扫描
数据量大了之后,查询变慢是必然的。慢SQL基本都能归到一个原因:索引没建对或者查询没有用到索引。可以用EXPLAIN ANALYZE查看执行计划:
sql复制EXPLAIN ANALYZE
SELECT doc_name, 1 - (chunk_embedding <=> '[0.01, 0.02, ...]') AS similarity
FROM doc_chunks
ORDER BY chunk_embedding <=> '[0.01, ...]'
LIMIT 5;
如果执行计划里出现Seq Scan,说明走了全表扫描,没有命中HNSW索引。常见原因有:
一是HNSW索引只对余弦距离算子vector_cosine_ops生效,如果你在CREATE INDEX时用了默认的vector_l2_ops,而SQL里用的是<=>(余弦),那索引就完全用不上。
二是表里数据量太少,PostgreSQL优化器认为直接全表扫描比走索引便宜。这种情况属于“正常但不合理”——如果表里只有几千条数据,全表扫描确实就是毫秒级,不用纠结。等数据量上来了执行计划自然会转变。
如果你确认数据量已经较大(比如十万级以上)但执行计划仍是Seq Scan,还有一个冷门原因:HNSW索引的max_scan_tuples参数或者新版本中的hnsw.iterative_scan设置影响了优化器的决策。最直接的解决办法是把enable_seqscan临时关掉再做对比测试:
sql复制SET enable_seqscan = off;
如果强制走索引后的查询时间跟全表扫描差别不大,说明系统就算走索引性能也没有优势,可能是索引配置有问题(比如m参数太小)。此时第一原则是“相信执行计划”,可以重建一个m=32的索引试试看。
4.4 相似度分数虚高,0.95以上的结果依然不相关
这种坑我踩过好几次。表面看相似度达到0.95(换算成余弦相似度),但打开文本一看跟问题八竿子打不着。排查下来基本是这两个原因:
一是文档切分出现了问题。比如split_text函数没有正确处理空行,导致多个不相关段落被拼到了一个chunk里,向量被压成了一个谁都像、谁都不像的平均值。解决办法是检查切分后的chunk_text,如果发现一个chunk里有多个明显不相关的主题,就减小chunk_size或者增大overlap。
二是query侧没有加指令前缀。前面提过,不加前缀的bge模型在检索场景下表征能力会下降,惯常表现为相似度普遍偏高但不具备区分度。
4.5 数据规模较大时的索引和性能调优参考
顺手整理一份我常用的调优参考表,给不同数据量阶段的同学一个方向性指引:
| 数据规模(向量条数) | 索引类型 | 关键参数建议 | 期望查询延迟 |
|---|---|---|---|
| < 1万 | 可不建索引 | 无 | < 10ms |
| 1万 - 50万 | HNSW | m=16, ef_construction=64 | 10-30ms |
| 50万 - 200万 | HNSW | m=32, ef_construction=128 | 20-50ms |
| > 200万 | HNSW或IVFFlat | IVFFlat lists=数据量/1000 | 视参数而定 |
关于HNSW的两个参数,简单解释下作用:m表示每个节点的最大连接数,值越大图的连通性越好,召回率越高,但索引体积和内存占用也越大;ef_construction表示索引构建时的动态候选列表长度,值越大建索引耗时越长,但索引质量更高。建议按照官方文档推荐,在召回率和写入性能之间取一个平衡点。
4.6 业务层考虑:如何优雅处理数据库故障
最后提一个容易被忽略的工程问题。pgvector作为PostgreSQL的扩展,并没有特殊的容灾机制,它完全依赖PostgreSQL主从复制、备份恢复来保证数据安全。这也意味着你的业务层必须具备数据库故障切换的能力。如果你把向量数据放在独立的pgvector实例里,那么这个实例的高可用方案要跟你业务库保持一致,别上头图方便直接单机裸奔。安全侧我补一句:bge模型完全本地推理,向量数据和原始文本不出内网,这在金融、政务、医疗等对数据出境敏感的场景里应用价值很大,建议在上汇报材料时可以作为方案亮点写入。
5. 进阶玩法与扩展思路
5.1 用混合检索弥补纯向量的不足
向量检索虽然能理解语义,但在精确匹配上不如关键词检索。比如用户搜“订单号: SO-2024-00123”,向量模型会把这段文本编码成一团语义向量,跟文档里完全相同的字符串反而可能不算特别相似的匹配。解决思路是引入BM25全文检索引擎做混合检索。
PostgreSQL自带的全文检索(tsvector)就能实现基础版关键词匹配,不需要额外引入外部搜索引擎。你可以用tsvector字段存关键词索引,然后在应用层做一个线性加权融合:最终得分 = 0.7 × 向量相似度 + 0.3 × BM25相关度。权重系数可以根据业务场景调优。RAG应用目前比较流行的做法就是“BM25召回 + 向量召回 + RRF权重融合”,既能提升精确匹配能力,又不会丢掉语义检索的优点。
5.2 向量存储空间的压缩手段
bge-small-zh输入向量是512维float类型,每条向量占用2KB存储空间。假设你有100万条chunk,向量字段就要占约2GB的空间,加上HNSW索引(索引体积一般是数据的1.1倍到3倍),总占用可能在5-6GB。听起来不多,但如果数据量涨到千万级,磁盘和内存压力还是可观的。
pgvector支持vector类型转换为halfvec(半精度向量),存储空间直接减半。如果你接受的精度损失不大(实测影响一般低于1%),把字段类型从vector(512)切换成halfvec(512),索引也重建一下,可以显著降低存储和内存占用:
sql复制ALTER TABLE doc_chunks
ALTER COLUMN chunk_embedding TYPE halfvec(512)
USING chunk_embedding::halfvec(512);
需要特别确认的是,只有pgvector 0.7.0版本以上才支持halfvec类型。在切换前做好备份,如果是生产环境,先在测试库验证一遍所有查询SQL的兼容性。
5.3 多维元数据过滤与权限隔离
前文提到meta_info JSONB字段,实际业务中它的作用很大。举个例子:某集团内部有多个部门共用一个知识库系统,但市场部的人不应搜到财务部的内部文档。使用pgvector的好处是,这种过滤可以直接下推给数据库,在SQL里加条件即可:
sql复制SELECT ...
FROM doc_chunks
WHERE meta_info->>'department' = 'market'
ORDER BY chunk_embedding <=> %s
LIMIT 5;
配合PostgreSQL的行级安全策略(RLS),你甚至可以在数据库层面实现审计级别的权限隔离,不需要在业务代码里写一堆if-else判断。这一点是独立向量数据库很难优雅做到的。
6. 一点实际的运维体会
这套方案我前前后后在两个项目里落地过,一个是为零售行业做的商品问答助手,数据量约30万条chunk,运行在4核8G的云服务器上,查询延迟稳定在30ms左右;另一个是某企业内部IT运维知识库,数据量约8万条,问的问题五花八门,从“投影仪连不上电脑”到“域账号怎么重置密码”,bge-small-zh的召回效果都令人满意,加上HNSW索引后数据库负载一直在可忽略的水平。
有个体会想分享给正在做技术选型的朋友:不要一股脑去追最新的大模型和独立的向量数据库。很多场景下,把现有PostgreSQL利用起来,配合一个合适的embedding模型,已经能覆盖80%以上的需求。pgvector的价值不在于单点性能多么极致,而在于“数据在哪里,向量就在哪里”——不用在两套系统间搬运数据,事务、权限、备份统统复用PostgreSQL成熟的生态能力,这套组合的稳定性远比新技术堆砌出来的架构靠谱。
最后留个扩展思路:如果你现在手头已经有一个文档库或者知识库系统,下一步可以考虑给每个文本chunk额外打上时间戳,然后在查询SQL里加一个created_at的过滤条件,就能轻松实现“只检索最近30天的知识”。这种需求在维护更新频繁的行业(比如跨境电商的售后条款、游戏版本的更新公告)里非常实用。方法都很简单,改动也不大,值得一试。
