搜索
简介
几乎每个应用都需要搜索。无论用户是在知识库中查找相关文章、探索产品目录,还是针对文档集提出自然语言问题,Laravel 都提供了内置工具来处理这些场景 —— 并且通常无需任何外部服务即可实现。
大多数应用会发现,Laravel 提供的内置数据库驱动方案已经足够 —— 只有当您需要在大规模场景下实现拼写容错、分面筛选或地理位置搜索等功能时,才需要外部搜索服务。
全文搜索
当您需要关键词相关性排序(即数据库根据结果与搜索词的匹配程度进行评分和排序)时,Laravel 的 whereFullText 查询构建器方法可以利用 MariaDB、MySQL 和 PostgreSQL 的原生全文索引。全文搜索能够理解单词边界和词干提取,因此搜索 “running” 可以匹配包含 “run” 的记录。无需外部服务。
语义/向量搜索
对于基于含义而非精确关键词的 AI 驱动语义搜索,whereVectorSimilarTo 查询构建器方法使用存储在 PostgreSQL 中的向量嵌入(通过 pgvector 扩展)。例如,搜索 “best wineries in Napa Valley” 可以呈现标题为 “Top Vineyards to Visit” 的文章 —— 即使单词完全不重叠。向量搜索需要安装 pgvector 扩展的 PostgreSQL 以及 Laravel AI SDK。
重排序
Laravel 的 AI SDK 提供了重排序功能,能够使用 AI 模型根据语义相关性对任意结果集进行重新排序。作为快速初始检索步骤(如全文搜索)之后的第二阶段,重排序尤为强大 —— 兼顾了速度与语义准确性。
Laravel Scout 搜索
对于希望使用 Searchable 特性、让搜索索引随 Eloquent 模型的创建、更新、删除自动同步的应用,Laravel Scout 既提供了内置的数据库引擎,也支持 Algolia、Meilisearch、Typesense 等第三方服务驱动。
全文搜索
LIKE 查询虽然适用于简单的子串匹配,但它不理解语言。用 LIKE 搜索 “running” 无法找到包含 “run” 的记录,并且结果不会按相关性排序 —— 它们只是按数据库发现的顺序返回。全文搜索通过使用能理解单词边界、词干提取和相关性评分的专用索引解决了这两个问题,使数据库能够优先返回最相关的结果。
MariaDB、MySQL 和 PostgreSQL 都内置了快速的全文搜索 —— 无需外部搜索服务。您只需在要搜索的列上添加全文索引,然后使用 whereFullText 查询构建器方法进行搜索。
WARNING
目前仅 MariaDB、MySQL 和 PostgreSQL 支持全文搜索。
添加全文索引
要使用全文搜索,首先需要在要搜索的列上添加全文索引。您可以对单个列添加索引,也可以传入一个列数组来创建复合索引,从而同时跨多个字段进行搜索:
Schema::create('articles', function (Blueprint $table) {
$table->id();
$table->string('title');
$table->text('body');
$table->timestamps();
$table->fullText(['title', 'body']);
});在 PostgreSQL 上,您可以为索引指定语言配置,该配置控制单词如何词干提取:
$table->fullText('body')->language('english');有关创建索引的更多信息,请查阅迁移文档。
执行全文查询
索引就位后,使用 whereFullText 查询构建器方法进行搜索。Laravel 会为您的数据库驱动生成适当的 SQL —— 例如,MariaDB 和 MySQL 上是 MATCH(...) AGAINST(...),PostgreSQL 上是 to_tsvector(...) @@ plainto_tsquery(...):
$articles = Article::whereFullText('body', 'web developer')->get();使用 MariaDB 和 MySQL 时,结果会自动按相关性分数排序。在 PostgreSQL 上,whereFullText 会筛选匹配的记录,但不会按相关性排序 —— 如果您需要在 PostgreSQL 上自动按相关性排序,可以考虑使用 Scout 的数据库引擎,它会为您处理排序。
如果您创建了跨多个列的复合全文索引,可以通过将相同的列数组传递给 whereFullText 来同时搜索所有这些列:
$articles = Article::whereFullText(
['title', 'body'], 'web developer'
)->get();orWhereFullText 方法可用于将全文搜索子句添加为“或”条件。有关完整细节,请查阅查询构建器文档。
语义/向量搜索
全文搜索依赖于关键词匹配 —— 查询中的单词必须以某种形式出现在数据中。语义搜索则采用了根本不同的方法:它使用 AI 生成的向量嵌入将文本的含义表示为数字数组,然后找出与查询含义最相似的结果。例如,搜索 “best wineries in Napa Valley” 可以呈现标题为 “Top Vineyards to Visit” 的文章 —— 即使单词完全不重叠。
向量搜索的基本工作流程是:为每个内容生成一个嵌入(一个数字数组)并将其与数据一起存储,然后在搜索时为用户的查询生成一个嵌入,并找出与查询向量在向量空间中最接近的存储嵌入。
NOTE
向量搜索需要安装 pgvector 扩展的 PostgreSQL 数据库以及 Laravel AI SDK。所有 Laravel Cloud Serverless Postgres 数据库都已包含 pgvector。
生成嵌入
嵌入是一个高维数字数组(通常包含成百上千个数字),它表示一段文本的语义含义。您可以使用 Laravel 的 Stringable 类上的 toEmbeddings 方法为字符串生成嵌入:
use Illuminate\Support\Str;
$embedding = Str::of('Napa Valley has great wine.')->toEmbeddings();若要一次为多个输入生成嵌入(这比逐个生成更高效,因为只需一次对嵌入提供者的 API 调用),请使用 Embeddings 类:
use Laravel\Ai\Embeddings;
$response = Embeddings::for([
'Napa Valley has great wine.',
'Laravel is a PHP framework.',
])->generate();
$response->embeddings; // [[0.123, 0.456, ...], [0.789, 0.012, ...]]有关配置嵌入提供者、自定义维度及缓存的更多细节,请查阅 AI SDK 文档。
存储与索引向量
要存储向量嵌入,请在迁移中定义一个 vector 列,指定与嵌入提供者输出匹配的维度数(例如,OpenAI 的 text-embedding-3-small 模型为 1536)。您还应该在该列上调用 index 来创建 HNSW(Hierarchical Navigable Small World)索引,该索引可以极大加快大数据集上的相似性搜索速度:
Schema::ensureVectorExtensionExists();
Schema::create('documents', function (Blueprint $table) {
$table->id();
$table->string('title');
$table->text('content');
$table->vector('embedding', dimensions: 1536)->index();
$table->timestamps();
});Schema::ensureVectorExtensionExists 方法确保在创建表之前已在您的 PostgreSQL 数据库上启用了 pgvector 扩展。
在您的 Eloquent 模型上,将向量列强制转换为 array,以便 Laravel 自动处理 PHP 数组与数据库向量格式之间的转换:
protected function casts(): array
{
return [
'embedding' => 'array',
];
}有关向量列和索引的更多细节,请查阅迁移文档。
相似性查询
为内容存储嵌入后,您可以使用 whereVectorSimilarTo 方法搜索相似记录。该方法通过余弦相似度将给定的嵌入与存储向量进行比较,过滤掉低于 minSimilarity 阈值的结果,并自动按相关性排序 —— 最相似的记录优先。阈值应为 0.0 到 1.0 之间的值,1.0 表示向量完全相同:
$documents = Document::query()
->whereVectorSimilarTo('embedding', $queryEmbedding, minSimilarity: 0.4)
->limit(10)
->get();方便起见,当传入普通字符串而非嵌入数组时,Laravel 会自动使用您配置的嵌入提供者为该字符串生成嵌入。这意味着您可以直接传入用户的搜索查询,无需手动将其转换为嵌入:
$documents = Document::query()
->whereVectorSimilarTo('embedding', 'best wineries in Napa Valley')
->limit(10)
->get();若要对向量查询进行更底层的控制,还可使用 whereVectorDistanceLessThan、selectVectorDistance 和 orderByVectorDistance 方法。这些方法允许您直接处理距离值而非相似度分数,将计算出的距离作为列在结果中返回,或手动控制排序。有关完整细节,请查阅查询构建器文档及 AI SDK 文档。
结果重排序
重排序是一种技术:AI 模型根据每条结果与给定查询的语义相关性对结果集进行重新排序。与向量搜索不同,向量搜索需要您预先计算并存储嵌入,而重排序可以对任何文本集合进行操作 —— 它将原始内容和查询作为输入,返回按相关性排序的条目。
作为快速初始检索步骤之后的第二阶段,重排序尤为强大。例如,您可以使用全文搜索快速将数千条记录缩小至前 50 个候选,然后使用重排序将最相关的结果置顶。这种“检索后重排序”模式兼顾了速度与语义准确性。
您可以使用 Reranking 类对字符串数组进行重排序:
use Laravel\Ai\Reranking;
$response = Reranking::of([
'Django is a Python web framework.',
'Laravel is a PHP web application framework.',
'React is a JavaScript library for building user interfaces.',
])->rerank('PHP frameworks');
$response->first()->document; // "Laravel is a PHP web application framework."Laravel 集合也提供了一个 rerank 宏,它接受字段名(或闭包)和查询,使得对 Eloquent 结果进行重排序变得容易:
$articles = Article::all()
->rerank('body', 'Laravel tutorials');有关配置重排序提供者及可用选项的完整细节,请查阅 AI SDK 文档。
Laravel Scout
上述搜索技术均为可直接在代码中调用的查询构建器方法。Laravel Scout 则采用了不同的方式:它为您的 Eloquent 模型提供了一个 Searchable 特性,当记录被创建、更新或删除时,Scout 会自动同步搜索索引。当您希望模型始终保持可搜索状态、无需手动管理索引更新时,这尤为便利。
数据库引擎
Scout 的内置数据库引擎对您现有的数据库执行全文搜索和 LIKE 搜索 —— 无需任何外部服务或额外的基础设施。只需为模型添加 Searchable 特性,并定义 toSearchableArray 方法,返回您希望使其可搜索的列。
您可以使用 PHP 属性来控制每列的搜索策略。SearchUsingFullText 将使用数据库的全文索引,SearchUsingPrefix 仅匹配字符串开头(example%),未指定属性的列将使用默认的 LIKE 策略,并在两侧添加通配符(%example%):
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Laravel\Scout\Attributes\SearchUsingFullText;
use Laravel\Scout\Attributes\SearchUsingPrefix;
use Laravel\Scout\Searchable;
class Article extends Model
{
use Searchable;
#[SearchUsingPrefix(['id'])]
#[SearchUsingFullText(['title', 'body'])]
public function toSearchableArray(): array
{
return [
'id' => $this->id,
'title' => $this->title,
'body' => $this->body,
];
}
}WARNING
在指定某列应使用全文查询约束之前,请确保该列已添加全文索引。
添加特性后,您可以使用 Scout 的 search 方法搜索模型。Scout 的数据库引擎会自动按相关性排序结果,即使在 PostgreSQL 上也是如此:
$articles = Article::search('Laravel')->get();当您的搜索需求适中,并且希望享受 Scout 自动索引同步的便利性而又无需部署外部服务时,数据库引擎是一个极佳的选择。它能很好地处理最常见的搜索用例,包括筛选、分页及软删除记录的处理。有关完整细节,请查阅 Scout 文档。
第三方引擎
Scout 也支持第三方搜索引擎,如 Algolia、Meilisearch 和 Typesense。这些专用搜索服务提供了拼写容错、分面筛选、地理位置搜索及自定义排序规则等高级功能 —— 这些功能在超大规模场景下或您需要高度完善的“边输入边搜索”体验时变得尤为重要。
由于 Scout 在其所有驱动之上提供了统一的 API,后期从数据库引擎切换至第三方引擎只需极少的代码改动。您可以从数据库引擎开始,仅在应用需求超出数据库能力范围时再迁移至第三方服务。
有关配置第三方引擎的完整细节,请查阅 Scout 文档。
NOTE
许多应用永远不需要外部搜索引擎。本文介绍的内置技术已覆盖绝大多数用例。
组合技术
本文描述的搜索技术并非互斥 —— 组合使用它们往往能产生最佳效果。以下两种常见模式展示了这些工具如何协同工作。
全文检索 + 重排序
使用全文搜索快速将大数据集缩小至候选集,然后应用重排序按语义相关性对这些候选进行排序。这兼顾了数据库原生全文搜索的速度与 AI 驱动的相关性评分的准确性:
$articles = Article::query()
->whereFullText('body', $request->input('query'))
->limit(50)
->get()
->rerank('body', $request->input('query'), limit: 10);向量搜索 + 传统筛选
将向量相似度与标准的 where 子句结合,将语义搜索限定在记录的某个子集中。当您需要基于含义的搜索,但又必须按所有权、分类或其他任何属性限制结果时,这非常有用:
$documents = Document::query()
->where('team_id', $user->team_id)
->whereVectorSimilarTo('embedding', $request->input('query'))
->limit(10)
->get();