RAG in Laravel with pgvector and Embeddings | Mohamed Said        [  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MH.png)   Mohamed Said Laravel Backend Engineer  ](https://www.msaied.com) [ Home ](https://www.msaied.com) [ Projects ](https://www.msaied.com/projects) [ Articles  ](https://www.msaied.com/articles) [ Certificates ](https://www.msaied.com/certificates) [ Contact ](https://www.msaied.com#contact-section) 

       [  ](https://github.com/EG-Mohamed)       

 [ Home ](https://www.msaied.com) [ Projects ](https://www.msaied.com/projects) [ Articles ](https://www.msaied.com/articles) [ Certificates ](https://www.msaied.com/certificates) [ Contact ](https://www.msaied.com#contact-section) 

  [ home ](https://www.msaied.com)    [ articles ](https://www.msaied.com/articles)    Practical RAG in Laravel: pgvector, Embeddings, and Retrieval Pipelines        On this page       1. [  Why RAG Instead of Fine-Tuning? ](#why-rag-instead-of-fine-tuning)
2. [  Setting Up pgvector ](#setting-up-pgvector)
3. [  Generating and Storing Embeddings ](#generating-and-storing-embeddings)
4. [  Retrieval: Nearest-Neighbour Query ](#retrieval-nearest-neighbour-query)
5. [  Building the Prompt ](#building-the-prompt)
6. [  Caching Embeddings ](#caching-embeddings)
7. [  Key Takeaways ](#key-takeaways)

  ![Practical RAG in Laravel: pgvector, Embeddings, and Retrieval Pipelines](https://cdn.msaied.com/487/2db17c4f7927d4a229a4786c03e7b67b.png)

  #laravel   #ai   #pgvector   #postgresql   #rag  

 Practical RAG in Laravel: pgvector, Embeddings, and Retrieval Pipelines 
=========================================================================

     30 Jul 2026      4 min read    ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MJ.jpg)  Mohamed Said  

       Table of contents

1. [  01   Why RAG Instead of Fine-Tuning?  ](#why-rag-instead-of-fine-tuning)
2. [  02   Setting Up pgvector  ](#setting-up-pgvector)
3. [  03   Generating and Storing Embeddings  ](#generating-and-storing-embeddings)
4. [  04   Retrieval: Nearest-Neighbour Query  ](#retrieval-nearest-neighbour-query)
5. [  05   Building the Prompt  ](#building-the-prompt)
6. [  06   Caching Embeddings  ](#caching-embeddings)
7. [  07   Key Takeaways  ](#key-takeaways)

 Why RAG Instead of Fine-Tuning?
-------------------------------

Retrieval-augmented generation (RAG) lets you ground an LLM's answers in your own data without the cost and complexity of fine-tuning. The pattern is simple: embed your documents, store the vectors, retrieve the top-k nearest neighbours at query time, and inject them as context. Laravel's ecosystem — PostgreSQL, Eloquent, and the HTTP client — is more than enough to build this cleanly.

Setting Up pgvector
-------------------

Install the extension and add a migration:

```sql
CREATE EXTENSION IF NOT EXISTS vector;

```

```php
// database/migrations/2024_01_01_000000_add_embedding_to_documents.php
public function up(): void
{
    Schema::table('documents', function (Blueprint $table) {
        // 1536 dims for text-embedding-3-small
        $table->vector('embedding', 1536)->nullable();
    });

    DB::statement(
        'CREATE INDEX documents_embedding_hnsw_idx
         ON documents USING hnsw (embedding vector_cosine_ops)'
    );
}

```

Laravel's `Blueprint` doesn't know `vector` natively, so register a macro in a service provider:

```php
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

Blueprint::macro('vector', function (string $column, int $dimensions): \Illuminate\Database\Schema\ColumnDefinition {
    return $this->addColumn('vector', $column, compact('dimensions'));
});

// Register the type with Doctrine so migrations don't break
\Doctrine\DBAL\Types\Type::addType('vector', VectorType::class);

```

`VectorType` is a thin Doctrine type that serialises a PHP float array to the `[0.1,0.2,...]` string pgvector expects.

Generating and Storing Embeddings
---------------------------------

Wrap the OpenAI call in a dedicated action:

```php
final readonly class GenerateEmbedding
{
    public function __construct(private \Illuminate\Http\Client\Factory $http) {}

    /** @return float[] */
    public function handle(string $text): array
    {
        $response = $this->http
            ->withToken(config('services.openai.key'))
            ->post('https://api.openai.com/v1/embeddings', [
                'model' => 'text-embedding-3-small',
                'input' => $text,
            ])
            ->throw()
            ->json('data.0.embedding');

        return $response;
    }
}

```

Dispatch a job when a document is saved:

```php
final class EmbedDocument implements ShouldQueue
{
    use Dispatchable, Queueable;

    public function __construct(public readonly int $documentId) {}

    public function handle(GenerateEmbedding $action): void
    {
        $doc = Document::findOrFail($this->documentId);
        $vector = $action->handle($doc->body);

        // Store as pgvector literal
        DB::table('documents')
            ->where('id', $doc->id)
            ->update(['embedding' => '[' . implode(',', $vector) . ']']);
    }
}

```

Retrieval: Nearest-Neighbour Query
----------------------------------

A clean retrieval abstraction keeps the pgvector SQL out of your controllers:

```php
final readonly class DocumentRetriever
{
    public function __construct(
        private GenerateEmbedding $embedder,
        private int $topK = 5,
    ) {}

    /** @return \Illuminate\Support\Collection */
    public function retrieve(string $query): \Illuminate\Support\Collection
    {
        $vector = '[' . implode(',', $this->embedder->handle($query)) . ']';

        return Document::query()
            ->selectRaw('*, embedding  ? AS distance', [$vector])
            ->whereNotNull('embedding')
            ->orderBy('distance')
            ->limit($this->topK)
            ->get();
    }
}

```

The `` operator is pgvector's cosine distance. For inner-product similarity use ``.

Building the Prompt
-------------------

```php
final readonly class RagPipeline
{
    public function __construct(
        private DocumentRetriever $retriever,
        private \Illuminate\Http\Client\Factory $http,
    ) {}

    public function answer(string $question): string
    {
        $context = $this->retriever->retrieve($question)
            ->map(fn (Document $d) => "- {$d->title}: {$d->body}")
            ->implode("\n");

        return $this->http
            ->withToken(config('services.openai.key'))
            ->post('https://api.openai.com/v1/chat/completions', [
                'model' => 'gpt-4o-mini',
                'messages' => [
                    ['role' => 'system', 'content' => "Answer using only the context below.\n\n{$context}"],
                    ['role' => 'user',   'content' => $question],
                ],
            ])
            ->throw()
            ->json('choices.0.message.content');
    }
}

```

### Caching Embeddings

Embedding the same query repeatedly wastes tokens. Cache by hash:

```php
$cacheKey = 'embedding:' . hash('xxh128', $text);
$vector = Cache::remember($cacheKey, now()->addDay(), fn () => $action->handle($text));

```

Key Takeaways
-------------

- Register a `Blueprint::macro` for `vector` columns; pair it with a Doctrine type for migration compatibility.
- Use HNSW indexes (`vector_cosine_ops`) for sub-millisecond ANN at scale — IVFFlat requires a `VACUUM` before it becomes useful.
- Keep embedding generation in a queued job; retrieval at request time is fast enough for synchronous use.
- Wrap retrieval behind a `DocumentRetriever` class so you can swap pgvector for another store without touching controllers.
- Cache query embeddings by content hash to avoid redundant API calls on repeated questions.

 Found this useful?

          [  ](https://twitter.com/intent/tweet?url=https%3A%2F%2Fwww.msaied.com%2Farticles%2Fpractical-rag-in-laravel-pgvector-embeddings-and-retrieval-pipelines-2&text=Practical+RAG+in+Laravel%3A+pgvector%2C+Embeddings%2C+and+Retrieval+Pipelines) [  ](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fwww.msaied.com%2Farticles%2Fpractical-rag-in-laravel-pgvector-embeddings-and-retrieval-pipelines-2) 

 Frequently Asked Questions 
----------------------------

  3 questions  

     Q01  Do I need a dedicated vector database, or is pgvector enough?        pgvector with an HNSW index handles millions of vectors comfortably for most SaaS workloads. A dedicated store like Qdrant or Pinecone adds operational overhead that is rarely justified until you exceed tens of millions of vectors or need advanced filtering that pgvector cannot express efficiently. 

      Q02  How do I handle documents longer than the embedding model's token limit?        Chunk the document before embedding — typically 512–1024 tokens with a small overlap (e.g. 64 tokens) to preserve context across chunk boundaries. Store each chunk as a separate row with a foreign key back to the parent document, then deduplicate retrieved chunks by document at query time. 

      Q03  Can I test the retrieval pipeline without hitting the OpenAI API?        Yes. Bind a fake implementation of GenerateEmbedding in your test service provider that returns a deterministic float array. Insert pre-computed embeddings into the test database and assert that DocumentRetriever returns the expected rows — no HTTP calls required. 

  Continue reading

 More Articles 
---------------

 [ View all    ](https://www.msaied.com/articles) 

 [ ![PostgreSQL CTEs, Window Functions, and Lateral Joins in Laravel](https://cdn.msaied.com/506/7d5fcddaf6c26c87a749a20b8bdffbfa.png) laravel postgresql sql 

### PostgreSQL CTEs, Window Functions, and Lateral Joins in Laravel

Go beyond basic Eloquent queries. Learn how to leverage PostgreSQL CTEs, window functions, and LATERAL joins d...

  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MJ.jpg)  Mohamed Said 

 4 Aug 2026     1 min read  

  Read    

 ](https://www.msaied.com/articles/postgresql-ctes-window-functions-and-lateral-joins-in-laravel-4) [ ![PhpStorm 2026.2 Released: Laravel Tool Window, PHP 8.5 Pipe Operator, and AI Agents](https://cdn.msaied.com/509/27bfaf4aff7b913b8c6c3a37df9dc143.png) PhpStorm PHP Laravel 

### PhpStorm 2026.2 Released: Laravel Tool Window, PHP 8.5 Pipe Operator, and AI Agents

PhpStorm 2026.2 ships a dedicated Laravel tool window with Artisan, error logs, and Laravel Cloud tabs, plus P...

  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MJ.jpg)  Mohamed Said 

 3 Aug 2026     3 min read  

  Read    

 ](https://www.msaied.com/articles/phpstorm-20262-released-laravel-tool-window-php-85-pipe-operator-and-ai-agents-1) [ ![PhpStorm 2026.2 Released: Laravel Tool Window, PHP 8.5 Pipe Operator, and AI Agents](https://cdn.msaied.com/505/151a0bba66cc27064e090e69e55d7c92.png) PhpStorm JetBrains PHP 8.5 

### PhpStorm 2026.2 Released: Laravel Tool Window, PHP 8.5 Pipe Operator, and AI Agents

PhpStorm 2026.2 ships a dedicated Laravel tool window with Artisan, error logs, and Laravel Cloud tabs, plus P...

  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MJ.jpg)  Mohamed Said 

 3 Aug 2026     3 min read  

  Read    

 ](https://www.msaied.com/articles/phpstorm-20262-released-laravel-tool-window-php-85-pipe-operator-and-ai-agents) 

   [  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MH.png)   Mohamed Said Laravel Backend Engineer  ](https://www.msaied.com)Senior Backend Engineer specializing in Laravel, scalable SaaS platforms, APIs, and cloud infrastructure. I build secure, high-performance web applications that help businesses grow.

Explore

- [Home](https://www.msaied.com)
- [Projects](https://www.msaied.com/projects)
- [Articles](https://www.msaied.com/articles)
- [Certificates](https://www.msaied.com/certificates)
- [Contact](https://www.msaied.com#contact-section)

Connect

- [   hello@msaied.com ](mailto:hello@msaied.com)
- [   +20 109 461 9204 ](tel:+201094619204)

© 2026 Mohamed Said. All rights reserved.

 [  ](https://github.com/EG-Mohamed) [  ](https://www.linkedin.com/in/msaiedm/) [  ](https://wa.me/201094619204) [  ](mailto:hello@msaied.com) [  ](https://drive.google.com/file/u/0/d/1MF20IPRJyzfy32mhEutjL5EpSls0w2Q8/view)
