Why personalization matters in modern Laravel apps
Visitors expect a web experience that feels tailor‑made for their interests. A 2023 study from Statista shows that 71% of online shoppers are more likely to buy from sites that recommend products based on their behavior. In a Laravel ecosystem, delivering that level of relevance without bloated custom code is a real challenge.
This article shows how to combine OpenAI function calling with Laravel Scout to create a lightweight, AI‑powered personalization layer that scales.
Understanding OpenAI function calling
OpenAI’s function calling feature lets you describe a JSON schema that the model can invoke as a deterministic function. Instead of parsing free‑form text, you receive a structured payload that your Laravel service can act on immediately.
For content personalization the function typically returns a list of content IDs, tags, or user segments. The model decides which function to call based on the conversation context, eliminating ambiguous prompts.
use OpenAI\Client;\n\n$client = new Client(['api_key' => env('OPENAI_API_KEY')]);\n\n$function = [\n 'name' => 'suggest_content',\n 'description' => 'Return an array of content IDs relevant to the user',\n 'parameters' => [\n 'type' => 'object',\n 'properties' => [\n 'ids' => [\n 'type' => 'array',\n 'items' => ['type' => 'string']\n ]\n ],\n 'required' => ['ids']\n ]\n];\n\n$response = $client->chat()->create([\n 'model' => 'gpt-4o',\n 'messages' => [['role' => 'user', 'content' => 'I love hiking and photography']]],\n 'functions' => [$function]\n);\n\n$payload = $response->json()['choices'][0]['message']['function_call']['arguments'];\n$ids = json_decode($payload, true)['ids'];Notice how the returned $ids array can be fed directly into a Scout query.
Integrating Laravel Scout for fast retrieval
Laravel Scout abstracts full‑text search engines like Algolia, Meilisearch, or the built‑in database driver. When you already have a list of content IDs from OpenAI, Scout can fetch the matching models in a single, indexed query.
First, install Scout and a driver (Meilisearch is a popular open‑source choice):
composer require laravel/scout\ncomposer require meilisearch/meilisearch-php\nphp artisan scout:install\nphp artisan vendor:publish --provider="Laravel\Scout\ScoutServiceProvider"Configure .env with the Meilisearch host and key, then add the Searchable trait to your Content model.
use Laravel\Scout\Searchable;\n\nclass Content extends Model\n{\n use Searchable;\n\n // Define the fields that should be indexed\n public function toSearchableArray()\n {\n return [\n 'title' => $this->title,\n 'body' => $this->body,\n 'tags' => $this->tags,\n ];\n }\n}Now you can retrieve personalized content with a single Scout call:
$personalized = Content::search()->whereIn('id', $ids)->take(10)->get();Building a personalized content service
Wrap the OpenAI call and Scout query inside a dedicated service class. This keeps controllers thin and makes unit testing straightforward.
namespace App\Services;\n\nuse OpenAI\Client;\nuse App\Models\Content;\n\nclass PersonalizationService\n{\n protected $client;\n\n public function __construct(Client $client)\n {\n $this->client = $client;\n }\n\n public function forUser(string $prompt): \Illuminate\Support\Collection\n {\n $function = $this->functionSchema();\n $response = $this->client->chat()->create([\n 'model' => 'gpt-4o',\n 'messages' => [['role' => 'user', 'content' => $prompt]],\n 'functions' => [$function]\n ]);\n $args = json_decode($response->json()['choices'][0]['message']['function_call']['arguments'], true);\n $ids = $args['ids'];\n return Content::search()->whereIn('id', $ids)->take(10)->get();\n }\n\n protected function functionSchema(): array\n {\n return [\n 'name' => 'suggest_content',\n 'description' => 'Suggest up to 10 content IDs based on user intent',\n 'parameters' => [\n 'type' => 'object',\n 'properties' => [\n 'ids' => [\n 'type' => 'array',\n 'items' => ['type' => 'string']\n ]\n ],\n 'required' => ['ids']\n ]\n ];\n }\n}\nInject this service into a controller and return a JSON payload:
use App\Services\PersonalizationService;\n\npublic function feed(PersonalizationService $service)\n{\n $prompt = request('interest'); // e.g., "urban gardening tips"\n $content = $service->forUser($prompt);\n return response()->json($content);\n}Testing and debugging the AI pipeline
Because the OpenAI response is deterministic only when the function schema matches, start by logging the raw payload. Laravel’s built‑in logging makes this trivial:
Log::debug('OpenAI payload', ['payload' => $response->json()]);Write a PHPUnit test that mocks the OpenAI client. The mock should return a fixed JSON with known IDs, allowing you to assert that Scout receives exactly those IDs.
$mock = $this->createMock(Client::class);\n$mock->method('chat')->willReturnSelf();\n$mock->method('create')->willReturn((object)[\n 'json' => fn() => ['choices' => [[\n 'message' => [\n 'function_call' => [\n 'arguments' => '{"ids":["c1","c2","c3"]}'\n ]\n ]\n ]]]\n]);\n$service = new PersonalizationService($mock);\n$results = $service->forUser('test');\n$this->assertCount(3, $results);Performance considerations
OpenAI latency averages 200‑400 ms for function‑calling requests (as of Q4 2023). To keep end‑user response times under one second, cache the AI suggestions for a short window (e.g., five minutes) using Laravel’s cache driver.
$cacheKey = 'personalization:'.md5($prompt);\n$ids = Cache::remember($cacheKey, now()->addMinutes(5), function () use ($prompt, $service) {\n return $service->forUser($prompt)->pluck('id')->toArray();\n});\n$personalized = Content::whereIn('id', $ids)->take(10)->get();Combine caching with Scout’s eager loading of relationships to avoid N+1 queries. The result is a fast, AI‑driven feed that feels native to the user.
Conclusion
By marrying OpenAI function calling with Laravel Scout, developers gain a deterministic, searchable bridge between AI insight and database performance. The pattern scales: the AI layer decides *what* to show, Scout decides *how* quickly to fetch it. Implement the service, add caching, and you’ll deliver personalized experiences that meet modern e‑commerce expectations without reinventing the wheel.
Start experimenting today, monitor latency, and iterate on the function schema to refine relevance. The combination of Laravel’s elegance and OpenAI’s language power is a competitive advantage for any content‑rich platform.
Author: Mahmut Sarıkaya — sarikayadev.com
Sources
OpenAI API Documentation; Laravel Scout Official Documentation; Meilisearch Getting Started Guide.