Guide · 使用教程
Voyage AI RAG 教程:嵌入召回、重排与证据质量评估
用脱敏样本走通嵌入与重排,分别评估 Recall、MRR、权限、引用和完整检索成本。
这篇教程完成一个小型检索实验:用 Voyage 把获准访问的资料和问题转成向量,得到候选,再用 Rerank 3 排序,最后根据人工标注的证据评估效果。示例用虚构政策和内存中的精确余弦检索,便于看清每一步;正式知识库再把候选召回接到向量库或混合检索。
开始前准备
准备 Voyage API key、Python 环境和至少 50 个脱敏问题。Key 放在服务端环境变量 VOYAGE_API_KEY,不要写进代码、浏览器或截图。每个问题标注能支持答案的 chunk ID;“资料中没有答案”的问题另标记,用于检查拒答。先核对账号额度、组织预算与数据处理条款。
安装官方 Python 包:
python -m pip install voyageai
下方示例执行时会发送获准访问的文本并产生 API 用量。代码展示接口组合,不代表已对你的账号或业务资料做过在线测量。
第一步:固定模型、维度和权限范围
初次实验使用 voyage-4-lite、1024 维和浮点向量;文档任务为 document,问题任务为 query。把这组配置与 chunk 版本保存,查询时检查一致。不能因为两个向量长度相同,就认定它们来自兼容的向量空间。
示例先从已验证的用户身份取租户,再过滤资料,之后才调用 API。正式系统还应检查文档级 ACL、源文件撤权与日志权限。提示词不是访问控制;重排完成后才删除未授权结果也无法收回已经传出去的资料。
第二步:嵌入召回,再进行重排
将下面代码保存为 retrieval_demo.py。环境变量由你在本地安全设置,不在脚本里粘贴 key。
import math
import voyageai
MODEL = "voyage-4-lite"
DIMENSION = 1024
tenant = "alpha" # 正式应用从已认证的会话与权限数据库推导
documents = [
{"id": "a-refund", "tenant": "alpha",
"text": "Alpha 客服政策:退款申请由人工审核后处理。"},
{"id": "a-shipping", "tenant": "alpha",
"text": "Alpha 发货政策:工作日收到订单后安排发货。"},
{"id": "a-account", "tenant": "alpha",
"text": "Alpha 账号政策:更换邮箱需要验证当前身份。"},
{"id": "b-refund", "tenant": "beta",
"text": "Beta 的内部退款政策不得向 Alpha 用户展示。"},
]
allowed = [d for d in documents if d["tenant"] == tenant]
if not allowed:
raise RuntimeError("没有获准访问的资料")
client = voyageai.Client() # 读取 VOYAGE_API_KEY
doc_vectors = client.embed(
[d["text"] for d in allowed],
model=MODEL, input_type="document",
output_dimension=DIMENSION, truncation=False,
).embeddings
query = "退款由谁审核?"
query_vector = client.embed(
[query], model=MODEL, input_type="query",
output_dimension=DIMENSION, truncation=False,
).embeddings[0]
def cosine(a, b):
if len(a) != len(b):
raise ValueError("向量维度不一致")
denominator = math.sqrt(sum(x*x for x in a)) * math.sqrt(
sum(x*x for x in b)
)
return sum(x*y for x, y in zip(a, b)) / denominator if denominator else 0.0
scored = sorted(
zip(allowed, doc_vectors),
key=lambda pair: cosine(query_vector, pair[1]), reverse=True,
)
# 生产系统可取前 20/50 条;示例只有三份获准访问的资料
candidates = [doc for doc, _ in scored[:20]]
reranked = client.rerank(
query=query, documents=[d["text"] for d in candidates],
model="rerank-3-lite", top_k=min(2, len(candidates)),
truncation=False,
)
for result in reranked.results:
doc = candidates[result.index]
print(doc["id"], result.relevance_score, doc["text"])
返回的 index 指向这次送入 rerank 的候选列表,不是整个原始文档表。必须保留这层映射,否则排序正确却引用错文件。这里显式关闭截断,让超长输入直接暴露为错误;正式系统可按文档结构切块并记录截断策略。别静默丢掉含答案的尾部内容。
第三步:把召回与排序分开评估
先测正确证据是否进入前 20 条,再测第一条相关证据排在什么位置。下面的函数只统计人工标注的 ID,不调用模型来给自己评分:
def recall_at_k(ranked_ids, relevant_ids, k):
relevant = set(relevant_ids)
if not relevant:
return None # 无答案问题另外检查拒答
return len(set(ranked_ids[:k]) & relevant) / len(relevant)
def reciprocal_rank(ranked_ids, relevant_ids):
relevant = set(relevant_ids)
for rank, doc_id in enumerate(ranked_ids, 1):
if doc_id in relevant:
return 1.0 / rank
return 0.0
对题库逐条计算并取平均,得到 Recall@20 与 MRR。单次示例不能代表业务质量。至少比较四组:原有召回、Voyage 嵌入召回、相同候选加 rerank-3-lite、相同候选加 rerank-3。固定回答模型和提示词,另外人工核对前 5 条证据与最终引用。
若 Recall 很低,先处理解析、切块、过滤和词法匹配;若 Recall 高但正确片段靠后,再调重排和候选数量。分数阈值要按型号和题库校准,relevance score 不是概率,也不能批准退款等业务操作。
第四步:估算费用与生产故障
重排处理 token = 查询 token × 候选数 + 所有候选 token。50-token 问题和 20 个各 500-token 的片段共约 11,000 token;还须加查询嵌入、初始索引、增量更新、向量库、生成和失败重试。用 API 实际返回的用量计算,不能用中文字符数直接代替 token。
| 检查 | 通过标准 |
|---|---|
| 跨租户与撤权 | 未授权文本不进入候选或外部 API |
| 维度/模型切换 | 配置不匹配立即拒绝查询;新索引单独验收 |
| 超长片段 | 显式报错或按已记录的切块规则处理 |
| 429/超时 | 有限次数退避;降级需明确记录,不能静默换模型 |
| 缺少证据 | 回答层拒答,并向读者说明资料不足 |
| 引用与日志 | 保持 chunk/source/version 映射,不记录不必要的原文 |
上线前用灰度索引观察 p95 延迟、有效证据率与每千次查询总成本,保留旧模型和索引回退配置。资料格式、语言或模型版本变化时,重跑同一题库。
资料:Voyage 快速入门、嵌入 API、重排 API、价格、限流。
常见问题
- 为什么区分 document 和 query?
- Voyage 在检索任务中使用对应输入类型,为文档和查询提供适合其用途的向量化处理。
- 同样维度的向量一定兼容吗?
- 不一定。模型空间、输出维度、数据类型与索引版本都要匹配;迁移时使用独立索引和题库验收。
- 重排结果的 index 对应哪份资料?
- 它对应本次传给 rerank 的候选列表位置,应用必须保留候选到原 chunk/source 的映射。
- 什么情况下该先改召回?
- 正确证据未进入候选时,先排查解析、切块、权限过滤、关键词和嵌入;重排无法找回未传入的内容。
- 租户权限应该什么时候过滤?
- 在候选形成与外部 API 调用之前,根据已验证身份和文档 ACL 过滤,避免未获准文本被发送出去。
- 这份教程给出了实际性能结果吗?
- 没有。示例展示接口组合和评估方法,运行需自己的账号;业务效果应使用脱敏题库、实际用量与人工证据标注测量。