向量数据库迁移实录:从 FAISS 到 Weaviate 的那些坑
最近因为需要支持多用户知识库检索,我开始琢磨向量数据库的替换方案。原来系统里用的是本地的 FAISS,搭配 LangChain 用起来还挺顺,但随着需求复杂起来,比如想加 hit\_count 记录、分页、精确查某个文件的所有块,FAISS 显然就有点捉襟见肘了。
🥊 为什么考虑换掉 FAISS?
简单说两点:
- FAISS 不支持原生 filter 查询,像我要查某个
kb_id
的内容,或模糊查某个 filename 的所有内容,只能自己手动过滤; - 没有内置分页。每次都得查全量,再手动分页,这在数据量大时非常不友好。
于是我决定换掉 FAISS,试试 Weaviate。
🔁 切换 Weaviate 的过程
✅ 基本查询
Weaviate 的向量检索写法和 FAISS 有点类似,只是接口风格更“声明式”一点。最开始试了下这样一段代码:
results = collection.query.near_vector(
near_vector=query_vector,
limit=5,
filters=Filter.by_property("kb_id").equal(kb_id)
)
这一段是查询向量最相近的内容,且只筛选 kb_id
为指定值的记录。比起 FAISS 自己 filter 一堆,Weaviate 这点真的太方便了。
📈 查询命中的内容并计数
接下来加了一个需求:一旦某个块被召回了,就把它的 hit_count
字段 +1。这其实也很方便:
for item in results.objects:
collection.data.update(
uuid=item.uuid,
properties={"hit_count": item.properties.get("hit_count", 0) + 1}
)
注意这里 item.uuid
是默认带的,不需要你在 return_metadata
里特地加上(我一开始就踩了这个坑)。
🧩 模糊查询 + 分页
后来又要实现:用户选择一个知识库、一个文件名,分页查看里面所有块,还能模糊查内容。
一开始我尝试构造 filters:
filters = {
"operator": "And",
"operands": [
{"path": ["kb_id"], "operator": "Equal", "valueText": kb_id},
{"path": ["filename"], "operator": "Equal", "valueText": filename},
{"path": ["text"], "operator": "Like", "valueText": f"*{query}*"} # 模糊
]
}
但后来我发现 Weaviate 新版 SDK 提供了更优雅的 Filter 方式:
from weaviate.classes.query import Filter
filters = (
Filter.by_property("kb_id").equal(kb_id)
& Filter.by_property("filename").equal(filename)
& Filter.by_property("text").like(f"*{query}*") # 支持模糊
)
这个组合可以直接传给 collection.query.fetch_objects(...)
方法,分页也只需设置 limit
和 offset
:
offset = (page - 1) * pageSize
result = collection.query.fetch_objects(
filters=filters,
limit=pageSize,
offset=offset,
return_properties=["text", "hit_count", "upload_time", "filename", "kb_id"]
)
❌ 踩坑记录
不能插入 id 和 vector 到 properties
批量插入时我傻傻地把id
和vector
放进了properties
字典里,Weaviate 直接报错:It is forbidden to insert
id
orvector
inside properties...后来我改成使用
DataObject
的方式解决了。return\_metadata=["uuid"] 会报错
我以为想取 UUID 要写return_metadata=["uuid"]
,结果报了个特别诡异的错误:Extra inputs are not permitted [type=extra\_forbidden, input\_value=True, input\_type=bool]
实际上根本不需要那一行,
.uuid
默认就能取。- Filter 的链式调用坑
一开始我写了个Filter.all(...)
组合多个 filter,结果提示Filter
根本没这个方法。正确的写法是用&
链接多个Filter.by_property(...)
,就像上面示例。
💡 写在最后
整体迁移下来,Weaviate 在功能和可扩展性方面明显优于 FAISS,尤其是在过滤器、属性更新、分页这些业务场景上更灵活。如果你正好也面临类似的向量检索多条件查询、知识块统计更新的需求,不妨试试换到 Weaviate。