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

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

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

  [ home ](https://msaied.com)    [ articles ](https://msaied.com/articles)    Practical RAG in Laravel: pgvector, Embeddings, and Retrieval-Augmented Generation        On this page       1. [  Why RAG Instead of Fine-Tuning? ](#why-rag-instead-of-fine-tuning)
2. [  Enabling pgvector in a Laravel Migration ](#enabling-pgvector-in-a-laravel-migration)
3. [  Generating and Storing Embeddings ](#generating-and-storing-embeddings)
4. [  Retrieval: Nearest-Neighbour Search ](#retrieval-nearest-neighbour-search)
5. [  Assembling the Prompt ](#assembling-the-prompt)
6. [  Chunking Strategy Matters ](#chunking-strategy-matters)
7. [  Production Considerations ](#production-considerations)
8. [  Takeaways ](#takeaways)

  ![Practical RAG in Laravel: pgvector, Embeddings, and Retrieval-Augmented Generation](https://cdn.msaied.com/681/08058424f0e8433b83d9008c6b701cd8.png)

  #laravel   #ai   #pgvector   #postgresql   #rag  

 Practical RAG in Laravel: pgvector, Embeddings, and Retrieval-Augmented Generation 
====================================================================================

     19 Sep 2026      4 min read    ![Mohamed Said](https://cdn.msaied.com/01M22N44A70A5MC2S599JP0MPH.webp)  Mohamed Said  

       Table of contents

1. [  01   Why RAG Instead of Fine-Tuning?  ](#why-rag-instead-of-fine-tuning)
2. [  02   Enabling pgvector in a Laravel Migration  ](#enabling-pgvector-in-a-laravel-migration)
3. [  03   Generating and Storing Embeddings  ](#generating-and-storing-embeddings)
4. [  04   Retrieval: Nearest-Neighbour Search  ](#retrieval-nearest-neighbour-search)
5. [  05   Assembling the Prompt  ](#assembling-the-prompt)
6. [  06   Chunking Strategy Matters  ](#chunking-strategy-matters)
7. [  07   Production Considerations  ](#production-considerations)
8. [  08   Takeaways  ](#takeaways)

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

Fine-tuning is expensive and goes stale. Retrieval-augmented generation (RAG) keeps your knowledge base in a database you already control, lets you update it without retraining, and gives you full auditability over what context the model sees. If you are already running PostgreSQL, the `pgvector` extension turns it into a capable vector store — no extra infrastructure required.

Enabling pgvector in a Laravel Migration
----------------------------------------

```php
// database/migrations/2024_01_01_000000_create_document_chunks_table.php
public function up(): void
{
    DB::statement('CREATE EXTENSION IF NOT EXISTS vector');

    Schema::create('document_chunks', function (Blueprint $table) {
        $table->id();
        $table->foreignId('document_id')->constrained()->cascadeOnDelete();
        $table->text('content');
        $table->integer('token_count');
        // 1536 dimensions for text-embedding-3-small
        $table->string('embedding', 8192)->nullable(); // stored as text, cast in queries
        $table->timestamps();
    });

    DB::statement(
        'ALTER TABLE document_chunks ADD COLUMN embedding_vec vector(1536)'
    );

    // IVFFlat index — tune lists to ~sqrt(row_count)
    DB::statement(
        'CREATE INDEX ON document_chunks USING ivfflat (embedding_vec vector_cosine_ops) WITH (lists = 100)'
    );
}

```

Using a raw `ALTER TABLE` keeps the migration readable while sidestepping Blueprint's lack of native vector type support.

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

Wrap the OpenAI call in a dedicated action so it is easy to swap providers later.

```php
namespace App\AI;

use App\Models\DocumentChunk;
use OpenAI\Laravel\Facades\OpenAI;

final class EmbedChunk
{
    public function handle(DocumentChunk $chunk): void
    {
        $response = OpenAI::embeddings()->create([
            'model' => 'text-embedding-3-small',
            'input' => $chunk->content,
        ]);

        $vector = $response->embeddings[0]->embedding; // float[]

        // pgvector expects '[0.1,0.2,...]' literal syntax
        $literal = '[' . implode(',', $vector) . ']';

        $chunk->updateQuietly(['embedding_vec' => DB::raw("'$literal'::vector")]);
    }
}

```

Dispatch this inside a queued job after chunking a document so the HTTP cycle stays fast.

Retrieval: Nearest-Neighbour Search
-----------------------------------

```php
namespace App\AI;

use App\Models\DocumentChunk;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;

final class RetrieveChunks
{
    public function __construct(private EmbedQuery $embedQuery) {}

    /** @return Collection */
    public function topK(string $query, int $k = 5): Collection
    {
        $vector = $this->embedQuery->forText($query);
        $literal = '[' . implode(',', $vector) . ']';

        return DocumentChunk::query()
            ->select(['id', 'document_id', 'content', 'token_count'])
            ->selectRaw(
                'embedding_vec  ?::vector AS distance',
                [$literal]
            )
            ->orderBy('distance')
            ->limit($k)
            ->get();
    }
}

```

The `` operator is cosine distance. Use `` for inner product or `` for L2 depending on how your embeddings were normalised.

Assembling the Prompt
---------------------

```php
final class RagPipeline
{
    public function __construct(
        private RetrieveChunks $retriever,
    ) {}

    public function answer(string $question): string
    {
        $chunks = $this->retriever->topK($question, k: 5);

        $context = $chunks
            ->map(fn ($c) => "---\n" . $c->content)
            ->implode("\n");

        $response = OpenAI::chat()->create([
            'model' => 'gpt-4o-mini',
            'messages' => [
                ['role' => 'system', 'content' =>
                    'Answer using only the context below. If unsure, say so.'],
                ['role' => 'user', 'content' =>
                    "Context:\n$context\n\nQuestion: $question"],
            ],
            'max_tokens' => 512,
        ]);

        return $response->choices[0]->message->content;
    }
}

```

Chunking Strategy Matters
-------------------------

A naive split on paragraph breaks produces uneven chunks. Prefer a sliding-window approach: 512-token chunks with a 64-token overlap so context is not lost at boundaries. Count tokens with `tiktoken-php` before persisting.

```php
$chunks = collect($splitter->split($text, chunkSize: 512, overlap: 64));

```

Production Considerations
-------------------------

- **Index warm-up**: IVFFlat requires `SET ivfflat.probes = 10` at query time for better recall at the cost of speed. Tune per workload.
- **Embedding cache**: Hash the input text and cache the vector in Redis to avoid redundant API calls during re-indexing.
- **Tenant isolation**: Add a `tenant_id` column and include it in the `WHERE` clause before the vector search; pgvector will filter first if the planner cooperates.
- **Batch embedding**: Use the embeddings endpoint's array input to embed up to 2048 strings per request and cut API round-trips dramatically.

Takeaways
---------

- pgvector + PostgreSQL is a viable production vector store for most Laravel apps — no separate service needed.
- Keep embedding generation in queued jobs; keep retrieval in a dedicated class injectable via the service container.
- The `` cosine operator, IVFFlat index, and `probes` setting are the three knobs that control recall vs. latency.
- Chunking quality directly determines answer quality — invest time in your splitter before tuning the model.
- Cache embeddings aggressively; the embedding API is the main cost driver in a RAG pipeline.

 Found this useful?

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

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

  3 questions  

     Q01  Do I need a dedicated vector database like Pinecone or Weaviate for RAG in Laravel?        Not for most applications. If you are already running PostgreSQL, the pgvector extension provides cosine, L2, and inner-product similarity search with IVFFlat or HNSW indexes. A dedicated vector database makes sense only when you have hundreds of millions of vectors or need features like multi-tenancy at the vector-store level. 

      Q02  How do I keep embeddings up to date when document content changes?        Listen for the Eloquent `updated` event on your Document model, delete the old chunks, re-chunk the new content, and dispatch fresh embedding jobs. Wrapping this in a database transaction ensures you never serve stale vectors mid-update. 

      Q03  What is the difference between IVFFlat and HNSW indexes in pgvector?        IVFFlat partitions vectors into lists and searches a subset of them; it is faster to build but requires a `probes` setting to balance recall vs. speed. HNSW builds a hierarchical graph and generally delivers better recall at query time with no probe tuning, but takes longer to build and uses more memory. For most Laravel apps starting out, IVFFlat with a sensible `lists` value is the pragmatic choice. 

  Continue reading

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

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

 [ ![Gate Responses, Policy Before-Hooks, and Ownership Guards in Laravel](https://cdn.msaied.com/680/65326929bb7b3e15cee4d9753000eddc.png) laravel authorization security 

### Gate Responses, Policy Before-Hooks, and Ownership Guards in Laravel

Beyond simple true/false gates: learn how to return rich Gate responses, intercept policies with before-hooks,...

  ![Mohamed Said](https://cdn.msaied.com/01M22N44A70A5MC2S599JP0MPH.webp)  Mohamed Said 

 19 Sep 2026     3 min read  

  Read    

 ](https://msaied.com/articles/gate-responses-policy-before-hooks-and-ownership-guards-in-laravel) [ ![Fresh: A Laravel Package Skeleton with Testbench, CI, and Boost Integration](https://cdn.msaied.com/679/6e34855eba64d6cc443c5cb2e7d17555.png) Laravel Package Development Orchestra Testbench 

### Fresh: A Laravel Package Skeleton with Testbench, CI, and Boost Integration

Fresh is an opinionated Laravel package skeleton by Mazen Touati that ships with PHPUnit, Larastan, Rector, Pi...

  ![Mohamed Said](https://cdn.msaied.com/01M22N44A70A5MC2S599JP0MPH.webp)  Mohamed Said 

 18 Sep 2026     3 min read  

  Read    

 ](https://msaied.com/articles/fresh-a-laravel-package-skeleton-with-testbench-ci-and-boost-integration) [ ![Inertia DevTools Now Available for Firefox](https://cdn.msaied.com/674/445325ab535802b1b68d3adc3ada5cd0.png) Inertia.js DevTools Firefox 

### Inertia DevTools Now Available for Firefox

Inertia DevTools has landed on Firefox with full feature parity to the Chrome extension. Firefox users can now...

  ![Mohamed Said](https://cdn.msaied.com/01M22N44A70A5MC2S599JP0MPH.webp)  Mohamed Said 

 17 Sep 2026     3 min read  

  Read    

 ](https://msaied.com/articles/inertia-devtools-now-available-for-firefox) 

   [  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MH.png)   Mohamed Said Laravel Backend Engineer  ](https://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://msaied.com)
- [Projects](https://msaied.com/projects)
- [Articles](https://msaied.com/articles)
- [Certificates](https://msaied.com/certificates)
- [Contact](https://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)
