Sarıkaya Dev Logo

AI‑Driven Content Personalization in Laravel with OpenAI Function Calling and Scout

Mahmut Sarıkaya 5 min read 5 Views 0
AI‑Driven Content Personalization in Laravel with OpenAI Function Calling and Scout

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}\n

Inject 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.

Javascript Test

nuxt.config.ts · JavaScript

let a = "deneme";
asidşal

Javascript Test

nuxt.config.ts · JavaScript

// https://nuxt.com/docs/api/configuration/nuxt-config

export default defineNuxtConfig({
    modules: [
    
        
     
      ],
  devtools: { enabled: true },
  postcss: {
    plugins: {
      tailwindcss: {},
      autoprefixer: {},
    },
  },
  css: [
    "~/assets/main.scss"
  ],

  content: {

    markdown: {
      remarkPlugins: ['remark-emoji'],
      prism: {
        theme: false

      },
    
    },
   
  }
})
Lorem Ipsum Nedir? Lorem Ipsum, dizgi ve baskı endüstrisinde kullanılan mıgır metinlerdir. Lorem Ipsum, adı bilinmeyen bir matbaacının bir hurufat numune kitabı oluşturmak üzere bir yazı galerisini alarak karıştırdığı 1500'lerden beri endüstri standardı sahte metinler olarak kullanılmıştır. Beşyüz yıl boyunca varlığını sürdürmekle kalmamış, aynı zamanda pek değişmeden elektronik dizgiye de sıçramıştır. 1960'larda Lorem Ipsum pasajları da içeren Letraset yapraklarının yayınlanması ile ve yakın zamanda Aldus PageMaker gibi Lorem Ipsum sürümleri içeren masaüstü yayıncılık yazılımları ile popüler olmuştur. Lorem Ipsum Nedir? Lorem Ipsum, dizgi ve baskı endüstrisinde kullanılan mıgır metinlerdir. Lorem Ipsum, adı bilinmeyen bir matbaacının bir hurufat numune kitabı oluşturmak üzere bir yazı galerisini alarak karıştırdığı 1500'lerden beri endüstri standardı sahte metinler olarak kullanılmıştır. Beşyüz yıl boyunca varlığını sürdürmekle kalmamış, aynı zamanda pek değişmeden elektronik dizgiye de sıçramıştır. 1960'larda Lorem Ipsum pasajları da içeren Letraset yapraklarının yayınlanması ile ve yakın zamanda Aldus PageMaker gibi Lorem Ipsum sürümleri içeren masaüstü yayıncılık yazılımları ile popüler olmuştur. Lorem Ipsum Nedir? Lorem Ipsum, dizgi ve baskı endüstrisinde kullanılan mıgır metinlerdir. Lorem Ipsum, adı bilinmeyen bir matbaacının bir hurufat numune kitabı oluşturmak üzere bir yazı galerisini alarak karıştırdığı 1500'lerden beri endüstri standardı sahte metinler olarak kullanılmıştır. Beşyüz yıl boyunca varlığını sürdürmekle kalmamış, aynı zamanda pek değişmeden elektronik dizgiye de sıçramıştır. 1960'larda Lorem Ipsum pasajları da içeren Letraset yapraklarının yayınlanması ile ve yakın zamanda Aldus PageMaker gibi Lorem Ipsum sürümleri içeren masaüstü yayıncılık yazılımları ile popüler olmuştur.

Deneme

index.php · php

print('deneme')

Ndnen

Deneme

index.cs · csharp

public string deneme = 5;

Ndnen

Deneme

cs · cs

using System;

namespace HelloWorld
{
  class Program
  {
    static void Main(string[] args)
    {
      Console.WriteLine("Hello World!");    
    }
  }
}

Ndnen

Deneme

csharp · cs

using System;

namespace HelloWorld
{
  class Program
  {
    static void Main(string[] args)
    {
      Console.WriteLine("Hello World!");    
    }
  }
}

Ndnen

Deneme

py · py

cubes = [1, 8, 27, 65, 125]  # something's wrong here
4 ** 3  # the cube of 4 is 64, not 65!
64
cubes[3] = 64  # replace the wrong value
cubes
[1, 8, 27, 64, 125]

Ndnen

Tags: #Laravel #OpenAI #function calling #Laravel Scout #content personalization
Share:
M

Written by

Mahmut Sarıkaya

Software Developer

Comments

No comments yet. Be the first to share your thoughts!

Leave a Comment

9 + 0 =