Guide · 使用教程
Turbopuffer 混合检索教程:BM25、向量召回、RRF 与权限验收
用官方 Python SDK 以公开演示文档搭建 Turbopuffer 混合召回,检查 BM25 与向量融合、权限过滤、质量、冷查询和真实费用。
这篇教程用一小组公开的演示文档,把 Turbopuffer 的 BM25 与向量检索接成一个可检查的 RAG 召回层。流程是:写入带文本的行 → 跑两路查询 → RRF 融合 → 人工核对候选 → 再把候选交给生成模型。示例不上传真实客户资料,也不把 API key 放进浏览器。
开始前准备
- 在 Turbopuffer 控制台创建 API key,选择支持目标 embedding 模型的区域。以下示例沿用官方文档的
gcp-us-central1和nvidia/nemotron-3-embed-8b,换区域时先查模型列表。 - 安装 Python 3 和官方 SDK:
python3 -m pip install turbopuffer。 - 在终端设置
TURBOPUFFER_API_KEY环境变量。密钥只放服务端环境变量或密钥管理器,别写入代码、聊天记录或客户端。 - 为这次演示准备独立 namespace,避免和生产索引混用。注意官方没有免费档,试跑前在价格页确认最低用量和账户预算。
第一步:写入同时用于 BM25 与向量的文本
保存下面代码为 hybrid_demo.py。这段代码按 2026 年 10 月官方 Python 示例整理:full_text_search 启用 BM25,embed 让同一 content 字段生成向量。示例仅用三条公开句子说明路径;真实 RAG 应保留文档 ID、页码、来源和更新时间。
import os
import uuid
import turbopuffer
key = os.environ["TURBOPUFFER_API_KEY"]
tpuf = turbopuffer.Turbopuffer(
api_key=key,
region="gcp-us-central1",
)
name = os.getenv("TURBOPUFFER_NAMESPACE", f"rag-demo-{uuid.uuid4().hex[:8]}")
ns = tpuf.namespace(name)
ns.write(
upsert_rows=[
{"id": 1, "content": "Error E104: reset the payment webhook secret", "is_public": True},
{"id": 2, "content": "Troubleshoot a failed checkout callback", "is_public": True},
{"id": 3, "content": "Internal billing incident notes", "is_public": False},
],
distance_metric="cosine_distance",
schema={
"content": {
"type": "string",
"full_text_search": True,
"embed": {"model": "nvidia/nemotron-3-embed-8b", "dims": 1024},
}
},
)
如果业务有中英文混合、错误码、SKU 等精确词,先用真实样本确认分词与 BM25 结果;不要以三条演示数据推断上线效果。
第二步:两路召回并融合
把下面代码接在同一个文件尾部。两条查询都加 is_public 过滤,防止语义分支或关键词分支绕过权限。multi_query 的 rerank_by=("RRF",) 只融合名次,不等于生成模型回答,也不能自动修复错误召回。
question = "How do I fix payment webhook error E104?"
public_only = ("is_public", "Eq", True)
response = ns.multi_query(
queries=[
{
"rank_by": ("content", "ANN", ("Embed", question)),
"filters": public_only,
"limit": 10,
"include_attributes": ["content"],
},
{
"rank_by": ("content", "BM25", question),
"filters": public_only,
"limit": 10,
"include_attributes": ["content"],
},
],
rerank_by=("RRF",),
)
for row in response.results[0].rows:
print(row.id, row.content)
运行:python3 hybrid_demo.py。首先检查 E104 相关文档是否在前列,再确认 is_public=False 的第 3 条没有出现在结果中。若 SDK 或模型更新导致参数变化,以官方混合检索示例为准。
第三步:接入真正的 RAG
生产环境把文档切成可追溯的块,记录 tenant_id、document_id、page、updated_at 和可见范围。服务端先认证请求者,再由认证结果生成租户与群组过滤;所有检索分支必须使用同一限制。不要相信用户在提示词里自称“有权限”。
从融合结果取 20–50 条候选,可在应用层加重排模型,再把最终 5–10 条及来源元数据送入生成模型。答案必须引用文档 ID/页码;没有足够证据时返回“不确定”,不能用模型猜测填空。
第四步:建立可重复的验收
- 质量:准备至少 50 条含标准答案的问题,分别测纯 BM25、纯向量、混合三组 Recall@10、NDCG 和错召回;把错误码与自然语言问题分开看。
- 权限:跨租户、未公开文档、撤权后查询都应返回零条越权结果;权限变更与索引更新要检查时间差。
- 性能:热缓存和冷缓存分别记录 p50/p95/p99;集中写入后测可见时间,超时要有回退策略。
- 账单:记每次写入、查询、存储与 embedding 费用,按“每千次满足质量门槛的有效查询”核算;Launch 的 16 美元是月最低用量。
- 回滚:保留原检索后端并行验证一段时间,监控满意度下降、越权或超额账单,触发时切回旧路由。
常见错误
只用纯向量检索错误码,往往漏掉精确词;只用 BM25 又容易漏同义表达。若冷查询拉高尾延迟,考虑预热或调整 namespace 与流量模式。若结果混入别的租户,先停用该召回路由并修复服务端过滤,不能指望后续 LLM 自行剔除。
资料:官方 Quickstart、混合检索指南、权限指南、价格页。
常见问题
- Turbopuffer 会自动生成 embedding 吗?
- 官方示例可在字段 schema 中设置 embed,用原生模型在写入和查询时生成向量。也可以使用外部 embedding;模型是否可用应按区域查文档。
- RRF 能代替 reranker 吗?
- 不能完全代替。RRF 合并多个召回列表的名次;若要更精细地判断候选与问题的相关性,可在应用层增加二阶段重排。
- API key 可以放前端吗?
- 不要。长期密钥放服务端环境变量或密钥管理器,客户端只调用受控后端接口。
- 为什么两路查询都要加权限过滤?
- 若只有一条分支过滤,另一条仍可能召回无权限文档。融合后的结果不能替代前置访问控制。
- 没有免费档还能低成本试跑吗?
- 可以用极小的公开演示数据试跑,但官方没有免费档,Launch 每月最低用量为 16 美元。试跑前设置预算并查看当前价格页。
- 教程里的代码已经替我执行过吗?
- 没有。示例按官方 Python SDK 文档整理,需要你自己的账户、密钥和网络环境;上线前应在隔离 namespace 中运行并核对结果。