大多数搜索框都旨在回答一个问题:与我输入的词语匹配的是什么?这对于图书馆目录来说是正确的问题,但对于营养应用来说却是错误的问题。
当用户在 Appetec 中打开食谱页面时,他们输入的词语是请求中最不重要的部分。真正重要的是那些看不见的信息:今天剩余的卡路里、他们每周不假思索地烹饪了什么、他们是否是素食者,以及他们是否在晚餐前已经超出了预算。一个忽略所有这些的搜索引擎会很高兴地返回匹配的食谱——并且同样高兴地破坏用户打开应用的原因。这是一种旨在弥补这一差距的模式背后的架构:针对此人、此时此刻的正确相关性。
为什么仅靠文本匹配是不够的
当同一个查询应该为不同的用户返回不同的结果时——当“最佳”取决于用户从未输入的上下文时,以及当您拥有的目录小于用户期望的目录时,此模式就适用。个性化食谱搜索将相关性视为三种信号的混合,而不是单一信号:
| 信号 | 捕捉内容 |
|---|---|
| 文本 | 用户输入的内容——或在餐盘扫描模式下,检测到的内容 |
| 目标匹配度 | 食谱与用户今日此餐剩余卡路里的匹配程度 |
| 习惯 | 该特定用户实际食用此食谱的频率 |
一个简单的引擎只能看到该表的第一行。此模式将这三种信号融合成一个单一的排名列表,然后当本地目录不足时,从外部来源进行回填——每次回填时都悄悄地扩充目录。结果感觉不像是查询数据库,而更像是了解你的厨房和饮食的朋友。
流水线如何构建
该系统作为一个检索流水线运行,前端是个性化层,后端是自愈合目录。
两个搜索引擎,一份契约。 Elasticsearch是主要的:支持模糊拼写容错、英文词干提取(“grill”能找到“grilled”)、加权相关性评分。如果它不可用,相同的查询将回退到遵循相同过滤器和个性化规则的 MongoDB 搜索。搜索在故障时会降级;但绝不会消失。
用户感受得到但从未见过的层。在结果返回之前,有三件事会重塑列表。卡路里预算是根据每日目标减去已记录的摄入量计算的,以此提升符合剩余窗口的食谱——如果用户已经超出了目标,列表会悄悄地按卡路里最低优先重新排序,从而引导用户恢复健康,而非放纵。饮食历史将过去60天的用餐记录提炼成一张“你实际吃了什么”的地图,让收藏夹在第一页上触手可及。饮食偏好——素食、纯素、非素食,以及二十多个更细致的标签,如keto、gluten-free和high-protein——过滤并重新排名其下所有内容。
一个自我增长的目录。当本地结果不足时,流水线会从外部来源 FatSecret 中分页获取,将这些结果无缝地拼接进同一个无限滚动信息流中。然后,它将这些外部食谱写回本地目录,使用 LLM 将每个食谱分类为素食、非素食或纯素食,以便下次正确进行个性化——这与我们在其他地方应用混合检索架构的理念相同,即本地和外部结果需要感觉像一个系统。
三个缓存,三项任务。组合结果集、每个用户的习惯地图和外部 API 响应各自拥有独立的生命周期,因此个性化的多源查询仍能以大致与单一普通查询相同的速度返回。
值得一提的权衡
这一切并非没有代价,坦诚地说明成本是使这种模式值得信赖而不仅仅是巧妙的一部分。
相关性被刻意设计为非中立。纯粹的文本相关性排名在这里以一种积极无益的方式“公平”。结果被有目的地偏向于目标匹配和习惯,因为一个在文本上略逊一筹但符合用户预算和口味的食谱是更好的答案。“为什么这个排在第一位?”成为了一个产品决策,而不是搜索引擎的默认设置——这就是为什么这些权重存在于应用程序代码中,而不是一个非自主的配置文件中。
拥有整个目录并非目标——拥有正确的部分才是。考虑到的极端情况是完全人工管理的目录(高质量,范围窄)或完全代理外部 API(范围无限,零控制,零个性化)。该系统采取了折衷方案:以自有和用户创作的食谱为主,在需要时从外部回填,并吸收回填的内容——随着时间的推移,趋向于精确拥有用户实际搜索的内容。
弹性优先于峰值效率。两个完整的搜索引擎比一个成本更高——这与我们大多数后端决策背后的弹性优先思维相同。这种成本是故意接受的:食谱搜索是核心的日常使用功能,在这种情况下,搜索降级只是轻微的不便,但搜索缺失则意味着应用程序损坏。
个性化会牺牲可预测性。结果确实会因一天中的时间、消耗的卡路里和历史记录而异——即使是同一个用户在早餐和晚餐时也会不同。这是有意为之,但这提高了测试和支持的标准:“在我的手机上能用”在正确性被定义为每个用户、每个时刻时,就不再有太大意义了。
何时适用此模式——何时不适用
有必要明确指出此模式不应被使用的情况。当结果应根据用户从未输入的上下文而真正因用户而异时,当自有目录小于用户预期但可以通过外部丰富时,当功能足够频繁以致于优雅降级比最小化基础设施更重要时,以及当“相关性”合法地包含文本以外的信号时,它的复杂性是值得的。
当每个用户的搜索结果都相同(例如公共文档搜索、SKU 查询)时,个性化增加了成本却没有回报,这时它就是错误的工具。当目录足够小且稳定,以至于单个引擎通过简单过滤就足够时,它是不必要的。并且,在严格、相同、完全可解释的排名是硬性要求的任何地方,它都会适得其反。
实际任务
这里的检索质量被视为一个个性化问题,而不仅仅是一个匹配问题。一个在文本上完美无缺,却忽略用户是纯素食者、已经超出预算、并且整个月都做了同样五道晚餐的食谱搜索,技术上是成功的,但实际上却是失败的。这项任务从来不是找到与查询匹配的食谱——而是呈现这个人接下来应该烹饪的食谱。这个架构的每一层,从双重搜索引擎到用户超出预算时悄悄进行的重新排序,都旨在弥合这一差距。
如果您正在为自己的产品在搜索或检索方面权衡类似的构建与融合决策,我们很乐意与您深入探讨。

