Skip to content

Latest commit

 

History

History
319 lines (223 loc) · 27.1 KB

File metadata and controls

319 lines (223 loc) · 27.1 KB
git bb48eb2a640f8f91dc6f2452dd3c84a2d4d5a52c

Поиск

Введение

Почти каждому приложению нужен поиск. Независимо от того, ищут ли пользователи релевантные статьи в базе знаний, изучают каталог товаров или задают вопросы естественным языком по набору документов, Laravel предоставляет встроенные инструменты для каждого из этих сценариев, и часто для этого не нужны внешние сервисы.

Для большинства приложений встроенных возможностей Laravel на базе базы данных более чем достаточно. Внешние поисковые сервисы обычно нужны только тогда, когда требуются исправление опечаток, фасетная фильтрация или гео-поиск в очень большом масштабе.

Полнотекстовый поиск

Когда нужен ранжированный поиск по ключевым словам, где база данных оценивает и сортирует результаты по степени совпадения с поисковыми терминами, метод построителя запросов whereFullText использует нативные полнотекстовые индексы MariaDB, MySQL и PostgreSQL. Полнотекстовый поиск понимает границы слов и стемминг, поэтому запрос "running" может находить записи, содержащие "run". Внешний сервис не требуется.

Семантический / векторный поиск

Для семантического поиска на основе ИИ, который сопоставляет результаты по смыслу, а не по точным ключевым словам, метод построителя запросов whereVectorSimilarTo использует векторные представления, сохранённые в PostgreSQL с расширением pgvector или MariaDB. Например, запрос "best wineries in Napa Valley" может найти статью "Top Vineyards to Visit", даже если слова не совпадают. Для векторного поиска требуется PostgreSQL с расширением pgvector либо MariaDB 11.7 или новее, а также Laravel AI SDK.

Реранжирование

AI SDK Laravel предоставляет возможности reranking, которые используют AI-модели для переупорядочивания любого набора результатов по семантической релевантности запросу. Реранжирование особенно эффективно как второй этап после быстрого первичного извлечения, например полнотекстового поиска: вы получаете и скорость, и семантическую точность.

Поиск Laravel Scout

Для приложений, которым нужен трейт Searchable, автоматически поддерживающий поисковые индексы Eloquent-моделей в актуальном состоянии, Laravel Scout предлагает как встроенный движок базы данных, так и драйверы для сторонних сервисов вроде Algolia, Meilisearch, Typesense и Turbopuffer.

Полнотекстовый поиск

Хотя запросы 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, подходящий для вашего драйвера базы данных: например, MATCH(...) AGAINST(...) для MariaDB и MySQL или to_tsvector(...) @@ plainto_tsquery(...) для PostgreSQL:

$articles = Article::whereFullText('body', 'web developer')->get();

При использовании MariaDB и MySQL результаты автоматически сортируются по релевантности. В PostgreSQL whereFullText фильтрует совпадающие записи, но не сортирует их по релевантности. Если вам нужна автоматическая сортировка по релевантности в PostgreSQL, рассмотрите движок базы данных Scout, который делает это за вас.

Если вы создали составной полнотекстовый индекс по нескольким столбцам, можно искать по всем ним, передав тот же массив столбцов в whereFullText:

$articles = Article::whereFullText(
    ['title', 'body'], 'web developer'
)->get();

Метод orWhereFullText позволяет добавить полнотекстовое условие как or-условие. Полные сведения смотрите в документации построителя запросов.

Семантический / векторный поиск

Полнотекстовый поиск опирается на ключевые слова: слова из запроса должны присутствовать в данных в той или иной форме. Семантический поиск использует принципиально другой подход: сгенерированные ИИ векторные представления передают смысл текста в виде массивов чисел, после чего находятся результаты, наиболее близкие к запросу по смыслу. Например, запрос "best wineries in Napa Valley" может найти статью "Top Vineyards to Visit", даже если слова совсем не пересекаются.

Базовый процесс векторного поиска выглядит так: сгенерировать векторное представление (числовой массив) для каждого фрагмента содержимого и сохранить его вместе с данными, затем во время поиска сгенерировать такое представление для пользовательского запроса и найти ближайшие к нему сохранённые представления в векторном пространстве.

Note

Векторный поиск требует Laravel AI SDK и поддерживается PostgreSQL (требуется расширение pgvector), MariaDB 11.7 или новее и MongoDB (требуется пакет Laravel MongoDB). Во всех базах данных PostgreSQL в Laravel Cloud расширение pgvector уже установлено.

Генерация векторных представлений

Векторное представление - многомерный числовой массив (обычно из сотен или тысяч чисел), передающий семантический смысл фрагмента текста. Его можно сгенерировать для строки с помощью метода toEmbeddings, доступного в классе Laravel Stringable:

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, указав размерность, соответствующую выводу используемого провайдера (например, 1536 для модели OpenAI text-embedding-3-small). Также следует вызвать 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 гарантирует, что расширение pgvector включено в вашей PostgreSQL базе данных до создания таблицы.

В Eloquent-модели используйте приведение AsVector, чтобы Laravel автоматически преобразовывал массивы PHP во векторный формат базы данных и обратно:

use Illuminate\Database\Eloquent\Casts\AsVector;

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

Подробнее о векторных столбцах и индексах смотрите в документации по миграциям.

Запросы по сходству

После сохранения векторных представлений содержимого можно искать похожие записи с помощью метода 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.

Реранжирование результатов

Реранжирование - это метод, при котором модель ИИ переупорядочивает набор результатов по их семантической релевантности заданному запросу. В отличие от векторного поиска, требующего заранее вычислить и сохранить векторные представления, реранжирование работает с любой коллекцией текста: оно принимает исходное содержимое и запрос и возвращает элементы, отсортированные по релевантности.

Реранжирование особенно эффективно как второй этап после быстрого первичного отбора. Например, с помощью полнотекстового поиска можно быстро сократить тысячи записей до 50 кандидатов, а затем применить реранжирование, чтобы поднять наиболее релевантные результаты. Такой двухэтапный подход сочетает скорость и семантическую точность.

Вы можете rerank массив строк с помощью класса 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 также имеют macro rerank, принимающий имя поля (или замыкание) и запрос, что упрощает reranking результатов Eloquent:

$articles = Article::all()
    ->rerank('body', 'Laravel tutorials');

Полные сведения о настройке reranking providers и доступных опциях смотрите в документации AI SDK.

Laravel Scout

Описанные выше техники поиска являются методами построителя запросов, которые вы вызываете напрямую в коде. Laravel Scout использует другой подход: он предоставляет трейт Searchable, который добавляется к Eloquent-моделям, после чего Scout автоматически поддерживает поисковые индексы в актуальном состоянии при создании, обновлении и удалении записей. Это особенно удобно, когда модели должны всегда быть доступны для поиска без ручного управления индексами.

Движок базы данных

Встроенный движок базы данных Scout выполняет полнотекстовый поиск и LIKE-поиск по существующей базе данных без внешнего сервиса и дополнительной инфраструктуры. Просто добавьте трейт Searchable к модели и определите метод toSearchableArray, возвращающий столбцы, которые должны быть доступны для поиска.

Вы можете использовать PHP-атрибуты, чтобы управлять стратегией поиска для каждого столбца. SearchUsingFullText использует полнотекстовый индекс базы данных, SearchUsingPrefix ищет только с начала строки (example%), а столбцы без атрибута используют стратегию LIKE с wildcard по обеим сторонам (%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

Многим приложениям никогда не нужен внешний поисковый движок. Встроенные техники, описанные на этой странице, покрывают подавляющее большинство сценариев.

Комбинирование техник

Описанные на этой странице техники поиска не исключают друг друга, и их комбинация часто дает лучший результат. Ниже два распространенных паттерна, показывающих, как эти инструменты работают вместе.

Полнотекстовое извлечение + реранжирование

Используйте полнотекстовый поиск, чтобы быстро сузить большой набор данных до кандидатов, затем примените reranking, чтобы отсортировать кандидатов по семантической релевантности. Это дает скорость нативного полнотекстового поиска базы данных и точность 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();