接向量检索接口时,文档里一长串召回字段,你往往只填了索引名和查询向量就跑通了,于是开始怀疑那些没填的到底是可选的还是被悄悄套了默认值。本文把这些字段按必填、可省、看场景三类拆开,说明省掉之后检索行为和成本会怎么变,并给一份排查清单,帮你在调试和上线时知道先动哪个。
先把字段分成三档:不传就报错、不传有默认值、不传等于关掉
判断一个召回请求里哪些字段能省,最省事的办法不是去背参数表,而是把字段按「省掉之后会发生什么」分成三档。同一档里的字段处理方式基本一致,分完档你再看自己的请求,哪里能删、哪里不能删一眼就清楚。
| 档位 | 省掉之后的行为 | 典型字段 | 能不能省 |
|---|---|---|---|
| 第一档:必填 | 直接返回参数错误,请求不成立 | 索引或集合标识、查询向量本体 | 不能省,也别想省 |
| 第二档:有默认值 | 服务端补一个默认值,请求成功但结果可能不是你想要的 | 返回条数、相似度度量方式、是否返回向量本体 | 能省,但要先确认默认值 |
| 第三档:功能开关 | 等于不启用该功能,请求正常返回 | 过滤条件、命名空间、阈值过滤 | 能省,且省掉通常更安全 |
第一档之所以只有那么两三个字段,是因为少任何一个服务端都不知道你要查什么、去哪查。很多人在这里省,是误以为某些平台会自动挑一个默认索引;实际上没有索引标识的请求通常连路由都过不去,报错信息往往就是缺参数。这类报错最好处理,直接把字段补回去就行,别去猜有没有别的写法。
- 第一步:先看报错把请求原样发一次,只保留索引标识和查询向量,看返回的是参数错误还是正常结果。如果报错点名了某个字段,那个字段就属于第一档,补上即可。
- 第二步:再对默认值请求能跑通之后,逐项检查你没传的字段,去站内资料页核对服务端的默认取值。重点看返回条数和是否返回向量本体,这两项默认值和直觉差得比较远。
- 第三步:最后才开开关确认默认值都符合预期之后,再按需要加过滤条件、命名空间、阈值过滤这些开关。一次只加一个,加完看结果变化,避免几个开关一起上导致排查困难。
第二档最容易被忽略。省掉不算错,请求也成功,但返回的条数可能远多于你打算处理的量,或者相似度度量方式和你建索引时用的不一致,导致排序看起来「怪怪的」。遇到结果数量或排序不对劲,先别怀疑模型,回头对一遍这一档字段的默认值,往往就找到原因了。
- 返回条数:不传通常有个服务端默认值,可能比你前端分页需要的多,白拿一堆数据
- 相似度度量方式:不传会走默认度量,和索引构建时的设置不一致时,排序结果会偏离预期
- 是否返回向量本体:不传一般是不返回,如果你下游还要拿向量做二次计算,就会缺字段
- 过滤条件:属于第三档,不传就是不过滤,返回的是全量候选,不是「过滤后为空」
- 命名空间:不传通常落在默认空间,多租户场景下很容易串数据,这点要特别确认
- 阈值过滤:不传等于不设下限,低相似度的结果也会回来,需要自己在业务侧再筛
这套分档不是通用标准,不同服务商的字段归类和默认值会有差异,尤其是相似度度量和返回条数这两项,同一个字段名在不同平台上的默认行为可能不一样。判断时以你实际对接的那家为准,到站内资料页核对当时的字段说明和默认值,不要拿别家的经验直接套。如果某家把过滤条件也做成了必填,那它就从第三档升到第一档,按报错处理就行。
这几类字段省掉一般不出事,但要接受默认行为
这四个字段的共同点是:不传不会直接报错,接口照样返回结果,但你会因此拿到一套「服务端替你选好的默认行为」。省掉它们之前,先想清楚下游有没有人按你脑补的数量、分数、维度在写逻辑。省可以,但要把默认行为当成一个已知条件,而不是不存在的东西。
| 字段 | 省掉之后发生什么 | 什么情况下可以省 | 什么情况下别省 |
|---|---|---|---|
| 返回条数 | 服务端给一个默认值,具体数字看当时接口说明 | 只是随手试一下、不接下游逻辑 | 要分页、要拼上下文、要按条数计费或截断 |
| 相似度阈值/分数下限 | 全量按分数排序返回,低分噪声一起进来 | 本地小规模试跑、想先看看分数分布 | 正式接进问答或检索链路,噪声会污染上下文 |
| 返回向量本体 | 响应体只给 id、分数、元数据,体积小很多 | 推理阶段、前端只需要命中结果 | 调试相似度、做重排、做聚类或去重时需要 |
| 命名空间/租户字段 | 落到索引默认空间,所有数据混在一起 | 单库单租户、索引只服务一个业务 | 多租户共用一个索引,省掉就是数据串门的开始 |
- 不传返回条数时,不要凭记忆假设是几条,先跑一次看响应里的实际长度,再决定下游怎么写;
- 不传分数下限时,先看命中分数分布,如果低分结果明显成堆,就该显式给个下限;
- 不传向量本体时,确认调用方没有人拿它做二次计算,否则会在某次重构时突然缺字段;
- 不传命名空间时,确认这个索引未来半年不会加第二个租户,加不了就现在补上。
- 先跑一次最小请求只传查询向量,其余字段全省,把完整响应存下来,数一下实际返回了几条、字段有哪些。
- 对照响应字段决定下游分页逻辑按实际条数写,不要按想象中的默认值写;响应里没有向量本体,就别在解析代码里声明它。
- 再逐个显式补回上生产前把条数、分数下限、命名空间三个显式写上,向量本体按需要决定;写完再用同一条查询对比两次响应。
- 把默认值记在项目里在接口封装层注释清楚当前用了哪些默认行为,换服务或换索引时优先复查这里。
看场景才能定的字段:省了不会报错,但会影响结果质量
上一节说清楚了哪些字段是接口层面的必填项。但真正让人踩坑的不是「传不传会报错」,而是「传了没事、不传也没事,结果却不一样」。这类字段省下来接口照样返 200,只有做对比测试或者线上效果变差时才暴露。下面按四类场景拆开讲判断口径。
| 要做的场景 | 容易省掉的字段 | 省掉的直接后果 | 怎么验证有没有踩坑 |
|---|---|---|---|
| 限定某批文档/某个时间范围 | 过滤条件(filter) | 可能变成全库范围搜;若走的是后过滤,返回条数先够、再变少 | 先用明显只该命中一两条数据的条件试,看返回条数是否真的收窄 |
| 多路召回或混合检索 | 各路权重/融合参数 | 退化成纯向量或纯关键词单路,结果偏一侧但接口不报错 | 同一批 query 分别跑单路和混合,人工比对头部结果差异 |
| 想要更好排序 | 重排相关参数 | 没有重排阶段,直接拿初排结果返回,容易被误判成向量库不行 | 固定 query 集,带重排与不带重排各跑一遍,看前排是否明显更贴题 |
| 要拿来源链接/标题做引用 | 是否返回元数据 | 拿不到标题、URL、原文片段,引用没法拼 | 看返回体里除向量和 score 外还有没有业务字段 |
过滤条件这块单独说,因为它的坑最隐蔽。过滤分预过滤和后过滤:预过滤是先按条件缩小候选再算相似度,后过滤是先算相似度取前 N 条、再从里面筛。两者在条件宽松时看不出差别,条件苛刻时后过滤会出现「返回条数不足」——你要十条,筛完只剩三条,而且不报错。判断方法很简单,用一个人为限制很死的时间范围去查,如果返回条数明显低于你设的 topK,基本就是后过滤在起作用。这时候要么把 topK 调大给筛选留余量,要么去问服务方能不能改成预过滤。注意:如果你的过滤条件本来就覆盖大部分数据,这个差别可以忽略,不用强行折腾。
- 先确认过滤走的哪条路用一个范围极窄的时间条件加一个正常的 topK 去查,观察返回条数是否等于 topK。少于 topK 且数据客观存在,就说明存在后过滤行为,需要给 topK 留余量或调整条件。
- 再单独验权重和融合把混合检索的权重参数去掉跑一次,和带权重的跑一次,拿同一批 query 对比头部结果。只靠肉眼看一两条不准,至少准备十几条覆盖不同意图的 query。
- 最后决定重排和元数据开不开要生成带出处的回答,元数据必须开;只是内部排序打分的场景可以关掉省带宽。重排则看你对前排精度的容忍度,容忍不了就开,并在一开始就纳入对比基线。
- 预过滤
- 先按条件缩小候选集合,再在集合内做相似度计算,条数更容易凑够
- 后过滤
- 先算相似度取前若干条,再从结果里筛条件,条件苛刻时返回条数会不够
- 权重/融合参数
- 决定多路召回结果怎么合并,不传通常退化成单路
- 重排
- 在初排结果上做二次打分,不传就没有这一阶段,接口仍返回成功
所以这类字段的取舍标准是场景,不是接口文档。要拿来源做引用就开元数据,只排序可以省;要限定数据范围就先搞清预过滤还是后过滤;要做混合检索就固定一批 query 做 A/B;想要前排更准就开重排。这些参数没有绝对的最优值,具体每次怎么配、当前各家支持到什么程度,到 /guide/retrieval-fields 核对当时的说明,别拿一篇旧总结当准绳。边界也要说清:如果线上效果本身已经够用、query 分布又稳定,不动这些参数反而更稳,没必要为了一点点理论收益频繁改配置。
调不通时的排查顺序:从最少字段开始往回加
向量检索调不通,十有八九不是接口坏了,是字段加太多、你分不清是哪一项把请求搞崩的。稳妥的做法是反过来:先用最少的必填字段打通,拿到一个基线返回,再一项一项往回加。加的过程中每加一项就记一次返回条数,出问题时能直接定位到刚加的那一项。
- 只传索引标识和查询向量请求体里只留索引名/集合名这类定位信息和查询向量本身,其余全部去掉。判断标准:接口返回 200 并且 body 里有结果数组(哪怕为空数组也算通),拿到这个基线再往下走。如果这一步就报错,问题在鉴权、索引名拼写或向量维度上,跟召回字段无关,别继续加字段。
- 加上返回条数在基线之上只加一个 top_k/limit 之类的返回条数字段。看两件事:返回结构是不是你要的形状,分数分布是不是从高到低、没有一堆并列同分。如果条数没生效或者分数全一样,先怀疑这个字段名对不对,而不是去加过滤。
- 再加过滤或命名空间把 filter、命名空间、分区这类收窄范围的字段单独加上。关键验证点是返回条数不能异常塌缩:从基线条数掉到 0 或掉到个位数,说明过滤条件和数据对不上,可能是字段名、值类型或命名空间没写对。这一步只动一个字段,别顺手把阈值也加上。
- 最后加阈值、重排、元数据这几项是收益型字段,放最后加。每加一项记一次返回条数,对比上一步是涨是跌还是不变。阈值加完条数归零,就是阈值定太高;重排加完条数变少,是候选集被截断;元数据加完报错,多半是字段不存在或返回体太大。一次只加一项,出问题就知道是谁引入的。
- 日志里固定记下实际传了哪些字段每次请求把实际生效的请求体字段名和值记进日志,别只记错误码。团队排查时不用靠回忆「你当时传了啥」,直接看日志对比两次请求的字段差异。这一步做一次,后面所有疑难问题都能省一半时间。
| 加的字段 | 这一步要看的信号 | 不对劲时先怀疑什么 |
|---|---|---|
| 索引标识 + 查询向量 | 接口通、有结果数组 | 鉴权、索引名、向量维度 |
| 返回条数 | 结构和分数分布合理 | 字段名写错、条数没生效 |
| 过滤 / 命名空间 | 条数不异常塌缩 | 条件与数据不匹配、值类型不对 |
| 阈值 | 条数变化符合预期 | 阈值过高导致全被筛掉 |
| 重排 | 候选集条数别被截太狠 | 候选条数设太小 |
| 元数据 | 返回体正常、无报错 | 字段不存在、返回体过大 |
- 一次只加一个字段,加完立刻对比返回条数,别批量加。
- 基线请求要保留成可复制的模板,后面每一步都从它改。
- 报错时先看是不是必填字段缺失,再看是不是可选字段写错,这两个方向完全不同。
- 返回条数从有到 0 是最强的定位信号,出现就直接回退到上一步。
- 团队用同一份请求模板,能大幅减少「我这边能通你那边不通」的扯皮。
常见问题
只传索引名和查询向量,接口能正常返回,是不是说明其他字段都不重要?
能返回只说明那些字段有默认值或属于开关型,不代表结果符合预期。默认条数、默认是否过滤、默认是否带元数据,都会直接改变你拿到的上下文。建议按「只传最少字段、逐项加回」的方式各自跑一遍,比对返回条数和内容差异再决定留哪些。
过滤条件不传会不会把别的租户数据召回出来?
取决于你的数据是否物理隔离。同一个索引里存了多租户数据、只靠过滤字段区分时,省掉过滤就等于跨租户召回,这是最不该省的一类。真正隔离的做法是每租户独立索引或独立命名空间,字段只是第二道保险,具体口径可以到 向量数据库目录(/stacks/vector-dbs) 对照各家实现。
返回向量本体这个字段能不能一直省?
推理链路上通常可以省,因为下游只用文本内容和分数。但两种情况下要开:一是做本地重排或自定义相似度计算,需要原始向量;二是排查分数异常,想确认写入时和查询时的向量是否一致。一直省着不排查,遇到分数全一样或全零时会很难定位。