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) 

 [ ![DDD Value Objects and DTOs in Laravel Without the Bloat](https://cdn.msaied.com/450/41688537085b86f102fd8c219a35319f.png) laravel ddd php 

### DDD Value Objects and DTOs in Laravel Without the Bloat

Learn how to implement domain-driven value objects and data transfer objects in Laravel using PHP 8.3 readonly...

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

 21 Jul 2026     1 min read  

  Read    

 ](https://www.msaied.com/articles/ddd-value-objects-and-dtos-in-laravel-without-the-bloat) [ ![Laravel + Python FastAPI: Image OCR Demo](https://cdn.msaied.com/447/5f3d87146fbf19957973cbc88c6c0155.png) Laravel Python FastAPI 

### Laravel + Python FastAPI: Image OCR Demo

Learn how to call a Python image OCR script from Laravel using FastAPI as the bridge. This premium tutorial wa...

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

 20 Jul 2026     3 min read  

  Read    

 ](https://www.msaied.com/articles/laravel-python-fastapi-image-ocr-demo) [ ![Scaffold Laravel Packages with the `laravel package` Command in Installer v5.31.0](https://cdn.msaied.com/449/522e744d3c7bae1f214c179a83ae5b88.png) Laravel Installer Package Development CLI 

### Scaffold Laravel Packages with the `laravel package` Command in Installer v5.31.0

Laravel Installer v5.31.0 introduces a `laravel package` command to scaffold packages from the CLI, automated...

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

 20 Jul 2026     3 min read  

  Read    

 ](https://www.msaied.com/articles/scaffold-laravel-packages-with-the-laravel-package-command-in-installer-v5310) 

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