Пошук
Вступ
Майже кожному застосунку потрібен пошук. Незалежно від того, чи шукають ваші користувачі релевантні статті в базі знань, чи гортають каталог товарів, чи ставлять запитання природною мовою до корпусу документів, - Laravel надає вбудовані інструменти для кожного з цих сценаріїв, і зовнішні сервіси для цього часто взагалі не потрібні.
Більшості застосунків цілком вистачить вбудованих можливостей на основі бази даних, які надає Laravel: зовнішні пошукові сервіси потрібні лише тоді, коли вам потрібні толерантність до одруківок, фасетна фільтрація чи геопошук у величезних масштабах.
Повнотекстовий пошук
Коли вам потрібне ранжування за релевантністю ключових слів - тобто щоб база даних оцінювала й сортувала результати за тим, наскільки добре вони збігаються з пошуковими термінами, - метод конструктора запитів whereFullText використовує рідні повнотекстові індекси MariaDB, MySQL та PostgreSQL. Повнотекстовий пошук розуміє межі слів і словотвір, тож пошук за «running» може знайти записи зі словом «run». Зовнішній сервіс не потрібен.
Семантичний / векторний пошук
Для семантичного пошуку на базі AI, який добирає результати за змістом, а не за точними ключовими словами, метод конструктора запитів whereVectorSimilarTo використовує векторні ембединги, збережені в PostgreSQL із розширенням pgvector. Наприклад, пошук за «best wineries in Napa Valley» може підняти статтю «Top Vineyards to Visit», хоч слова й не перетинаються. Векторний пошук потребує PostgreSQL із розширенням pgvector та Laravel AI SDK.
Переранжування
AI SDK Laravel надає можливості переранжування, які за допомогою AI-моделей перевпорядковують будь-який набір результатів за семантичною релевантністю до запиту. Переранжування особливо потужне як другий етап після швидкого початкового відбору - наприклад, повнотекстового пошуку, - даючи вам і швидкість, і семантичну точність.
Пошук через Laravel Scout
Для застосунків, яким потрібен трейт Searchable, що автоматично тримає пошукові індекси синхронізованими з моделями Eloquent, Laravel Scout пропонує і вбудований рушій бази даних, і драйвери для сторонніх сервісів на кшталт Algolia, Meilisearch та Typesense.
Повнотекстовий пошук
Хоч запити з LIKE і добре працюють для простого пошуку підрядка, вони не розуміють мови. Пошук LIKE за «running» не знайде запису зі словом «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 для вашого драйвера бази - наприклад, 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 дозволяє додати умову повнотекстового пошуку як умову «або». Усі подробиці шукайте в документації конструктора запитів.
Семантичний / векторний пошук
Повнотекстовий пошук покладається на збіг ключових слів: слова із запиту мають (у якійсь формі) траплятися в даних. Семантичний пошук діє принципово інакше: він використовує згенеровані AI векторні ембединги, щоб представити зміст тексту як масиви чисел, а потім знаходить результати, зміст яких найбільш схожий на запит. Наприклад, пошук за «best wineries in Napa Valley» може підняти статтю «Top Vineyards to Visit», хоч слова взагалі не перетинаються.
Базовий робочий процес векторного пошуку такий: згенерувати ембединг (числовий масив) для кожної одиниці контенту й зберегти його поруч із даними, а під час пошуку згенерувати ембединг запиту користувача й знайти збережені ембединги, найближчі до нього у векторному просторі.
Векторний пошук потребує Laravel AI SDK і підтримується PostgreSQL (потрібне розширення
pgvector) та MongoDB (потрібен пакет Laravel MongoDB). Усі бази Postgres на Laravel Cloud уже мають встановленийpgvector.
Генерація ембедингів
Ембединг - це багатовимірний числовий масив (зазвичай сотні чи тисячі чисел), який представляє семантичний зміст фрагмента тексту. Згенерувати ембединги для рядка можна методом toEmbeddings, доступним у класі Stringable Laravel:
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 приведіть векторний стовпець до 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 діє інакше: він надає трейт Searchable, який ви додаєте до моделей Eloquent, і 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,
];
}
}
Перш ніж вказувати, що стовпець має використовувати повнотекстові умови запиту, переконайтеся, що цьому стовпцю призначено повнотекстовий індекс.
Щойно трейт додано, ви можете шукати у своїй моделі методом search зі Scout. Рушій бази даних 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();