不建向量库也能检索:semantic find 拆解
先说结论
- 官方 Semantic find cookbook 把“文档内语义定位”变成一道 Choice 选择题:给每一行文本一个 ID,让模型直接选答案所在行;同时用 Noul 问题判断文档是否包含答案。
- 官方明确写了单个 Choice 问题最多 255 个选项,所以这个做法一次请求只能搜索不超过 255 行的候选范围;超过就要拆成两阶段。
- 向量库和这种“决策模型排序/检索”不是替代关系,而是串联:先由向量检索或关键词检索从海量语料中召回小候选集,再让决策模型在小候选集上做语义判断或排序。
- 成本会被候选数量直接放大:候选越多,输入 token 越多。TypeSafe 首页官方写的价格是 $42 Per Billion input tokens;按此推算,每 1k input tokens 约 $0.000042(自己推算,不是官方报价),因此工程上要先截断候选数。
- 不适合的情况有两种:语料持续变化却不想维护索引;候选规模远超单请求的 255 选项上限。
官方 semantic find 到底在做什么
官方页面一开始就把这个 cookbook 的目标写得很清楚:
“Build semantic search for GitHub’s Terms of Service. In one request, score 218 line ids against a plain-language query with a Choice question, and use a Noul question to check whether the document contains an answer.”
官方 Semantic find cookbook 这段原句说明,它是在 GitHub ToS 这份文档上,用 一次请求 同时完成两件事:用一个 Choice 问题给 218 个行 ID 打分;再用一个 Noul 问题判断文档里是否包含答案。
官方示例做法的核心可以概括为三步:
- 给每行加 ID:官方示例把每行文本前缀成类似
L052| You own Your Content...的形式。 - 用 Choice 问题选行:把 218 个行 ID 作为选项,让模型选择哪一行最能回答查询。
- 用 Noul 问题判断“是否存在答案”:因为 Choice 的概率总和永远是 1,所以即使文档里没有答案,也会有一行排到第一;只有 Noul 的概率可以掉到接近 0。
官方原文对此有明确解释:
“Choice question probabilities always add up to 1, so a line ranks first even when none answer the query. The ranking alone can’t distinguish a real answer from the closest irrelevant line.”
也就是说,仅有行排名是不够的——它只能给出“最像答案的那一行”,但无法直接说明“到底有没有答案”。这是 semantic find 里 Noul 问题存在的关键原因。
如果按官方示例把关键调用概括成伪代码,大致如下:
# 思路概括:本文按官方示例重新整理,不是官方原始代码的直接复制
options = {}
for i, line in enumerate(lines):
options[f"L{i:03d}"] = None
where = Choice(
instructions=f'Which line of the document contains the answer to: "{query}"?',
criteria=options,
)
exists = Noul(
instructions=f'Does any line of the document address or answer: "{query}"?',
criteria=NoulCriteria(
true="At least one line of the document states or directly implies the answer",
false="No line of the document addresses this",
),
)
result = client.system_one(
state={"document": joined_doc},
questions={"where": where, "exists": exists},
)
这个代码块不是官方原样代码,只是用简化的方式还原官方示例里最重要的调用结构;字段名如 Choice、Noul、NoulCriteria、criteria、system_one 都可以在官方页面查到。
最关键的限制:255 个选项
官方 semantic find 页面里有一句非常容易被忽略、但决定了整个方法边界的原句:
“A Choice question accepts up to 255 options, so this recipe searches documents of up to 255 lines in one request. Past that, search in two passes: one Choice question picks a window of lines, and a second ranks the lines inside it.”
这句话是官方写明的限制:Choice 问题最多接受 255 个选项。所以官方示例能一次请求搜索 218 行,是因为它正好小于 255。如果文档超过 255 行,就需要先选窗口,再在窗口内排名。
这里隐含着一层非常重要的判断:semantic find 不是用来直接检索大规模语料的。它更适合“候选集合已经很小”的场景。官方示例本身只有 218 行,不是上万个文档片段。
还需要强调:官方没有把 semantic find 称为“向量库替代方案”。官方页面只是在展示单文档内部的行级语义定位。把它理解为“不用向量库也能做全文语义检索”,是过度引申。它更适合作为小候选集上的精排或定位步骤。
它和向量检索怎么分工
我的判断是:向量库负责从百万级候选里召回,决策模型负责在小候选集上做语义判断/排序;两者是串联而不是替代。 这个判断不是官方在 semantic find 页直接说的,但官方 Re-ranking cookbook 给了一个很接近的分工模型。
官方 Re-ranking cookbook 明确写道:
“First, use a quick method such as keyword matching to cut those thousands of candidates down to a shortlist of plausible ones. We call this fast search. Fast search is good at that, but it can’t tell you which candidate on the shortlist is correct. That’s where re-ranking comes in. It scores every candidate on the shortlist against the query directly, and puts the best one first.”
官方在 rerank 页面也给出了具体实验数据,这是官方数据:
“Both steps run below on 3,565 court opinion passages from the CLERC dataset: BM25 builds a fast search shortlist of 30 candidates for each of 40 queries, then TypeSafe re-ranks each shortlist. With re-ranking, the correct passage lands in first place for 18% of queries, up from 5% with fast search alone.”
也就是说,官方 rerank 页面里,BM25 先做 fast search 负责召回,TypeSafe 的 Noul 分数再在小短列表上做精排。semantic find 可以看作这种“精排”在行级上的版本:候选集不是 30 个段落,而是 218 行。
所以工程上的串联方案就很自然了:
- 向量库或 BM25 在大语料里召回 top-K 小候选集。
- 把 top-K 截断到决策模型一次请求能处理的范围,比如几十到 200 多条。
- 用 semantic find 或 rerank 的 Noul/Choice 方法做语义定位或排序。
其中“截断候选数”是本文结合官方 255 上限与成本考虑给出的工程做法,不是官方在 semantic find 页面明确推荐的要求。官方只说了 255 选项限制,以及超过后可以做两阶段窗口搜索。
和另外两篇 cookbook 的关系
官方还有两篇检索类 cookbook,可以和 Semantic find 放在一起理解。
Re-ranking
Re-ranking cookbook 处理的是“已经有一个召回短列表,但短列表里哪一条最正确还不清楚”的问题。它对短列表里的每个 query-candidate pair 打 Noul 分,然后排序。它适合文档/段落级别的精排。
Classifying RAG passages
Classifying RAG passages cookbook 处理的是“检索回来的段落能不能安全地交给生成模型”的问题。官方原文写:
“Between retrieval and generation, add a second stage that classifies each retrieved passage. For each one, send TypeSafe one request carrying multiple questions about the query–passage pair: is it relevant, does it state something usable in an answer, does it contradict something the query takes for granted, and is it trying to instruct the model.”
这相当于在检索后、生成前加一层过滤:每个 passage 会被多个 Noul 问题打分,再根据阈值决定是作为证据加入、作为冲突加入,还是直接丢弃。官方示例里给出的阈值是:
# 官方 Classifying RAG passages 页面的示例阈值,不是通用推荐值
THRESHOLDS = {
"injection_max": 0.70,
"contradicts_min": 0.70,
"relevant_min": 0.45,
"evidence_min": 0.55,
}
这三个 cookbook 合在一起,可以形成一条比较清晰的流水线:
| 官方 cookbook | 阶段 | 候选粒度 | 关键动作 | 适合场景 |
|---|---|---|---|---|
| Semantic find | 文档内定位 | 行 | Choice 选行 + Noul 判断存在 | 单文档行级,候选 ≤255 |
| Re-ranking | 召回后重排 | 文档/段落 | 对每个候选 Noul 打分,排序 | 召回短列表精排 |
| Classifying RAG passages | 检索后生成前 | 段落 | 多问题 Noul 分类,决定证据/冲突/丢弃 | RAG 证据清洗 |
这个表格是我根据官方三篇页面内容整理的,不是官方给出的对照表。
成本直觉:候选越多,输入 token 越大
semantic find 的成本主要来自输入 token:整份文档的文本会作为 state 输入,候选行越多,拼接出来的文档越长,输入 token 就越多。
TypeSafe 首页展示的 Jev 价格是官方数据:
“$42 Per Billion input tokens.”
按照这个价格,可以做一次明确标注为“自己推算”的成本换算:
- $42 / 1,000,000,000 tokens = $0.000000042 per token;
- 每 1,000 input tokens 约为 $0.000042;
- 每 10,000 input tokens 约为 $0.00042(推算:$42 ÷ 1,000,000,000 × 10,000)。
这些数字是自己推算的结果,不是官方报价表里的直接数字。官方只给了每十亿 input tokens 的价格。
也就是说,如果语义检索的候选集合线性变大,输入 token 也会近似线性变大,成本随之线性上升。官方 rerank 页面也提到一个很直接的规模问题:
“Checking every document against the query one at a time works, at one comparison per document: millions of documents means millions of comparisons per query.”
因此,工程上常见的做法是:先截断候选数。比如先用向量检索或 BM25 召回 top-20 或 top-50,再交给 semantic find 或 rerank 做精排;而不是把召回结果无脑全部塞进一次请求。这个“先截断候选数”的做法是出于成本、延迟和 255 选项上限的工程权衡,不是官方在 semantic find 页面明确建议的操作。
什么情况下别这么做
有几种情况不建议直接用 semantic find 的思路:
-
语料持续变化,却又不想维护索引。
semantic find 每次请求都要把文档内容作为输入发给模型。如果文档频繁修改,又没有一个索引或缓存层来复用,每次查询都要重新拼装整份文档,成本和时间都会变差。这不是官方直接写下的结论,而是我从官方示例“每次请求携带候选文本”这个模式推导出的工程判断。 -
候选规模远超单请求能力。
官方写明 Choice 最多 255 个选项。如果你的候选行数是几千、几万,就不能单请求完成;如果硬要靠窗口多次请求,系统复杂度和成本都会明显上升。这种情况下,先做一次快速召回更合理。 -
已经拥有召回能力很强的向量检索系统时,不要把它当替代品。
更合理的组合是:向量库负责粗排,决策模型负责小集合上的精排。官方 rerank 页面已经给了这种串联思路的示例。
这次没核实的
- 官方 semantic find 页面没有给出 218 行 GitHub ToS 的实际 token 数;本节所有成本量级都是基于假设 token 数做的自己推算,不能当作官方成本数字。
- TypeSafe 首页的 “238x Lower input price than Claude Fable 5.1” 是官方展示的比较文案,但页面没有写明对比版本、日期和计价细节;我只能引用原句,未能核实完整对比口径。
- 官方 rerank 页的提升数据(top-1 从 5% 到 18%,top-10 从 38% 到 62%)是官方实验数据,我没有复现验证;此处按官方数据引用。
- 官方 Classifying RAG passages 页面的阈值 0.70、0.45、0.55 等是官方示例代码中的数值,不代表官方推荐或普适标准;不建议直接搬到其他项目里。
- 官方没有宣称 semantic find 是“向量库替代方案”,本文只是把它和 rerank、classifying 放在一起解读。
参考来源
评论区
登录后可评论。