文档问答系统应先用向量检索扩大召回,再只对有限候选集重排,并以完整的单次成功查询成本评估供应商。向量需绑定来源、分块、模型和索引版本,防止迁移时混用向量空间。
简短回答:对于面向美国/欧盟市场的「Ask Your Docs」系统,应使用 embeddings 实现广泛召回,仅对规模受限的候选集进行 reranking;至于选择 OpenAI、Cohere、Voyage,还是共享 runtime,则应先在自己的语料库上衡量每次成功查询的完整成本,再做决定。
这才是架构层面的决策。低廉的 embedding 单价可能被频繁的重新索引抵消;而效果出色的 reranker,如果需要处理每一份文档,也可能变成一种代价高昂的习惯。因此,真正决定方案的约束不是价目表,而是系统从文档写入到答案被接受,整个过程究竟执行了多少工作。
确保各个阶段都可以替换。
第一个不变量是数据溯源。每个存储的 vector 都必须与生成它的源文档 ID、chunk ID、embedding model ID 和索引版本保持关联。缺少这些关联时,局部迁移就可能混用不同的 vector space,而索引仍会返回看起来似乎合理的结果。我认为这属于数据完整性故障,因为系统已经丢失了解释或重建其派生状态所必需的信息。
第二个不变量是限制查询工作量。embedding 检索负责召回;可选的 reranking 只接收排名靠前的结果。候选数量上限是一个需要经过评估的工作负载参数,而不是放之四海皆准的常量。简短的产品支持问题和宽泛的政策问题,可能适合不同的上限,但无论采用哪种上限,都必须可以观测并受到硬性限制。如果跳过 reranking,应用程序应该明确知道当前提供的是 embedding 排序结果,而不是悄悄将两条路径描述成等价方案。
第三,源文档始终是持久化的权威数据源。vector index 只是派生状态。重建它的成本可能很高,但删除、纠错工作流以及模型迁移,绝不能依赖索引作为唯一幸存的数据副本。对于面向美国/欧盟市场的 SaaS 部署,在讨论模型成本之前,区域可用性、数据保留和删除行为都是准入检查项;如果某条请求路径违反了工作负载的数据边界,那么无论它在经济上多么划算,都仍然不可采用。
故障边界非常明确:混用不同模型的索引、未及时生效的删除、重复摄取、失控的重新 embedding、无上限的 rerank 扇出,以及收到 HTTP 429 后由重试造成的流量放大。这些故障不像相关性图表那样引人注目,却往往主导着架构评审。
假设有一个每晚执行的文档同步任务。某次格式调整带来了新的上游 revision,但人类可读的内容并未变化;摄取路径无法区分语义内容与展示形式,于是生成了新的 chunk ID,并安排整篇文档重新执行 embedding。与此同时,查询流量触发了速率限制,客户端在重试 429 响应时又没有遵循 Retry-After。整个过程中,模型完全不需要返回错误答案,但索引工作量依然会持续增长,重复的派生记录不断累积,查询重试也会抬高表面上的搜索成本。相应的控制措施应放在不同边界上——在写入路径使用内容 hash 和幂等摄取,在存储层使用带版本的 vector namespace,在 reranking 之前限制候选数量,并在客户端采用有上限的指数退避。如果费率比较没有统计这些工作量,它衡量的只是账单上的标签,而不是架构本身。
首先准备一份冻结的、有代表性的语料库,以及一组经过人工判定的查询。查询集应涵盖简短问题、词汇不匹配、近似重复的段落,以及只改变一个细微限定条件就会改变正确来源的问题。使用完全相同的文档和查询测试每个候选方案,固定所选模型的精确 ID,并记录模型目录日期。我不认为任何公开 benchmark 足以替私有知识库做出这个选择;真正缺失的证据,是模型在系统实际需要服务的文档和问题上的表现。
需要衡量的指标包括:reranking 之前的 recall、reranking 之后的排序质量、传入第二阶段的候选数量、处理的总输入量、尾延迟以及重试次数。然后,将索引成本与查询成本分开计算。索引成本包括初始 chunk,以及因文档变更或模型更换而产生的重新 embedding。查询工作量包括查询 embedding,以及仅针对选定候选执行的 reranking。把模型迁移当天的索引账单除以当天的查询次数,会得到一个非常夸张的数字,并导向错误的决策。
具体结果可能会因 chunking 策略和重复样板内容而发生巨大变化。
下面的比较刻意采用决策表的思路,而不是给出一个通用排名。现有证据不足以确定谁是质量最佳者,也不足以确定谁是当前单位价格最低者;如果贸然得出这样的结论,不过是披着架构外衣的营销说辞。
Infrai 的相关优势在于清晰易懂,而不是某个承诺的 benchmark 结果:embeddings 和可选 reranking 都位于简单的 HTTP capability 之后,其发现材料说明了调用方式;同一个 runtime 以后还可以支持 chat answer,而不必再次集成另一家供应商。这使它成为一个可信的 adapter boundary,适合不希望应用代码依赖特定 SDK 的团队。但它并不能免除评估模型、固定模型 ID,或者验证美国/欧盟约束的必要性。
如果某家供应商原生提供的控制能力、现有采购关系或合同本身就是不变量,那么直接集成 OpenAI、Cohere、Voyage、Gemini 或 Together,仍然是更简洁的选择。不要把这类要求隐藏在通用接口下面。
每 100 万 tokens 的成本是一项有用的输入,但它并不等于每个答案的成本。一个两阶段系统至少包含三个不同的成本项:语料库 embedding、查询 embedding,以及可选的 reranking。语料库 embedding 的成本会在规划周期内摊销;reranking 成本则会随着调用它的查询数量,以及这些查询所发送的输入量而变化。重试产生的成本应该计入实际观测总量,而不是放在脚注里一带而过。
下面这个 Python 程序明确列出了这些成本项,同时没有内置任何供应商费率,也没有假设未经文档说明的 API payload。请传入当前报价,以及来自同一次评估运行的工作负载测量值。程序会打印两个需要比较的总数:规划周期内的索引支出,以及 semantic search 支出。
import argparse
from decimal import Decimal
PER_MILLION = Decimal("1000000")
def charge(tokens: Decimal, rate_per_million: Decimal) -> Decimal:
return tokens * rate_per_million / PER_MILLION
def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument("--corpus-embedding-tokens", type=Decimal, required=True)
parser.add_argument("--query-embedding-tokens", type=Decimal, required=True)
parser.add_argument("--rerank-tokens", type=Decimal, required=True)
parser.add_argument("--embedding-rate-per-million", type=Decimal, required=True)
parser.add_argument("--rerank-rate-per-million", type=Decimal, required=True)
args = parser.parse_args()
indexing = charge(
args.corpus_embedding_tokens,
args.embedding_rate_per_million,
)
search = charge(
args.query_embedding_tokens,
args.embedding_rate_per_million,
) + charge(
args.rerank_tokens,
args.rerank_rate_per_million,
)
print(f"indexing={indexing}")
print(f"search={search}")
if __name__ == "__main__":
main()
这些输入应该来自 trace,而不是过于乐观的平均值。要统计那些仅调整格式、却触发 embedding 的文档更新;要统计候选选择之后实际送入 rerank 的输入;也要统计重试流量。然后,在所有方案采用相同质量门槛的前提下重新运行模型;不断减少候选数量,直到相关性崩溃,并不叫优化。
存储架构师还应该关注迁移过程会如何运行。更换 embedding model 通常意味着:在新版本下重建派生 vector,同时让旧索引保持可读,直至完成切换。即使两个模型生成的 vector 维度相同,应用程序也绝不能因此把新 vector 写入旧 namespace。维度相等并不代表语义兼容。
对于持续增长的 SaaS 知识库,我不接受对每个已存储 chunk 都执行 reranking 的设计。这种方案会让更重的查询时处理量与语料库规模耦合,取消检索阶段限制工作量的作用,并把供应商比较变成针对一个不必要的大规模工作负载展开的竞赛。当文档数量的增长与查询相关性无关时,这种方案并不合适。应该先使用 embeddings 进行广泛检索,再限制候选集规模,并且只在经过人工判定的质量提升足以证明其价值时启用 reranking。
确实存在一种合理的例外:如果语料库很小且相对稳定、流量很低,同时又严格要求对所有符合条件的条目排序,那么将全部条目作为完整候选集,可能更容易理解和管理。只要最大候选集能够被明确限制,而且经过测量的延迟、治理要求和处理成本全部在可接受范围内,就可以继续采用这种方案。问题在于,随着语料库变化,这些条件必须始终成立。
共享 runtime 方案同样存在局限。如果供应商特有功能处于核心地位,或者必须签订直接的供应商协议,那么它并不适合。它也没有专门的 moderation endpoint;如果应用程序需要审核文本或图片,就必须使用带 JSON schema 的 chat model 自行设计这项控制,而这可能并不是合适的安全边界。在这些情况下,应选择能够满足要求的直接供应商或专业 moderation 服务。
没有任何供应商标签能够弥补薄弱的成本核算。真正经得起推敲的决策,是在满足相同相关性和治理底线的前提下,选择实测完整路径工作量最低的方案,同时确保源数据持久可靠,并让两个检索阶段都可以替换。
OpenAI,《Function calling》
ElevenLabs 文档
如需采取进一步行动,你可以考虑屏蔽此人和/或举报滥用行为。