简介

几乎所有应用程序都需要搜索功能。无论用户是在知识库中搜索相关文章、浏览产品目录,还是针对文档语料库提出自然语言问题,Laravel 都提供了内置工具来处理这些场景——而且通常无需任何外部服务即可实现。

大多数应用程序会发现,Laravel 提供的内置数据库驱动选项完全够用——只有当您需要在大规模场景下使用拼写容错、分面筛选或地理搜索等功能时,才需要外部搜索服务。

全文搜索

当您需要关键字相关性排序(即数据库根据结果与搜索词的匹配程度对结果进行评分和排序)时,Laravel 的 whereFullText 查询构建器方法会利用 MariaDB、MySQL 和 PostgreSQL 上的原生全文索引。全文搜索能够理解词边界和词干提取,因此搜索“running”可以匹配包含“run”的记录。无需外部服务。

语义 / 向量搜索

若要实现基于语义而非精确关键字匹配结果的 AI 驱动语义搜索,whereVectorSimilarTo 查询构建器方法会使用存储在 PostgreSQL(搭配 pgvector 扩展)或 MariaDB 中的向量嵌入。例如,搜索“Napa Valley 的最佳酒庄”可能会呈现一篇名为“值得参观的顶级葡萄园”的文章——尽管其中的词汇完全不重叠。向量搜索需要使用带有 pgvector 扩展的 PostgreSQL 或 MariaDB 11.7 及更高版本,以及 Laravel AI SDK。

重排序

Laravel 的 AI SDK 提供重排序功能,可使用 AI 模型根据与查询的语义相关性对任意一组结果进行重新排序。重排序作为全文搜索等快速初始检索步骤之后的第二阶段尤为强大——可为您提供速度与语义准确性的双重保障。

Laravel Scout 搜索

对于希望使用能自动将搜索索引与 Eloquent 模型同步的 Searchable 特性的应用程序,Laravel Scout 既提供内置的数据库引擎,也提供 Algolia、Meilisearch、Typesense 和 Turbopuffer 等第三方服务的驱动程序。

全文搜索

虽然 LIKE 查询在简单的子串匹配方面表现良好,但它们不理解语言。针对“running”的 LIKE 搜索无法找到包含“run”的记录,且结果不会按相关性排序——只是按数据库找到的顺序返回。全文搜索通过使用能理解词边界、词干提取和相关性评分的专用索引,解决了这两个问题,让数据库能够优先返回最相关的结果。

快速的全文搜索已内置于 MariaDB、MySQL 和 PostgreSQL 中——无需外部搜索服务。您只需为希望搜索的列添加全文索引,然后使用 whereFullText 查询构建器方法对其进行搜索即可。

目前 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 生成的向量嵌入,将文本的语义表示为数字数组,然后找出语义与查询最相似的结果。例如,搜索“Napa Valley 的最佳酒庄”可能会呈现一篇名为“值得参观的顶级葡萄园”的文章——尽管其中的词汇完全没有重叠。

向量搜索的基本工作流程是:为每条内容生成一个嵌入(一个数字数组)并将其与数据一同存储,然后在搜索时,为用户的查询生成一个嵌入,并找出在向量空间中与它最接近的已存储嵌入。

向量搜索需要 Laravel AI SDK,并支持 PostgreSQL(需要 pgvector 扩展)、MariaDB 11.7 及更高版本,以及 MongoDB(需要 Laravel MongoDB 包)。Laravel Cloud 上的所有 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(分层可导航小世界)索引,这能大幅加快在大型数据集上的相似性搜索速度:

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 模型上,使用 AsVector 类型转换,以便 Laravel 自动处理 PHP 数组与数据库向量格式之间的转换:

use Illuminate\Database\Eloquent\Casts\AsVector;

protected function casts(): array
{
    return [
        'embedding' => AsVector::class,
    ];
}

有关向量列和索引的更多详情,请参阅 迁移文档。

按相似度查询

当您为内容存储好 embedding 后,可以使用 whereVectorSimilarTo 方法搜索相似记录。该方法使用余弦相似度将给定的 embedding 与已存储的向量进行对比,过滤掉低于 minSimilarity 阈值的结果,并自动按相关性对结果排序——相似度最高的记录排在最前。该阈值应为介于 0.0 和 1.0 之间的值,其中 1.0 表示向量完全相同:

$documents = Document::query()
    ->whereVectorSimilarTo('embedding', $queryEmbedding, minSimilarity: 0.4)
    ->limit(10)
    ->get();

为了方便起见,当传入的是纯字符串而非 embedding 数组时,Laravel 会使用您配置的 embedding 提供程序自动为您生成 embedding。这意味着您可以直接传入用户的搜索查询,无需先手动将其转换为 embedding:

$documents = Document::query()
    ->whereVectorSimilarTo('embedding', 'best wineries in Napa Valley')
    ->limit(10)
    ->get();

若要对向量查询进行更底层的控制,还可使用 whereVectorDistanceLessThan、selectVectorDistance 和 orderByVectorDistance 方法。借助这些方法,您可以直接使用距离值而非相似度得分进行操作,将计算出的距离选为结果中的一列,或手动控制排序方式。欲了解完整详情,请参阅查询构造器文档和AI SDK 文档。

重排序结果

重排序是一种由 AI 模型根据每个结果与给定查询在语义上的相关性,对一组结果重新排序的技术。与需要预先计算并存储 embedding 的向量搜索不同,重排序适用于任意文本集合——它以原始内容和查询作为输入,返回按相关性排序的项目。

重排序作为快速初步检索步骤之后的第二阶段尤为强大。例如,您可以使用全文搜索快速将数千条记录缩小到前 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 采用了不同的思路:它提供了 Searchable trait,您将其添加到 Eloquent 模型中,Scout 就会在记录创建、更新和删除时自动同步搜索索引。当您希望模型始终保持可搜索状态,又不想手动管理索引更新时,这种方式尤为便捷。

数据库引擎

Scout 内置的数据库引擎会对您现有的数据库执行全文搜索和 LIKE 搜索——无需外部服务或额外基础设施。只需将 Searchable trait 添加到您的模型中,并定义一个返回要搜索列的 toSearchableArray 方法即可。

您可以使用 PHP 特性(attribute)来控制每一列的搜索策略。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,
        ];
    }
}

在指定某列应使用全文查询约束之前,请确保该列已被分配全文索引。

添加 trait 后,您可以使用 Scout 的 search 方法对模型进行搜索。Scout 的数据库引擎会自动按相关性对结果排序,即使在 PostgreSQL 上也是如此:

$articles = Article::search('Laravel')->get();

当您的搜索需求适中,且希望在不部署外部服务的情况下获得 Scout 自动索引同步的便利时,数据库引擎是绝佳选择。它能很好地处理最常见的搜索用例,包括筛选、分页以及软删除记录的处理。欲了解完整详情,请参阅Scout 文档。

第三方引擎

Scout 还支持Algolia、Meilisearch和Typesense等第三方搜索引擎。这些专门的搜索服务提供高级功能,例如拼写容错、分面筛选、地理搜索以及自定义排序规则——这些功能在超大规模场景或需要极致流畅的输入即搜体验时会变得尤为重要。

由于 Scout 为所有驱动提供了统一的 API,后续从数据库引擎切换到第三方引擎所需的代码改动极少。您可以先使用数据库引擎,仅当应用需求超出数据库能力范围时,再迁移至第三方服务。

有关配置第三方引擎的完整详情,请参阅Scout 文档。

许多应用完全不需要外部搜索引擎。本页介绍的内置技术已能覆盖绝大多数用例。

组合使用技术

本页介绍的搜索技术并非互斥——将它们组合使用往往能取得最佳效果。以下是两个常见模式,展示了这些工具如何协同工作。

全文检索 + 重排序

使用全文搜索快速将大型数据集缩小为候选集,再应用重排序按语义相关性对这些候选进行排序。这样既能获得数据库原生全文搜索的速度,又能获得 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();