-
Notifications
You must be signed in to change notification settings - Fork 22
搜索介绍
使用 Claude Haiku 4.5 在草稿上做语言润色与校正处理
本文使用以下术语,请注意区分:
| 术语 | 定义 | 备注 |
|---|---|---|
| 程序 | ZeroLaunch-rs 管理的一个可执行项目(应用、快捷方式等) | 对应代码中 Program 结构体 |
| 搜索关键字 | 程序的标识符,包括名称、别名、拼音等 | 一个程序可有多个搜索关键字 |
| 用户输入 | 用户在搜索框输入的字符串 | 预处理后用于匹配 |
| 基础匹配分数 | 搜索模型原始评分与用户设置的稳定偏差之和 | 传入排序器的基准分数 |
| 排序分数 | 综合应用历史启动、时间偏好等因素后的最终得分 | 作为搜索结果排序的依据 |
| 搜索模型 | 实现具体匹配算法的模块 | 包括标准、Skim、Launchy 等 |
| 搜索引擎 | 调用搜索模型和排序器进行搜索的执行层 | 包括传统和语义搜索引擎 |
ZeroLaunch-rs 的搜索流程包含以下步骤:
-
用户输入触发搜索
- 用户每输入一个字符,系统会触发一次搜索查询
- 用户输入的字符串被发送到后端进行处理
-
后端搜索处理
- 后端接收用户输入,调用
ProgramManager的搜索接口update - 接口参数:搜索字符串
user_input和返回结果数量N
- 后端接收用户输入,调用
-
计算匹配分数
- 第一步:通过搜索模型计算程序与用户输入的原始匹配度
- 第二步:加上用户设置的稳定偏差,得到基础匹配分数
- 第三步:通过排序器基于历史数据计算排序分数
- 第四步:将基础匹配分数与排序分数进行加权组合得到最终分数
-
排序与返回结果
- 对所有程序按最终分数排序
- 返回分数最高的前 N 个程序作为搜索结果
ZeroLaunch-rs 支持两种搜索引擎:
使用配置的搜索模型进行匹配,支持三种搜索算法:
- 标准匹配算法(默认):容错能力强、准确性高、效果好
- Skim 算法:快速、实时性好、效果差
- Launchy 算法:轻量级、启动器导向、效果中
使用 AI 模型进行语义向量匹配,能够理解搜索意图而非仅匹配字符。
实现细节:语义搜索引擎会先将用户输入生成 embedding(向量),然后计算用户向量与每个程序的 embedding 的相似度作为基础匹配分数(base_score),再将 base_score 与排序器得分进行组合得到最终分数。
标准匹配算法是 ZeroLaunch-rs 默认的搜索算法,具有以下特点:
- 高容错性:能正确处理用户的输入错误(如多余字符、字符顺序变化等)
- 高准确性:通过多维度评分确保用户想要的程序排名靠前
- 高响应性:时间复杂度为 O(n·m),n 为搜索关键字长度,m 为用户输入长度
约束条件:当用户输入长度 > 搜索关键字长度时,直接返回极小值,不进行计算。
标准匹配算法的核心问题是:如何从众多程序中准确识别用户想要搜索的目标程序?
传统的精准匹配算法对用户输入错误的容错能力较弱。例如,当用户误输时,精准匹配可能找不到真正想要的程序。标准匹配算法通过引入容错机制解决这一问题。
最短编辑距离(Levenshtein 距离)是将一个字符串转换为另一个字符串所需的最少编辑操作数(插入、删除、替换)。更多信息参考:LeetCode 编辑距离
在本算法中,我们计算用户输入转换为程序搜索关键字任意子字符串所需的最少编辑操作数:
- 若用户输入已经是某个关键字的子字符串(无需编辑),则这个程序是极有可能的目标
- 若需要大量编辑才能匹配(如输入 5 个字符却需要修改 4 次),则匹配度应当较低
当多个程序的搜索关键字都与用户输入匹配时,应优先考虑搜索关键字较短的程序。例如:
- 用户输入 5 个字符
- 程序 A 的搜索关键字:8 个字符
- 程序 B 的搜索关键字:30 个字符
- → 程序 A 应排名更靠前
为避免长关键字对评分造成过大影响,使用 log2 函数进行平滑处理,使得关键字长度对评分有适度但不主导的影响。
当两个相同长度的搜索关键字都匹配用户输入时,应优先匹配位置更靠前的。例如:
- 用户输入:
vsc - 关键字:
vscode和aavscode - 两者都包含用户输入,但
vscode从头开始匹配,应评分更高
前缀匹配算法 计算用户输入与搜索关键字共享多长的前缀:
-
vscode与vsc的前缀长度:3 -
vscode与vss的前缀长度:2(因为第三个字符不同)
这样即使用户输入错了一个字符(如 vsc 误为 vss),仍能获得较高的评分,体现了算法的容错能力。
某些情况下,用户可能改变字符输入顺序。例如,想输入 steam 却输入成 staem。基础的编辑距离算法会给予较低评分,导致其他程序超越它。
为解决此问题,本算法借鉴 LaunchyQt 的思想,增加字符集匹配评分:
- 若用户输入的字符集是搜索关键字字符集的子集,则加上用户输入的长度作为额外分数
- 例如:
steam和staem的字符集相同,都是 {s, t, e, a, m},获得额外加分
其中:
- 编辑距离得分 = 基于最短编辑距离和关键字长度计算
- 字符集匹配得分 = 若用户字符集为关键字字符集的子集,得分为用户输入长度,否则为 0
- 前缀匹配得分 = 共享前缀长度,或用户输入为关键字子字符串时得分为用户输入长度
为了提供更灵活的搜索体验,ZeroLaunch-rs 自动为每个程序生成多个搜索关键字。这些关键字包括:
-
原始名称(小写):程序名字的小写版本
- 例:
光·遇→光·遇
- 例:
-
拼音转换:将汉字转换为拼音,英文保持不变
- 例:
光·遇→guang·yu
- 例:
-
首字母缩写:取拼音转换后版本的首字母
- 例:
光·遇→g·y
- 例:
-
首字母大写缩写(如果原名全是英文):提取所有大写字母
- 例:
PowerPoint→PP
- 例:
-
原始名称去符号:移除原始名称中的所有非字母、非数字字符
- 例:
光·遇→guangyu
- 例:
-
首字母缩写去符号:移除首字母缩写中的符号
- 例:
C++ Builder→cb(缩写为c++b,去符号后为cb)
- 例:
-
拼音去符号:移除拼音转换版本中的符号
- 例:
光·遇→guangyu
- 例:
算法针对每个关键字分别计算匹配分数,最终取其中最高分作为该程序的匹配分数。这样可以支持多种搜索方式:
-
拼音搜索:搜索
guangyu或gy可找到光·遇 -
符号容错:搜索
guangyu(不含符号)可找到光·遇(含符号) -
首字母搜索:搜索
gy可快速找到光·遇
可通过调试页面查看:在设置页面的"调试"选项卡中,使用"关键字生成"功能可以查看任何程序实际生成的所有搜索关键字。
自定义关键字:用户可以为程序添加自定义关键字,以生成更符合个人使用习惯的搜索词。
ZeroLaunch-rs 允许用户为特定程序设置稳定偏差(Stable Bias),这是一种人工干预搜索结果排名的机制。
稳定偏差的工作流程非常简单直观:
- 设置偏差:用户在配置中为想要调整的程序设置偏差值(正值提升,负值降低)
- 系统记忆:系统初始化时会读取这些设置,并将偏差值关联到对应的程序
- 搜索时应用:每次用户进行搜索时,系统会将该程序的偏差值加入基础匹配分数中
- 影响排序:更高的基础分数使得该程序在最终排序中获得更靠前的位置
简单来说:偏差是一个固定加值,只要程序被搜索到,这个加值就会被应用,从而影响排名。
稳定偏差通过程序的标识名称进行匹配。根据程序类型的不同,标识名称包括:
-
传统程序(
.exe、.lnk等):文件名(不含扩展名)- 例:
C:\Program Files\Notepad\notepad.exe→ 标识名称为"notepad"
- 例:
-
UWP 应用:应用的本地化显示名称
- 例:Windows 内置的计算器可能显示为
"计算器"或"Calculator"
- 例:Windows 内置的计算器可能显示为
-
自定义命令:命令的名称
- 例:自定义的
"快速锁屏"命令
- 例:自定义的
-
网页快捷方式:配置中设置的名称
匹配采用包含关系(不区分大小写):配置中的关键词只要出现在程序标识名称的任何位置就会被识别。例如:
- 配置
"note"的偏差会匹配"notepad"、"evernote"等包含 "note" 的程序 - 配置
"计算"的偏差会匹配"计算器"等包含 "计算" 的程序
💡 建议:使用较为特异的关键词避免误匹配。例如,使用
"notepad"比"note"更安全。
假设用户配置中设置了以下偏差:
-
"notepad.exe" →
+50(记事本) -
"report" →
+40(某报告程序)
情景 1:搜索 "note"(与记事本高度匹配)
| 程序 | 标准匹配分 | 稳定偏差 | 基础分数 | 排名影响 |
|---|---|---|---|---|
| 记事本 | 90 | +50 | 140 | ⬆️ 排名提升显著 |
| report | 40 | +40 | 80 | - |
结果:记事本因为既有高匹配分又有正偏差,基础分达到 140,远高于 report 的 80,大概率排在第一位。
情景 2:搜索 "np"(与记事本弱匹配)
| 程序 | 标准匹配分 | 稳定偏差 | 基础分数 | 排名影响 |
|---|---|---|---|---|
| 记事本 | 20 | +50 | 70 | ⬆️ 偏差救了它! |
| 70 | 0 | 70 | - |
结果:虽然记事本与 "np" 的匹配度不高(仅 20 分),但 +50 的偏差让它的基础分达到 70,与直接匹配 QQ 的分数相当,有机会参与靠前的排序。
情景 3:搜索 "qq"(记事本完全不匹配)
| 程序 | 标准匹配分 | 稳定偏差 | 基础分数 | 排名影响 |
|---|---|---|---|---|
| 记事本 | 0 | +50 | 50 | ⬇️ 依然靠后 |
| 100 | 0 | 100 | ⬆️ 直接匹配,排第一 |
结果:虽然记事本的偏差使其基础分达到 50,但它与 "qq" 完全无关,无法与真正相关的程序竞争。高偏差无法让毫不相关的程序强行排到前面。
| 使用场景 | 推荐偏差 | 作用 |
|---|---|---|
| 常用程序想优先显示 |
+30 到 +100
|
弥补某些搜索关键字不够完美时的排名劣势 |
| 不常用程序要沉底 |
-20 到 -50
|
降低其基础分,让更常用的程序优先显示 |
| 多个程序竞争同一排名 |
+20 到 +40
|
在匹配度接近时,通过偏差决出高低 |
在调试页面中,可以查看目标关键词对应的各程序匹配值,具体数值有助于辅助判断
- 偏差是永久的加成:一旦设置,程序每次被搜索时都会获得这个偏差
- 偏差不能让无关程序出现:如果程序与搜索词完全无关,高偏差无法让它排到真正相关程序之前
- 偏差的作用域:主要用于调整同类程序的相对排名,而非决定绝对排名
排序算法基于多个维度的历史数据,对基础匹配分数进行增强,以确保用户最常使用的程序能够优先显示。排序器综合考虑以下因素:
- 历史启动次数:程序的累计启动频率
- 近期使用习惯:最近 7 天的启动频率(带衰减)
- 最近热度:最近一次启动到现在的时间间隔
- 查询亲和度:特定查询词与程序的关联历史
程序的总启动次数越多,用户对它的依赖程度越高,因此应该获得更高的排序分数。
计算方法:使用自然对数函数进行缩放,防止历史次数对得分产生不受控的膨胀。代码中实际使用的公式为:
- history_score = ln(1 + history_launch_count)
默认权重:0.8(相对较低,避免过度依赖历史)
用户的习惯会随时间变化,最近 7 天的启动记录比久远的历史更能反映当前使用倾向。本算法引入衰减因子,使得近期数据权重更高。
计算方法:
- 统计最近 7 天的每日启动次数
- 第一天(最近)的权重系数为 1.0
- 每往前推一天,权重系数衰减为前一天的 1/1.3
- 累加所有天数的
启动次数 ×ばつ 权重系数
默认权重:1.5(最高权重,充分体现最近使用倾向)
如果一个程序是最近才启动过的,用户可能正在活跃使用,应该优先展示。
计算方法:
其中:
-
$K = 6$ (热度基数) -
$T = 10800$ 秒(约 3 小时,时间衰减单位)
这是一个衰减函数,启动越近得分越高。
默认权重:0.5(适度权重,不过度影响排序)
某些程序可能经常在特定的查询词下被用户启动(如搜索"git"时经常启动 Git Bash)。这种关联性应该被记录并在后续相同查询中优先考虑。
记录机制(有效次数的更新):
- 当用户输入特定查询词并启动某个程序时,系统记录这个
(查询词, 启动方式)关联 - 冷却机制:为了防止短时间内重复启动导致的刷分,引入了冷却时间(默认 60 秒)。只有超过冷却时间的启动才会被计入有效次数。
-
衰减累积:每次有效记录时,系统会更新存储的有效次数
$E_{stored}$ :
其中:
-
$E_{old}$ :更新前的有效次数,如果是初次启动,则该值为0 -
$\Delta t$ :距离上次启动的时间间隔 -
$\tau$ :衰减常数(默认 259200 秒,即 3 天) -
$+1$ :本次启动贡献的次数
这意味着,如果两次启动间隔很久,旧的次数会衰减得几乎为 0,新的有效次数就接近 1;如果间隔很短(但超过冷却时间),旧的次数几乎保留,新的有效次数就是
计算分数(查询时): 在搜索排序时,会基于存储的有效次数计算当前的亲和分数:
其中当前有效次数
默认权重:3.0(重要权重,但通过对数缩放使其更平滑)
其中:
- 基础匹配分数:搜索模型评分 + 稳定偏差
- 动态权重总和:$w_h \cdot h + w_r \cdot r + w_t \cdot t$
- 抑制因子:$\text{Clamp}(\frac{\text{基础匹配分数}}{15.0}, 0.0, 1.0)$
-
$w_h = 0.8$ (历史权重) -
$w_r = 1.5$ (近期习惯权重) -
$w_t = 0.5$ (热度权重) -
$w_q = 3.0$ (亲和度权重) -
$h, r, t, q$ 分别为各维度的得分
为了避免"高频使用的无关程序"挤占"低频使用的精准匹配程序"的问题,系统引入了基础分抑制因子:
- 抑制因子计算:$\text{抑制因子} = \frac{\text{基础匹配分数}}{15.0}$(限制在 0.0-1.0 范围内)
-
作用原理:
- 当基础匹配分数 ≥ 15.0 时,抑制因子 = 1.0,动态权重全额生效
- 当基础匹配分数 < 15.0 时,抑制因子 < 1.0,动态权重按比例衰减
- 查询亲和度权重不受抑制,因为它是用户在特定查询下的明确历史选择
举例说明:
- 搜索 "qq" 时,QQ程序基础分 ≈ 44,抑制因子 = 1.0,享受全额动态权重
- 搜索 "epi" 时,Vivaldi基础分 = 4.4,抑制因子 = 0.29,动态权重大幅衰减
- 这样确保了文本匹配度高的程序不会被历史使用频率过高的无关程序挤占
权重设置的原则:
- 查询亲和度 (3.0) 最高 → 用户在特定查询下的历史选择最能预示意图
- 近期习惯 (1.5) 次高 → 最近的使用频率能反映当前活跃应用
- 历史启动 (0.8) 较低 → 长期历史仅作参考,易过时
- 热度 (0.5) 最低 → 作为辅助增强,不主导排序
这样的设计确保了用户最有可能想要的程序排名靠前。
为提高搜索响应速度,ProgramManager 在 perform_search 阶段实现了一个短期 LRU 缓存(可配置)。但是不推荐开启,开启后,对于命中 LRU 缓存的相同查询结果将返回上次缓存的排序结果,历史/亲和度等更新不会立即生效(除非缓存刷新/失效)。
- 缓存键:对用户输入做统一预处理(小写化并移除重复空格)后的字符串。
- 缓存值:本次搜索的完整结果向量(按最终分数排序)。
- 当启用时,先检查缓存命中,命中则直接返回缓存结果;否则执行搜索并在结果生成后写入缓存。
- 缓存是否启用与容量由配置
search_cache_capacity与enable_lru_search_cache控制。
(参考实现位于 ProgramManager::perform_search)
ZeroLaunch-rs 的搜索系统通过两层评分机制实现高效准确的搜索:
- 基础匹配层(搜索模型):通过智能容错算法计算程序与用户输入的相关性
- 排序增强层(排序器):基于用户的历史行为模式调整排序顺序
两层共同作用,使得系统既能正确理解用户的查询意图,又能学习用户的使用习惯,实现"越用越聪明"的效果。