Показувати однакові пости всім користувачам — стратегія, що швидко вичерпує себе. Щоб утримувати аудиторію, платформа має пропонувати персоналізований контент на основі того, що людина вже переглядає.
Зазвичай це реалізують через теги або випадкову видачу. Проте такий підхід неточний. Самих лише тегів замало, щоб ідеально підібрати наступний матеріал.
Сучасні гіганти на кшталт Netflix, Facebook та LinkedIn використовують рекомендаційні системи на базі AI. Це дозволяє пропонувати релевантний контент і підтримувати високий рівень залученості.
# Що ми створюємо
У цьому туторіалі ми розробимо простий рекомендаційний рушій для блогу на Laravel. Ми використаємо MongoDB, vector embeddings та MongoDB Vector Search, щоб рекомендувати контент за змістом, а не просто за ключовими словами чи тегами.
Наприклад, якщо користувач читає статтю "Getting Started with Laravel APIs", система запропонує "Building REST APIs in Laravel" або "Laravel API Authentication".
Це концептуальний підхід: платформа розуміє суть тексту і підбирає схожі за значенням матеріали.
Як це працює зсередини:
- Перетворюємо пости у вектори (embeddings).
- Зберігаємо їх у MongoDB.
- Використовуємо векторний пошук для знаходження схожого контенту.
Тож, почнімо.
# Що знадобиться
Для роботи вам будуть потрібні:
- Знання Laravel.
- Налаштоване середовище розробки Laravel.
- Кластер MongoDB Atlas.
- Набір даних (dataset) для наповнення колекції постів.
# Що таке AI Embeddings
Ембединг — це спосіб перетворення тексту в набір чисел, зрозумілий комп'ютеру. Уявіть, що ви перекладаєте зміст речення у довгий список цифр, наприклад: [0.12, -0.44, 0.88, ...].
Самі по собі ці цифри нічого не скажуть людині, але для комп'ютера це формат, який зручно порівнювати.
Якщо два тексти схожі за змістом, їхні числові списки (вектори) також будуть подібними.
Наприклад:
- "Laravel API authentication"
- "Securing APIs in Laravel"
Ці фрази написані по-різному, але мають спільну ідею. Після перетворення в ембединги їхні вектори будуть розташовані близько один до одного у математичному просторі.
Коли ви маєте сотні постів, перетворених на вектори, ви можете просто запитати систему: "Які ще пости мають схожі вектори до того, що зараз читає користувач?". Найближчі варіанти і будуть найкращими рекомендаціями.
# Генерація ембедингів для постів
Спершу створимо новий проект Laravel та налаштуємо підключення до MongoDB Atlas:
composer create project laravel/laravel devrel-tutorial-contentPersonalization
Laravel не має вбудованої підтримки MongoDB, тому нам знадобиться розширення PHP для MongoDB (переконайтеся, що воно додане у ваш php.ini) та відповідний пакет.
Встановлюємо пакет mongodb/laravel-mongodb:
composer require mongodb/laravel-mongodb
# Налаштування .env
Додайте дані для підключення до MongoDB Atlas у файл .env, замінивши MONGODB_URL на ваш рядок підключення.
# Конфігурація config/database.php
Додайте налаштування MongoDB у масив connections файлу config/database.php:
'mongodb' => [
'driver' => 'mongodb',
'dsn' => env('MONGODB_URI'),
'database' => env('MONGODB_DATABASE'),
],
Та встановіть MongoDB як з'єднання за замовчуванням:
'default' => env('DB_CONNECTION', 'mongodb'),
Оскільки ми працюємо із семантикою, нам потрібні реальні тексти, а не випадковий набір слів від Faker. Створимо модель Post, контролер та фабрику:
php artisan make:model Post -cf
Оновіть app/Models/Post.php:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use MongoDB\Laravel\Eloquent\Model;
class Post extends Model
{
use HasFactory;
protected $connection = 'mongodb';
protected $collection = 'posts';
protected $fillable = [
'title',
'body',
'embedding'
];
}
Для генерації ембедингів ми використаємо безкоштовну та відкриту модель BAAI/bge-small-en-v1.5 через Hugging Face. Вам знадобиться API-ключ Hugging Face.
Додайте параметри в .env:
HUGGINGFACE_API_KEY="YOUR_API_KEY"
HUGGINGFACE_API_URL="https://router.huggingface.co/hf-inference/models/BAAI/bge-small-en-v1.5"
Створіть сервіс app\Services\EmbeddingService.php:
<?php
namespace App\Services;
use Illuminate\Support\Facades\Http;
class EmbeddingService
{
public function generate(string $text): array
{
$response = Http::withHeaders([
'Authorization' => 'Bearer ' . env('HUGGINGFACE_API_KEY'),
])->post(env('HUGGINGFACE_API_URL'), [
'inputs' => $text,
]);
return $response->json();
}
}
Цей сервіс відправляє текст у Hugging Face API і отримує векторне представлення, яке ми збережемо в базу даних для подальшого пошуку.
# Збереження ембедингів у MongoDB
При створенні постів ми будемо генерувати вектори для кожного з них. Ми підготували набір реальних даних у App\Data\PostData.php (повний список постів доступний у сирцях), щоб перевірити саме смислову схожість.
Оновіть database\Factories\PostFactory.php:
<?php
namespace Database\Factories;
use App\Models\Post;
use Illuminate\Database\Eloquent\Factories\Factory;
class PostFactory extends Factory
{
public function definition(): array
{
return [
'title' => fake()->sentence(),
'body' => fake()->paragraphs(3, true),
'embedding' => [],
];
}
}
# Seeder
Створіть сідер PostSeeder:
php artisan make:seeder PostSeeder
Код сідера об'єднує заголовок та зміст поста, генерує ембединг через наш сервіс та зберігає все в MongoDB:
foreach ($postData as $post) {
$text = $post['title'] . ' ' . $post['body'];
$embedding = $embeddingService->generate($text);
Post::create([
'title' => $post['title'],
'body' => $post['body'],
'embedding' => $embedding,
]);
}
Запустіть наповнення бази:
php artisan db:seed
# Налаштування MongoDB Vector Search
Оскільки MongoDB підтримує векторний пошук нативно, нам не потрібні сторонні рішення. У панелі Atlas створіть Search Index типу Vector Search з назвою vector_index.
Використовуйте JSON Editor для конфігурації:
{
"fields": [
{
"type": "vector",
"path": "embedding",
"numDimensions": 384,
"similarity": "cosine"
}
]
}
Важливо: numDimensions має бути 384, що відповідає обраній моделі Hugging Face.
# Створення запиту на рекомендації
Тепер найцікавіше: реалізуємо логіку пошуку в app\Http\Controllers\PostController.php. Ми використовуємо агрегацію $vectorSearch, щоб знайти 5 постів, чиї вектори найбільш схожі на вектор поточного документа.
$cursor = $collection->aggregate([
[
'$vectorSearch' => [
'index' => 'vector_index',
'queryVector' => $post->embedding,
'path' => 'embedding',
'numCandidates' => 100,
'limit' => 5,
]
]
]);
Цей метод знаходить тематично близькі статті, фільтрує поточну статтю з результатів та повертає JSON із заголовками рекомендацій.
# Тестування
Додайте маршрут у routes/api.php:
Route::get('/posts/{id}', [PostController::class, 'index']);
Тепер, звернувшись до API за ID поста, ви отримаєте список схожих матеріалів, підібраних на основі їхнього реального змісту.
# Як покращити систему
На базі цього фундаменту можна створити значно складнішу систему:
- Персоналізація: враховуйте історію переглядів конкретного користувача, а не лише поточний пост.
- Гібридний пошук: комбінуйте векторний пошук (за змістом) із традиційним пошуком за ключовими словами для більшої точності.
- Кешування: генерація ембедингів та векторний пошук потребують ресурсів. Кешуйте результати для популярних постів.
# Продуктивність та масштабування
Для швидкої роботи важливо правильно налаштувати векторний індекс у MongoDB. Параметр numCandidates впливає на точність і швидкість: вищі значення покращують результат, але збільшують час запиту. Також варто генерувати ембединги лише один раз — під час створення або редагування посту, щоб не навантажувати зовнішні API при кожному перегляді.
# Висновок
Ми побудували рекомендаційний рушій, який розуміє контекст. Такий підхід значно ефективніший за звичайні теги і широко використовується в медіа, e-commerce та освітніх платформах для покращення клієнтського досвіду.
Повний код проекту доступний на GitHub.