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 heavy AI framework.

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

ShareCopy linkCopied

 ![Practical RAG in Laravel: pgvector, Embeddings, and Retrieval Pipelines](https://cdn.msaied.com/575/21de38adc44ef949b9bdc13ad6f6166b.png) 

  On this page +1. [Why RAG Instead of Fine-Tuning?](#why-rag-instead-of-fine-tuning)
2. [1. Enable pgvector in PostgreSQL](#1-enable-pgvector-in-postgresql)
3. [2. Generating and Storing Embeddings](#2-generating-and-storing-embeddings)
4. [3. Similarity Search with Eloquent](#3-similarity-search-with-eloquent)
5. [4. Wiring the RAG Pipeline](#4-wiring-the-rag-pipeline)
6. [Key Takeaways](#key-takeaways)

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

Fine-tuning a model on your domain data is expensive, slow to iterate, and quickly goes stale. Retrieval-Augmented Generation (RAG) keeps your knowledge base in a vector store and fetches only the relevant chunks at query time — giving the LLM fresh, scoped context without retraining.

This article walks through a practical Laravel implementation: storing embeddings in PostgreSQL via `pgvector`, querying them with a raw Eloquent expression, and wiring everything into a clean service that your controllers and jobs can call.

---

1. Enable pgvector in PostgreSQL
--------------------------------

Install the extension once per database:

```sql
CREATE EXTENSION IF NOT EXISTS vector;

```

Then create a migration for your chunks table:

```php
Schema::create('document_chunks', function (Blueprint $table) {
    $table->id();
    $table->foreignId('document_id')->constrained()->cascadeOnDelete();
    $table->text('content');
    $table->string('model')->default('text-embedding-3-small');
    // 1536 dims for text-embedding-3-small
    $table->vector('embedding', 1536)->nullable();
    $table->timestamps();
});

```

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) {
    return $this->addColumn('vector', $column, compact('dimensions'));
});

```

And register the custom type with Doctrine in `AppServiceProvider`:

```php
DB::connection()->getDoctrineSchemaManager()
    ->getDatabasePlatform()
    ->registerDoctrineTypeMapping('vector', 'string');

```

---

2. Generating and Storing Embeddings
------------------------------------

Create an `EmbeddingService` that wraps the OpenAI HTTP call:

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

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

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

```

When a document is ingested, chunk it and dispatch a job:

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

    public function __construct(private readonly int $chunkId) {}

    public function handle(EmbeddingService $embeddings): void
    {
        $chunk = DocumentChunk::findOrFail($this->chunkId);
        $vector = $embeddings->embed($chunk->content);

        // pgvector expects a bracketed string: '[0.1,0.2,...]'
        $chunk->update(['embedding' => '[' . implode(',', $vector) . ']']);
    }
}

```

---

3. Similarity Search with Eloquent
----------------------------------

Cosine distance (``) is the right operator for normalized OpenAI embeddings:

```php
final class ChunkRepository
{
    public function nearest(array $queryVector, int $limit = 5): Collection
    {
        $literal = '[' . implode(',', $queryVector) . ']';

        return DocumentChunk::query()
            ->selectRaw('*, embedding  ? AS distance', [$literal])
            ->whereNotNull('embedding')
            ->orderByRaw('embedding  ?', [$literal])
            ->limit($limit)
            ->get();
    }
}

```

Add an IVFFlat index for datasets beyond ~100k rows:

```sql
CREATE INDEX ON document_chunks
    USING ivfflat (embedding vector_cosine_ops)
    WITH (lists = 100);

```

---

4. Wiring the RAG Pipeline
--------------------------

```php
final readonly class RagService
{
    public function __construct(
        private EmbeddingService $embeddings,
        private ChunkRepository $chunks,
        private \OpenAI\Client $client,
    ) {}

    public function answer(string $question): string
    {
        $vector = $this->embeddings->embed($question);
        $context = $this->chunks->nearest($vector)
            ->pluck('content')
            ->implode("\n\n---\n\n");

        $response = $this->client->chat()->create([
            'model' => 'gpt-4o-mini',
            'messages' => [
                ['role' => 'system', 'content' => "Answer using only the context below.\n\n{$context}"],
                ['role' => 'user',   'content' => $question],
            ],
        ]);

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

```

Bind it in a service provider and inject it wherever needed — controllers, Livewire components, or Filament actions.

---

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

- **pgvector** keeps your vector store inside Postgres — no extra infrastructure.
- Use a **Blueprint macro** to add the `vector` column type cleanly in migrations.
- **Cosine distance (``)** works best with OpenAI's normalized embeddings.
- Offload embedding generation to **queued jobs** to avoid blocking HTTP requests.
- An **IVFFlat index** is essential once your chunk count grows beyond tens of thousands.
- Keep the RAG logic in a dedicated `RagService` — controllers stay thin and the pipeline stays testable.

- [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)
- [embeddings](https://www.msaied.com/public/articles?search=embeddings)
- [rag](https://www.msaied.com/public/articles?search=rag)

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

  Which pgvector distance operator should I use with OpenAI embeddings?Use cosine distance (`&lt;=&gt;`) for OpenAI embeddings because they are L2-normalized. Inner product (`&lt;#&gt;`) is equivalent for normalized vectors but cosine is more explicit and widely supported in pgvector indexes.

   How do I test the RagService without hitting the OpenAI API?Bind a fake `EmbeddingService` in your test that returns a fixed float array, and mock the `OpenAI\\Client` chat call. Because both dependencies are injected via the service container, swapping them in Pest is straightforward with `$this-&gt;mock()` or a custom service provider.

   When should I switch from IVFFlat to HNSW indexing in pgvector?HNSW offers better recall and faster query times at the cost of higher build time and memory. Prefer HNSW when you need sub-millisecond p99 latency or when your dataset changes frequently, since IVFFlat requires a full index rebuild to re-cluster after large inserts.

   ![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 articleDebounced Queued Event Listeners in Laravel 13.26](https://www.msaied.com/public/articles/debounced-queued-event-listeners-in-laravel-1326) [Next articleDeftform: A Simple Laravel-Powered Form Builder That Stays Out of Your Way](https://www.msaied.com/public/articles/deftform-a-simple-laravel-powered-form-builder-that-stays-out-of-your-way)  

   On this page
-------------

1. [Why RAG Instead of Fine-Tuning?](#why-rag-instead-of-fine-tuning)
2. [1. Enable pgvector in PostgreSQL](#1-enable-pgvector-in-postgresql)
3. [2. Generating and Storing Embeddings](#2-generating-and-storing-embeddings)
4. [3. Similarity Search with Eloquent](#3-similarity-search-with-eloquent)
5. [4. Wiring the RAG Pipeline](#4-wiring-the-rag-pipeline)
6. [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/740/cce86edc21eddcbdd2f2454fadaf9c70.png)  · 3 min read### The Pipeline Pattern in Laravel: Custom Pipelines Beyond Middleware

5 Oct 2026 ](https://www.msaied.com/public/articles/the-pipeline-pattern-in-laravel-custom-pipelines-beyond-middleware-1) [ ![](https://cdn.msaied.com/739/2d6897fdcdcf090613f96f72a64b8a78.png)  · 4 min read### MySQL Full-Text Search in Laravel: Indexes, Relevance Scoring, and Boolean Mode

4 Oct 2026 ](https://www.msaied.com/public/articles/mysql-full-text-search-in-laravel-indexes-relevance-scoring-and-boolean-mode) [ ![](https://cdn.msaied.com/738/073696a3fefe18bec825beec5ac658f5.png)  · 4 min read### Laravel Queue Rate-Limited Middleware: Throttling Jobs Without Losing Work

4 Oct 2026 ](https://www.msaied.com/public/articles/laravel-queue-rate-limited-middleware-throttling-jobs-without-losing-work) 

  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)
