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

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

  #laravel   #ai   #pgvector   #embeddings   #rag  

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

     21 Aug 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   1. Enable pgvector in PostgreSQL  ](#1-enable-pgvector-in-postgresql)
3. [  03   2. Generating and Storing Embeddings  ](#2-generating-and-storing-embeddings)
4. [  04   3. Similarity Search with Eloquent  ](#3-similarity-search-with-eloquent)
5. [  05   4. Wiring the RAG Pipeline  ](#4-wiring-the-rag-pipeline)
6. [  06   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.

 Found this useful?

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

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

  3 questions  

     Q01  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. 

      Q02  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. 

      Q03  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. 

  Continue reading

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

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

 [ ![Agent Run Observability in Laravel AI SDK 0.11](https://cdn.msaied.com/576/2c65c83715560433872bec3ae0eb2bf6.png) Laravel AI AI SDK Observability 

### Agent Run Observability in Laravel AI SDK 0.11

Laravel AI SDK 0.11 ships a single correlation ID per agent run, lifecycle events with wall timings, hosted to...

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

 20 Aug 2026     4 min read  

  Read    

 ](https://msaied.com/articles/agent-run-observability-in-laravel-ai-sdk-011) [ ![Statamic Mailables Viewer: Preview Laravel Emails in the Control Panel](https://cdn.msaied.com/574/5ecb785e581163f6a143b14cec070996.png) Statamic Laravel Email 

### Statamic Mailables Viewer: Preview Laravel Emails in the Control Panel

Mailables Viewer is a free Statamic add-on by Jack McDade that auto-discovers Laravel mailables and renders li...

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

 20 Aug 2026     3 min read  

  Read    

 ](https://msaied.com/articles/statamic-mailables-viewer-preview-laravel-emails-in-the-control-panel) [ ![Advanced Authorization in Laravel: Gates, Policies, and Response-Based Access Control](https://cdn.msaied.com/571/e2c97418f4d543aac16e77c5dfd1055a.png) laravel authorization security 

### Advanced Authorization in Laravel: Gates, Policies, and Response-Based Access Control

Go beyond simple boolean gates. Learn how Laravel's response-based authorization lets you return rich denial r...

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

 20 Aug 2026     3 min read  

  Read    

 ](https://msaied.com/articles/advanced-authorization-in-laravel-gates-policies-and-response-based-access-control-4) 

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