2825 字
约 9 分钟
16
S20-A17 · 空结果处理:没搜到之后做什么

用户搜"差旅报销标准",知识库返回空。系统显示"未找到相关内容"——用户于是去问同事,而实际答案就在库里的《费用报销制度》第 4 章,只是那章标题写的是"差旅费用"而正文没有"标准"这个词。

"空结果"不是终点,而是一个需要分类的信号:可能是真的没有、可能是表达不匹配、可能是过滤条件太严、也可能是索引没同步。这里的成本不对称:一次"没搜到"的体验损失,远大于十次"搜到了"的收益。因为用户对"找不到"的记忆更深,而且它会直接影响"下次是否还来这里搜"。
而它的处理方式决定了用户是"再试一次"还是"再也不来"。


1. 五种"空"的原因

原因 特征
真的没有 任何表达都搜不到
表达不匹配 换词能搜到
过滤太严 去掉条件能搜到
索引缺失 数据存在但索引里没有
权限过滤 有数据但当前用户不可见

前三项是"检索问题",后两项是"数据/权限问题"。前两项最容易混淆:"真的没有"与"表达不匹配"在系统里都表现为空,区分方式是"用同义词再搜一次"——如果能搜到,就是后者。这个检查很便宜,应当自动化。
而系统对它们的正确反应完全不同:前者应当引导改写,后者应当说明原因。

2. 空结果的四种应对

应对 适用
提示改写 表达不匹配
放宽条件(自动) 过滤太严
展示相近结果 真的没有但有相关
说明并转人工 权限/索引问题

第一项要给具体的改写方向,而不是"请换个说法":"你搜的是'报销标准',可以试试'报销比例'或'费用报销'"。给出候选词比空泛建议有效得多。

第三项要标注"这些不是你要的":直接展示相近结果而不说明,会让用户误以为系统答错了。第三项的实现要点是"区分标题匹配与正文匹配":相近结果通常来自标题匹配,而用户的目标可能在正文。所以展示时可以标注"标题相关",让他知道为什么出现。

// 空结果页的三种文案(按原因)
表达不匹配:没有找到"差旅报销标准"。试试:差旅费用 / 报销比例 / 费用制度
确有资料但不可见:该内容存在,但你的账号无查看权限,可申请开通
确实没有:库中没有相关资料。可提交需求(表单链接),或咨询财务共享中心
// 关键:三种文案都不一样,而"未找到相关内容"是最没用的一种

3. 自动放宽的边界

放宽方式 风险
去掉一个过滤条件 结果可能超出范围
降低相关度阈值 噪声增加
同义词扩展 语义漂移
跨语言/跨库检索 结果不可信

第一项要让用户知道"系统放宽了条件":否则用户以为看到的是精确结果。所以"已放宽条件"应当是可见标签。

第四项(跨语言/跨库)要特别谨慎:跨库检索的"相关"往往靠翻译或向量近似,准确率显著低于同库。如果不标注来源,用户会把不可靠的结果当成权威。
第二项要有下限:阈值降到 0.2 时,返回的是"任何东西",而用户会认为系统在胡说。

// 分步放宽:每一档都标注"放宽了什么"
def search(q, filters):
    r = do_search(q, filters)
    if r: return r, None
    r = do_search(q, filters.without("year"))         # 去掉年份
    if r: return r, "已忽略年份筛选"
    r = do_search(q, filters.without("year","dept"))  # 再去部门
    if r: return r, "已忽略年份与部门筛选"
    return [], "未找到"
// 关键:返回"放宽说明",随结果一起展示

4. 引导改写的三种手段

手段 效果
从语料里提候选词 最准
拼写纠正 处理错字
同义词表 覆盖行业说法

第一项最有效:**从索引里取出"与查询共享字词的高频词"**第三项(同义词表)的维护成本最低:几十对词就能覆盖大部分行业术语差异,而它的收益立竿见影。代价是需要有人持续收集(从零结果日志里)。
,作为"你可能想搜"的候选。它用的是你的语料,不是通用词典。

第三项在专业领域很实用:"报销"与"费用"、"考勤"与"打卡"——这类词在通用词典里不同义,在内部语料里同义。

// 候选词生成的两种来源
语料侧:取"包含查询任一词"的文档标题,抽高频词(如"差旅费用""费用报销")
用户侧:取历史上"零结果后用户改写成的词"(最有价值,但需要日志)
// 前者可离线算,后者需要埋点:零结果 → 下一次查询的配对

空结果还有一个"同义"的形态:非空但无关。检索返回了 5 条,看起来"有结果",而用户实际没找到。它比"空"更糟——因为空会触发引导,无关结果不会,用户只能在噪声里翻。

// 三种结果状态的判定(都要有对应引导)
空:         结果数 = 0                    → 给候选词/说明
弱相关:     最大相关分 < 阈值             → 提示"结果可能不相关"
正常:       最大相关分 ≥ 阈值             → 直接展示
// 反例:只看"结果数 > 0"就展示 → 弱相关被当成命中

5. 零结果日志怎么用

指标 用途
零结果率 判断"表达不匹配"的规模
零结果后改写率 用户是否愿意再试
改写后成功率 引导是否有效
零结果高频词 补词典/补内容

第三项(改写后成功率)是"引导质量"的唯一客观指标:如果给了候选词而用户点了仍搜不到,说明候选词是错的——这类问题只能靠这个指标发现。
第四项是最直接的行动项:高频零结果词就是"应该建同义词"或"应该补文档"的清单。

第二项反映体验:改写率低于 30% 说明用户已经放弃,此时"优化文案"意义不大,要先解决"搜不到"本身。

-- 零结果词 + 用户的下一次查询(最有价值的诊断数据)
WITH z AS (
  SELECT user_id, q, ts FROM search_log WHERE result_count = 0
), n AS (
  SELECT user_id, q, ts FROM search_log WHERE result_count > 0
)
SELECT z.q AS zero_q, n.q AS next_q, COUNT(*) c
FROM z JOIN n ON n.user_id = z.user_id AND n.ts > z.ts AND n.ts < z.ts + 60
GROUP BY 1, 2 ORDER BY c DESC LIMIT 50;
-- 输出:哪些零结果词被改写成什么 → 直接生成同义词表

落地上还有一个"埋点"的最小集合:每次搜索记录"查询词、结果数、最大相关分、是否点击"。四个字段就够支撑上面所有分析,而它们几乎不增加成本。

// 一次搜索的埋点(四字段 + 用户)
{"q":"差旅报销标准","n":0,"max_score":0.41,"clicked":false,
 "uid":123,"ts":"2026-11-10T19:00:00Z"}
// 用途:零结果率、改写率、引导有效性、候选词生成

6. 排错:五种"空结果处理不当"

现象 成因 处理方向
用户重复搜同一词 无改写引导 给候选词
用户改一次就放弃 候选词质量差 用日志学改写
放宽后结果离题 无标注 显示"已放宽"
有数据却搜不到 索引缺失 对账
认可内容但无权限 提示含混 说明并给出申请路径

第五项最伤体验:用户知道"应该有",而系统说"没有"第五项还有一个"合规"角度:"存在但不可见"本身可能是敏感信息(如"这个人的工资单存在")。所以提示的粒度要控制:说明"该内容存在但无权限",不说明"它叫什么"。
,于是他对整个系统的信任下降。正确做法是"存在但不可见"要明确说出来。

最后一条与"内容"有关的边界:空结果处理不能替代"补内容"。如果 30% 的查询是零结果,那正确动作是"补文档/补同义词",而不是"把提示写得更温柔"。提示只能修补体验,不能修补缺失。

// 两种原因的区分(看零结果率与改写成功率)
零结果率 30% + 改写成功率 40% → 表达问题(补同义词、改标题)
零结果率 30% + 改写成功率 5%  → 内容缺失(补文档)
// 混淆这两者会导致"文案越改越好,用户还是搜不到"

7. 边界:什么情况不必处理空结果

场景 为什么 做法
内部工具(用户即作者) 自己能补内容 简单提示
搜索是辅助入口 另有主路径 不处理
极低频查询 不值得 简单提示
已有强兜底(转人工) 有人接 直接转

最后一条与"成本"有关的边界:空结果处理是"低投入高感知"的一类改动。加三种文案 + 一个候选词列表,成本可能是一天,而它直接改变"用户觉得这个系统好不好用"的判断。
判断口径一句话:用户搜不到之后会不会"再试一次"。会,就必须给改写引导;不会(直接转人工),把转人工做得顺畅更重要。


8. 自测

  1. 五种"空"的原因里,哪三项是检索问题、哪两项是数据/权限问题?为什么反应要不同?
  2. 为什么"请换个说法"是坏提示?"给出候选词"为什么更有效?
  3. 自动放宽条件时为什么必须标注"放宽了什么"?
  4. "从语料里提候选词"为什么比通用词典更准?
  5. 零结果日志的四个指标里,哪一个最直接指向"该补同义词"?改写率低说明什么?
  6. 判断"是否需要处理空结果"的那个问题是什么?
S20-A17 · 空结果处理:没搜到之后做什么
https://www.clxhxhhr.top/posts/6516/
作者
clxstart
发布于
2024-12-31
许可协议
CC BY-NC-SA 4.0
评论
0 条
还没有评论,先写一条吧。