不建向量库也能检索: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 问题判断文档里是否包含答案。

官方示例做法的核心可以概括为三步:

  1. 给每行加 ID:官方示例把每行文本前缀成类似 L052| You own Your Content... 的形式。
  2. 用 Choice 问题选行:把 218 个行 ID 作为选项,让模型选择哪一行最能回答查询。
  3. 用 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},
)

这个代码块不是官方原样代码,只是用简化的方式还原官方示例里最重要的调用结构;字段名如 ChoiceNoulNoulCriteriacriteriasystem_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 行。

所以工程上的串联方案就很自然了:

  1. 向量库或 BM25 在大语料里召回 top-K 小候选集。
  2. 把 top-K 截断到决策模型一次请求能处理的范围,比如几十到 200 多条。
  3. 用 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 的思路:

  1. 语料持续变化,却又不想维护索引
    semantic find 每次请求都要把文档内容作为输入发给模型。如果文档频繁修改,又没有一个索引或缓存层来复用,每次查询都要重新拼装整份文档,成本和时间都会变差。这不是官方直接写下的结论,而是我从官方示例“每次请求携带候选文本”这个模式推导出的工程判断。

  2. 候选规模远超单请求能力
    官方写明 Choice 最多 255 个选项。如果你的候选行数是几千、几万,就不能单请求完成;如果硬要靠窗口多次请求,系统复杂度和成本都会明显上升。这种情况下,先做一次快速召回更合理。

  3. 已经拥有召回能力很强的向量检索系统时,不要把它当替代品
    更合理的组合是:向量库负责粗排,决策模型负责小集合上的精排。官方 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 放在一起解读。

参考来源

评论区

0 条评论

登录后可评论。

摸鱼小队长 11 阅读