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 in Laravel ](#setting-up-pgvector-in-laravel)
3. [  Embedding Service ](#embedding-service)
4. [  Ingestion Pipeline ](#ingestion-pipeline)
5. [  Retrieval Service ](#retrieval-service)
6. [  Prompt Assembly ](#prompt-assembly)
7. [  Key Takeaways ](#key-takeaways)

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

  #laravel   #ai   #pgvector   #postgresql   #embeddings  

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

     2 Jul 2026      3 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 in Laravel  ](#setting-up-pgvector-in-laravel)
3. [  03   Embedding Service  ](#embedding-service)
4. [  04   Ingestion Pipeline  ](#ingestion-pipeline)
5. [  05   Retrieval Service  ](#retrieval-service)
6. [  06   Prompt Assembly  ](#prompt-assembly)
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 straightforward: embed your documents, store the vectors, embed the user query at runtime, retrieve the closest chunks, and inject them into the prompt. PostgreSQL's `pgvector` extension makes this viable without a dedicated vector store.

Setting Up pgvector in Laravel
------------------------------

Enable the extension in a migration:

```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->string('embedding_model', 64)->default('text-embedding-3-small');
        // Store as text; cast to vector in queries
        $table->text('embedding');
        $table->timestamps();
    });

    // Create an IVFFlat index after bulk-loading data
    DB::statement(
        'CREATE INDEX document_chunks_embedding_idx
         ON document_chunks
         USING ivfflat (embedding::vector(1536) vector_cosine_ops)
         WITH (lists = 100)'
    );
}

```

> **Note:** IVFFlat requires data to exist before the index is useful. For smaller datasets (&lt; 100k rows) an exact scan without an index is often fast enough.

Embedding Service
-----------------

Wrap the OpenAI call behind an interface so you can swap providers or mock in tests:

```php
interface EmbeddingProvider
{
    /** @return float[] */
    public function embed(string $text): array;
}

final class OpenAiEmbeddingProvider implements EmbeddingProvider
{
    public function __construct(
        private readonly OpenAI\Client $client,
        private readonly string $model = 'text-embedding-3-small',
    ) {}

    public function embed(string $text): array
    {
        $response = $this->client->embeddings()->create([
            'model' => $this->model,
            'input' => $text,
        ]);

        return $response->embeddings[0]->embedding;
    }
}

```

Bind it in a service provider:

```php
$this->app->singleton(
    EmbeddingProvider::class,
    fn () => new OpenAiEmbeddingProvider(
        client: OpenAI::client(config('services.openai.key')),
    )
);

```

Ingestion Pipeline
------------------

Chunk documents and persist embeddings as a queued job:

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

    public function __construct(private readonly Document $document) {}

    public function handle(EmbeddingProvider $embedder): void
    {
        $chunks = TextSplitter::splitByTokens($this->document->body, maxTokens: 512);

        foreach ($chunks as $content) {
            $vector = $embedder->embed($content);
            $literal = '[' . implode(',', $vector) . ']';

            DB::table('document_chunks')->insert([
                'document_id' => $this->document->id,
                'content'     => $content,
                'embedding'   => $literal,
                'created_at'  => now(),
                'updated_at'  => now(),
            ]);
        }
    }
}

```

Retrieval Service
-----------------

At query time, embed the question and pull the top-k chunks by cosine similarity:

```php
final class ChunkRetriever
{
    public function __construct(private readonly EmbeddingProvider $embedder) {}

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

        return DB::select(
            "SELECT content,
                    1 - (embedding::vector(1536)  ?::vector(1536)) AS similarity
             FROM document_chunks
             ORDER BY embedding::vector(1536)  ?::vector(1536)
             LIMIT ?",
            [$literal, $literal, $topK]
        ) |> collect(...);
    }
}

```

The `` operator is cosine distance; subtracting from 1 gives similarity.

Prompt Assembly
---------------

```php
$chunks = $retriever->retrieve($userQuestion);
$context = $chunks->pluck('content')->implode("\n\n---\n\n");

$messages = [
    ['role' => 'system', 'content' => "Answer using only the context below.\n\n{$context}"],
    ['role' => 'user',   'content' => $userQuestion],
];

```

Keep the system prompt tight. Stuffing too many chunks degrades answer quality and burns tokens.

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

- **pgvector removes the need for a separate vector database** for most Laravel applications.
- **IVFFlat indexes** trade recall for speed; tune `lists` and `probes` based on your dataset size.
- **Interface-backed embedding providers** make unit testing and provider swaps trivial.
- **Chunk size matters**: 256–512 tokens per chunk balances retrieval precision and context coverage.
- **Cosine distance (``)** is the right operator for normalized OpenAI embeddings.
- Queue ingestion jobs; embedding API calls are slow and should never block a request cycle.

 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&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) 

 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. PostgreSQL with the pgvector extension handles millions of vectors efficiently, especially with an IVFFlat or HNSW index. A dedicated vector store only becomes necessary when you need multi-tenanted vector isolation at very large scale or features like hybrid BM25+vector search that pgvector does not yet support natively. 

      Q02  How do I handle embedding model changes without re-ingesting all documents?        Store the model name alongside each embedding (as shown in the migration). When you switch models, run a background job that re-embeds only the chunks whose `embedding_model` column does not match the current model. This lets you migrate incrementally without downtime. 

      Q03  What chunk size should I use for text splitting?        256–512 tokens is a practical starting point. Smaller chunks improve retrieval precision but increase the number of rows and API calls during ingestion. Larger chunks carry more context per result but can dilute relevance scores. Benchmark against your specific documents and query patterns. 

  Continue reading

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

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

 [ ![Laravel queue:work Now Prints Why the Worker Stopped](https://cdn.msaied.com/629/bed93940d17b932032d64cee2e32d333.png) Laravel Queue Laravel 13.30 

### Laravel queue:work Now Prints Why the Worker Stopped

Laravel 13.30 adds a stop-reason line to queue:work output. Workers now print why they exited—memory limit, re...

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

 3 Sep 2026     3 min read  

  Read    

 ](https://www.msaied.com/articles/laravel-queuework-now-prints-why-the-worker-stopped) [ ![The Laracon Archive: 626 Talks, 287 Speakers, and 14 Years of Laravel History](https://cdn.msaied.com/628/13d8d0cef5fb083813df134cc253bb1b.png) laracon laravel community 

### The Laracon Archive: 626 Talks, 287 Speakers, and 14 Years of Laravel History

The Laracon Archive is a community-built, searchable index of every recorded Laracon talk — 626 talks from 287...

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

 3 Sep 2026     3 min read  

  Read    

 ](https://www.msaied.com/articles/the-laracon-archive-626-talks-287-speakers-and-14-years-of-laravel-history) [ ![Semantic Memory or Just Markdown? How Laravel Boost Manages AI Agent Project Rules](https://cdn.msaied.com/627/b162933f96fd615f3165bfe167816018.png) Laravel Boost AI Agents Context Engineering 

### Semantic Memory or Just Markdown? How Laravel Boost Manages AI Agent Project Rules

Laravel Boost tried semantic search with embeddings and a vector index to give AI coding agents project memory...

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

 3 Sep 2026     4 min read  

  Read    

 ](https://www.msaied.com/articles/semantic-memory-or-just-markdown-how-laravel-boost-manages-ai-agent-project-rules) 

   [  ![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)
