RAG in Laravel with pgvector and Embeddings | Mohamed Said       [Skip to content](#main)  [ ![](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MH.png) Mohamed SaidLaravel Backend Engineer ](https://www.msaied.com/public) - [Home](https://www.msaied.com/public)
- [Projects](https://www.msaied.com/public/projects)
- [Articles](https://www.msaied.com/public/articles)
- [Certificates](https://www.msaied.com/public/certificates)
- [About](https://www.msaied.com/public#about)

           [  Contact](https://www.msaied.com/public#contact) Menu 

Menu
----

Close 

 - [HomeStart here](https://www.msaied.com/public)
- [ProjectsCase studies](https://www.msaied.com/public/projects)
- [ArticlesEngineering notes](https://www.msaied.com/public/articles)
- [CertificatesCredentials](https://www.msaied.com/public/certificates)
- [AboutHow I work](https://www.msaied.com/public#about)
- [ContactGet in touch](https://www.msaied.com/public#contact)

  [Start a conversation](https://www.msaied.com/public#contact) [WhatsApp](https://wa.me/201094619204) [Email](mailto:hello@msaied.com) 

 1. [Home](https://www.msaied.com/public)
2. /
3. [Articles](https://www.msaied.com/public/articles)
4. /
5. Practical RAG in Laravel: pgvector, Embeddings, and Retrieval Pipelines

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

 Build a production-ready retrieval-augmented generation pipeline in Laravel using pgvector, OpenAI embeddings, and a clean retrieval service — without reaching for a dedicated vector database.

 ![](https://cdn.msaied.com/01M22N44A70A5MC2S599JP0MPH.webp) [Mohamed Said](https://www.msaied.com/public#person) Published 2 Jul 2026 · Updated 2 Jul 2026 · 3 min read

ShareCopy linkCopied

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

  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)

 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.

- [laravel](https://www.msaied.com/public/articles?search=laravel)
- [ai](https://www.msaied.com/public/articles?search=ai)
- [pgvector](https://www.msaied.com/public/articles?search=pgvector)
- [postgresql](https://www.msaied.com/public/articles?search=postgresql)
- [embeddings](https://www.msaied.com/public/articles?search=embeddings)

 Frequently asked questions 
---------------------------

  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.

   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.

   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.

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

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

[About](https://www.msaied.com/public#about) [GitHub ↗](https://github.com/EG-Mohamed) [LinkedIn ↗](https://www.linkedin.com/in/msaiedm/) [WhatsApp ↗](https://wa.me/201094619204) [Email Address ↗](mailto:hello@msaied.com) [My CV ↗](https://drive.google.com/file/u/0/d/1MF20IPRJyzfy32mhEutjL5EpSls0w2Q8/view)  

   [Previous articlephpcpd-next: A Modern Copy/Paste Detector CLI for PHP 8.5+](https://www.msaied.com/public/articles/phpcpd-next-a-modern-copypaste-detector-cli-for-php-85) [Next articlePHP 8.3+ Typed Enums, Backed Casts, and Readonly Properties in Modern Laravel](https://www.msaied.com/public/articles/php-83-typed-enums-backed-casts-and-readonly-properties-in-modern-laravel)  

   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)

 ###  Have a technical challenge?

 Tell me what you’re building. I reply within two working days.

[Start a conversation](https://www.msaied.com/public#contact) 

   Related articles
-----------------

 [ ![](https://cdn.msaied.com/743/8998fac3a41451ab3fe1588194e17a43.png) Filament · 3 min read### Securing Filament Plugins with Plumb: Automated Security Scoring for PHP Packages

5 Oct 2026 ](https://www.msaied.com/public/articles/securing-filament-plugins-with-plumb-automated-security-scoring-for-php-packages) [ ![](https://cdn.msaied.com/742/2d02018669cdeedccb5de2efb898f0ee.png) Filament · 3 min read### Filament v3.3.56 Released: File Hash Names and Livewire Upload Fix

5 Oct 2026 ](https://www.msaied.com/public/articles/filament-v3356-released-file-hash-names-and-livewire-upload-fix) [ ![](https://cdn.msaied.com/741/5b55c123ad08e4d34e1f4b99ad6a428b.png)  · 3 min read### Filament v4 Schema-Based Forms, Infolists, and the Unified Schema API

5 Oct 2026 ](https://www.msaied.com/public/articles/filament-v4-schema-based-forms-infolists-and-the-unified-schema-api-5) 

  Have a technical challenge?
----------------------------

Tell me what you’re building. I reply within two working days.

 [Discuss your project ↗](https://www.msaied.com/public#contact) 

  © 2026 Mohamed Said · Built with Laravel, meant to last.Senior Backend Engineer specializing in Laravel, scalable SaaS platforms, APIs, and cloud infrastructure. I build secure, high-performance web applications that help businesses grow.

 - [Home](https://www.msaied.com/public)
- [Articles](https://www.msaied.com/public/articles)
- [Certificates](https://www.msaied.com/public/certificates)
- [GitHub](https://github.com/EG-Mohamed)
- [LinkedIn](https://www.linkedin.com/in/msaiedm/)
- [WhatsApp](https://wa.me/201094619204)
- [Email Address](mailto:hello@msaied.com)
- [My CV](https://drive.google.com/file/u/0/d/1MF20IPRJyzfy32mhEutjL5EpSls0w2Q8/view)
- [Sitemap](https://www.msaied.com/public/sitemap.xml)
