语义搜索功能
大家好,最近河内的秋天气息越来越清新了,你有没有发现?早上很凉,晚上风很大。但那之后的一周对我来说很忙碌。我专注于完成公司项目的“最后期限”,晚上则努力完成博客的搜索功能。这个最后期限与往常不同,因为这是产品全年的主要功能。至于博客,搜索功能迟早要完成,现在正是完成它的最佳时机。
在切换到 Fresh 之前,我的博客已经有搜索功能了。当时我使用的方式是使用 Postgres 的全文搜索。对于那些不知道的人来说,在使用 Postgres 之前,我也使用 redisearch 进行搜索。一般来说,Postgres 的结果更好,而 redisearch 更复杂。但实际上,我的博客数据并没有那么庞大,所以 redisearch 没有机会大放异彩。
当我切换到 Fresh 时,人工智能正蓬勃发展。很多人都在谈论人工智能以及它能做什么。在完成基本功能并准备开发搜索功能后,我想,“为什么不尝试使用人工智能呢?”所以,我决定“发布”没有搜索功能的新博客版本。
为了使用 AI 创建搜索功能,我不得不花费大量时间进行研究和实验。我学习了如何实现它、如何使用 LLM 模型、嵌入模型、向量数据类型、如何将数据转换为向量以及如何查询...
简单来说,向量是一组有限的数字,就像数学中一样。集合中的元素数量决定了向量的大小(维度)。大小越大,向量越能概括它所代表的数据。要将常规数据(文本、语音、图像等)转换为向量,有很多方法,但由于如今 LLM 的流行,您只需将数据放入嵌入模型中,它就会为您提供向量数据。
语义搜索(semantic search)与传统的全文关键词搜索不同。全文搜索是根据输入的文本字符数量来匹配并返回最相关的结果。而语义搜索则是基于内容的。假设你的文章是解释node.js是如何工作的。当搜索短语“how node.js works”时,语义搜索可以找到这篇文章。另一方面,全文搜索会尝试查找包含单词“node.js”、“works”的文章...
要查询向量数据,您至少需要两个步骤。首先,将查询转换为向量,然后使用查询函数。例如,使用 pg-vector(支持向量的 Postgres 扩展)有以下查询函数:

您可以将 L2 距离、余弦距离、L1 距离……视为向量比较方法。根据用例,您可以相应地选择查询类型。例如,在搜索问题中,我选择了余弦距离方法 - 也就是说,两个向量应该具有相似的形状。
如何操作

首先,选择合适的数据库。我使用 Turso 作为主数据库。但是,Turso 基于 SQLite,而 SQLite 并未针对矢量数据进行优化。虽然他们引入了扩展来支持矢量,但这有点复杂。
pg-vector 则相反。它被广泛使用,是 Postgres 的扩展。说到 Postgres,我想到的是 Supabase,它提供免费使用。Supabase 集成了 pg-vector,只需单击一下即可激活,这是一个很好的选择。
接下来是选择模型。为了节省成本,我从一开始就一直在寻找免费模型。我忍不住提到了 groq 及其 Completions API。但是,groq 没有嵌入模型,所以我不得不寻找另一个。
nomic-embed-text 是我在 Ollama 库中找到的一个嵌入模型。它可以将文本矢量化。此外,Nomic 提供了一个有限制的免费嵌入 API。不过,我应该提醒你,Nomic 不是一个多语言模型。它对越南语的支持有限,因此生成的向量可能不是越南语语义的最佳选择。
一切准备就绪后,就该编写代码来添加矢量数据和搜索逻辑了。
首先,将文章内容转换为向量并将其存储在 Supabase 中。我没有转换整个文章内容,而是总结了文章的主要内容,然后将其输入到 nomic-embed-text 中。这有助于删除不必要的信息并减少模型要处理的输入标记数。
另一个需要注意的是,尽管这些模型有免费的 API,但它们总是有局限性。第一次处理数据非常昂贵,因为我有 400 多篇越南语和英语文章。更好的方法是在本地运行 Llama 3.2 3B 和 nomic-embed-text 模型。我为此使用 LM Studio。
搜索逻辑很简单。获取用户的查询 -> 通过 nomic-embed-text 转换为向量 -> 查询与文章向量的余弦并按两个向量之间的最近距离排序。
但是,如果用户搜索的是 node.js、javascript 等关键字,语义搜索很可能不会返回结果,因为数据太短,生成的向量所包含的含义不够多,导致余弦距离过大。因此,为了处理这种情况,我需要维护一个全文搜索机制。幸运的是,Supabase 支持这种类型的搜索。
挑战
回想起来,这看起来很简单,但对我来说最具挑战性的部分是数据预处理步骤。
一篇文章通常会传达多个思想,包括主要内容和次要内容。通常,搜索者只对文章的主要内容感兴趣,他们倾向于搜索相关的东西。如果我将整个文章内容转换成一个向量,它会被“稀释”或“嘈杂”,因为向量大小是有限的。我认为如果我可以删除次要信息并强调主要思想,搜索会更准确。想象一下,一篇有 1500 个单词的文章转换成 1024 维的向量,与一篇只有 500 个单词的主要内容的文章在同一个向量中。哪一个更“清晰”地表示数据?
用户的搜索模式也很难预测,因为每个人的搜索方式都不同。有些人喜欢写得简短,而有些人则喜欢写得长一点或提供问题的背景……因此,处理用户输入数据也是一个挑战。我如何将其转换为与博客上的搜索内容相匹配的简洁且相关的查询?
所用 AI 模型的质量也是一个问题。通常,模型训练得越多越好,商业模型也有质量保证。但是,为了尽量降低成本,我目前使用有限制的免费 LLM 模型。希望有一天我能集成更强大的模型来提高我博客的搜索质量。