Technical Documentation

Brim Internals

A deep dive into architecture, implementation patterns, and advanced usage for Laravel engineers.

1 Architecture Overview

Brim follows a driver-based architecture similar to Laravel's filesystem and cache systems. This allows swapping embedding providers and vector stores without changing application code.

┌───────────────────────────────────────────────────────────────┐
│                       Your Laravel App                        │
├───────────────────────────────────────────────────────────────┤
│  Model + HasEmbeddings Trait                                  │
│  ┌─────────────────────────────────────────────────────────┐  │
│  │ - toEmbeddableText(): string   // Define what to embed  │  │
│  │ - semanticSearch($query)       // Query scope           │  │
│  │ - findSimilar($limit)          // Find related models   │  │
│  │ - generateEmbedding()          // Manual trigger        │  │
│  └─────────────────────────────────────────────────────────┘  │
├───────────────────────────────────────────────────────────────┤
│                        Brim Service                           │
│  ┌────────────────────────┐    ┌────────────────────────┐     │
│  │    EmbeddingManager    │    │   VectorStoreManager   │     │
│  │  ┌──────────────────┐  │    │  ┌──────────────────┐  │     │
│  │  │  OllamaDriver    │  │    │  │  PgVectorStore   │  │     │
│  │  │  OpenAIDriver    │  │    │  │  (extensible)    │  │     │
│  │  └──────────────────┘  │    │  └──────────────────┘  │     │
│  └────────────────────────┘    └────────────────────────┘     │
├───────────────────────────────────────────────────────────────┤
│  ┌────────────────────────┐    ┌────────────────────────────┐ │
│  │    Ollama (local)      │    │   PostgreSQL + pgvector    │ │
│  │    OpenAI (cloud)      │    │   - HNSW indexing          │ │
│  │                        │    │   - Cosine similarity      │ │
│  └────────────────────────┘    └────────────────────────────┘ │
└───────────────────────────────────────────────────────────────┘

Embedding Drivers

Convert text → 768-dimensional vectors. Ollama runs locally (no API keys), OpenAI offers higher quality with cloud latency trade-off.

Vector Store

pgvector extends PostgreSQL with vector operations. HNSW index enables sub-millisecond similarity searches on millions of vectors.

2 Understanding Embeddings

An embedding is a dense vector representation of text where semantically similar content maps to nearby points in high-dimensional space.

// Conceptual representation
"The cat sat on the mat"
    ↓ Embedding Model (nomic-embed-text)
[0.023, -0.891, 0.442, ..., 0.127]  // 768 dimensions

"A feline rested on the rug"[0.019, -0.887, 0.451, ..., 0.131]  // Similar vector (high cosine similarity)

"PostgreSQL query optimization"[-0.672, 0.234, -0.118, ..., 0.891]  // Different vector (low similarity)

Cosine Similarity

Brim uses cosine similarity to measure vector relatedness. The pgvector <=> operator computes cosine distance, which we convert to similarity: 1 - distance.

-- PostgreSQL query (what Brim generates)
SELECT model_id,
       1 - (embedding <=> '[0.1, 0.2, ...]'::vector) AS similarity
FROM brim_embeddings
WHERE model_type = 'App\Models\Article'
ORDER BY similarity DESC
LIMIT 10;

3 Installation & Configuration

Prerequisites

# PostgreSQL with pgvector extension
CREATE EXTENSION vector;

# Ollama (for local embeddings)
curl -fsSL https://ollama.com/install.sh | sh
ollama pull nomic-embed-text

Package Installation

composer require brimleylabs/brim

php artisan vendor:publish --provider="Brim\BrimServiceProvider"
php artisan migrate

Configuration (config/brim.php)

return [
    // Default embedding driver: 'ollama' or 'openai'
    'driver' => env('BRIM_DRIVER', 'ollama'),

    // Auto-generate embeddings on model save
    'auto_sync' => env('BRIM_AUTO_SYNC', true),

    // Queue embedding generation (recommended for production)
    'queue' => env('BRIM_QUEUE_EMBEDDINGS', false),

    'drivers' => [
        'ollama' => [
            'host' => env('BRIM_OLLAMA_HOST', 'http://localhost:11434'),
            'model' => env('BRIM_OLLAMA_MODEL', 'nomic-embed-text'),
        ],
        'openai' => [
            'api_key' => env('BRIM_OPENAI_KEY'),
            'model' => env('BRIM_OPENAI_MODEL', 'text-embedding-3-small'),
        ],
    ],

    'chunking' => [
        'enabled' => true,
        'max_tokens' => 8192,      // Model context window
        'chunk_size' => 1000,      // Tokens per chunk
        'chunk_overlap' => 200,   // Overlap between chunks
    ],
];

4 Basic Usage

1. Prepare Your Model

<?php

namespace App\Models;

use Brim\Contracts\Embeddable;
use Brim\Traits\HasEmbeddings;
use Illuminate\Database\Eloquent\Model;

class Article extends Model implements Embeddable
{
    use HasEmbeddings;

    /**
     * Define the text content for embedding generation.
     * This is the ONLY required method to implement.
     */
    public function toEmbeddableText(): string
    {
        return <<title}

        {$this->content}

        Tags: {$this->tags->pluck('name')->implode(', ')}
        TEXT;
    }

    /**
     * Optional: Namespace embeddings for multi-tenant apps
     */
    public function getEmbeddingNamespace(): ?string
    {
        return "tenant:{$this->tenant_id}";
    }
}

2. Automatic Embedding Generation

// With auto_sync enabled, embeddings generate on save
$article = Article::create([
    'title' => 'Understanding Vector Databases',
    'content' => 'Vector databases store high-dimensional...',
]);
// Embedding automatically generated!

// Check if embedding exists
$article->hasEmbedding();  // true

// Manual generation (if auto_sync disabled)
$article->generateEmbedding();

// Delete embedding
$article->deleteEmbedding();

3. Semantic Search

// Basic semantic search
$results = Article::semanticSearch('machine learning tutorials')->get();

// With limit
$results = Article::semanticSearch('database optimization')
    ->take(5)
    ->get();

// Access similarity scores
foreach ($results as $article) {
    echo "{$article->title}: {$article->similarity_score}";
}

// Combine with Eloquent scopes
$results = Article::semanticSearch('API design patterns')
    ->where('published', true)
    ->where('created_at', '>', now()->subMonth())
    ->with(['author', 'tags'])
    ->get();

4. Find Similar Models

// Find articles similar to a specific article
$article = Article::find(1);
$similar = $article->findSimilar(5);

foreach ($similar as $related) {
    echo "{$related->title} - {$related->similarity}% match";
}

// "Related Articles" widget
public function show(Article $article)
{
    return view('articles.show', [
        'article' => $article,
        'related' => $article->findSimilar(3),
    ]);
}

5 Advanced Patterns

Queued Embedding Generation

For production workloads, queue embedding generation to avoid blocking requests.

// config/brim.php
'queue' => true,

// The GenerateEmbedding job handles retries and batching
// Jobs are dispatched automatically on model save

// Manual queue dispatch
use Brim\Jobs\GenerateEmbedding;

GenerateEmbedding::dispatch($article)
    ->onQueue('embeddings')
    ->delay(now()->addSeconds(10));

Multi-Tenant Namespacing

Isolate embeddings per tenant without separate tables.

class Document extends Model implements Embeddable
{
    use HasEmbeddings;

    public function getEmbeddingNamespace(): ?string
    {
        return "org:{$this->organization_id}";
    }

    public function toEmbeddableText(): string
    {
        return $this->content;
    }
}

// Search within a specific namespace
$results = Document::inNamespace("org:{$orgId}")
    ->semanticSearch($query)
    ->get();

Hybrid Search (Semantic + Filters)

Combine vector similarity with traditional SQL filtering.

class ProductController
{
    public function search(Request $request)
    {
        $query = Product::semanticSearch($request->q);

        // Apply traditional filters
        if ($request->category) {
            $query->where('category_id', $request->category);
        }

        if ($request->min_price) {
            $query->where('price', '>=', $request->min_price);
        }

        if ($request->in_stock) {
            $query->where('stock', '>', 0);
        }

        return $query
            ->with(['category', 'images'])
            ->paginate(20);
    }
}

Batch Operations

use Brim\Facades\Brim;

// Batch generate embeddings for existing records
Article::whereDoesntHave('embeddings')
    ->chunk(100, function ($articles) {
        foreach ($articles as $article) {
            $article->generateEmbedding();
        }
    });

// Using the Artisan command
// php artisan brim:embed App\\Models\\Article --batch=50

// Prune orphaned embeddings (models deleted but embeddings remain)
Brim::store()->deleteOrphaned();

Custom Embedding Logic

class Recipe extends Model implements Embeddable
{
    use HasEmbeddings;

    /**
     * Rich embeddable content from multiple sources
     */
    public function toEmbeddableText(): string
    {
        $ingredients = $this->ingredients
            ->pluck('name')
            ->implode(', ');

        $techniques = $this->steps
            ->pluck('technique')
            ->unique()
            ->implode(', ');

        return <<title}
        Cuisine: {$this->cuisine->name}
        Difficulty: {$this->difficulty}
        Cooking Time: {$this->cook_time} minutes

        Description: {$this->description}

        Ingredients: {$ingredients}

        Techniques used: {$techniques}

        Dietary: {$this->dietary_tags}
        TEXT;
    }

    /**
     * Disable auto-sync for bulk imports
     */
    public bool $brimAutoSync = false;
}

6 Real-World Applications

E-commerce Product Search

// Customer searches: "comfortable shoes for standing all day"
// Finds: Orthopedic insoles, nursing shoes, memory foam sneakers

$products = Product::semanticSearch('comfortable shoes for standing all day')
    ->where('in_stock', true)
    ->take(12)
    ->get();

// "Customers also viewed" - find similar products
$similar = $product->findSimilar(4)
    ->where('id', '!=', $product->id);

Support Ticket Routing & Knowledge Base

class TicketController
{
    public function store(Request $request)
    {
        $ticket = Ticket::create($request->validated());

        // Find similar resolved tickets for suggested solutions
        $suggestions = Ticket::semanticSearch($ticket->description)
            ->where('status', 'resolved')
            ->with('resolution')
            ->take(5)
            ->get();

        // Auto-categorize based on similar tickets
        if ($topMatch = $suggestions->first()) {
            $ticket->category_id = $topMatch->category_id;
            $ticket->save();
        }

        return response()->json([
            'ticket' => $ticket,
            'suggestions' => $suggestions,
        ]);
    }
}

Content Recommendation Engine

class RecommendationService
{
    public function forUser(User $user): Collection
    {
        // Build user interest profile from reading history
        $recentReads = $user->readingHistory()
            ->with('article')
            ->latest()
            ->take(10)
            ->get();

        // Create composite query from user interests
        $interests = $recentReads
            ->pluck('article.title')
            ->implode('. ');

        // Find articles similar to user's interests
        return Article::semanticSearch($interests)
            ->whereNotIn('id', $recentReads->pluck('article_id'))
            ->where('published_at', '>', now()->subWeek())
            ->take(10)
            ->get();
    }
}

RAG (Retrieval-Augmented Generation)

// Use Brim as the retrieval layer for LLM context
class DocumentQA
{
    public function answer(string $question): string
    {
        // Retrieve relevant document chunks
        $context = DocumentChunk::semanticSearch($question)
            ->take(5)
            ->get()
            ->pluck('content')
            ->implode("\n\n");

        // Send to LLM with retrieved context
        return OpenAI::chat()->create([
            'model' => 'gpt-4',
            'messages' => [
                [
                    'role' => 'system',
                    'content' => "Answer based on this context:\n{$context}"
                ],
                ['role' => 'user', 'content' => $question],
            ],
        ])->choices[0]->message->content;
    }
}

Duplicate & Plagiarism Detection

class DuplicateDetector
{
    public function findDuplicates(Submission $submission): Collection
    {
        return $submission->findSimilar(10)
            ->filter(fn($s) => $s->similarity > 0.85)  // 85% threshold
            ->map(fn($s) => [
                'submission' => $s,
                'similarity' => round($s->similarity * 100, 1) . '%',
                'warning' => $s->similarity > 0.95 ? 'Likely duplicate' : 'Review needed',
            ]);
    }
}

7 Performance & Optimization

Benchmark Reference

Operation 1K records 100K records 1M records
Embedding generation (Ollama) ~50ms/record ~50ms/record ~50ms/record
Similarity search (HNSW) <5ms <10ms <50ms
Index memory (768 dims) ~3MB ~300MB ~3GB

Optimization Tips

// 1. Use queues for embedding generation in production
'queue' => true,

// 2. Tune HNSW index parameters for your dataset size
-- For datasets > 100K, adjust m and ef_construction
CREATE INDEX ON brim_embeddings
USING hnsw (embedding vector_cosine_ops)
WITH (m = 16, ef_construction = 64);

// 3. Use connection pooling (PgBouncer) for high concurrency

// 4. Consider dedicated database connection for embeddings
'vector_store' => [
    'connection' => 'pgsql_vectors',  // Separate connection
],

// 5. Cache frequent searches
$results = Cache::remember(
    "search:{$query}",
    3600,
    fn() => Article::semanticSearch($query)->take(10)->get()
);

8 API Reference

HasEmbeddings Trait Methods

Method Return Description
toEmbeddableText() string Required. Returns text to embed.
getEmbeddingNamespace() ?string Optional namespace for isolation.
generateEmbedding() void Generate embedding immediately.
deleteEmbedding() void Remove embedding from store.
hasEmbedding() bool Check if embedding exists.
findSimilar(int $limit) Collection Find similar models.
semanticSearch(string $query) Builder Query scope for semantic search.
embeddings() MorphMany Relationship to embedding records.

Brim Facade Methods

use Brim\Facades\Brim;

// Generate embedding for a model
Brim::generateFor($model);

// Queue embedding generation
Brim::queueFor($model);

// Delete embeddings
Brim::deleteFor($model);

// Direct search (without model scope)
Brim::search(Article::class, 'query', limit: 10, minSimilarity: 0.5);

// Get statistics
Brim::stats();  // ['total' => 1000, 'models' => 500, 'by_type' => [...]]

// Health check
Brim::healthCheck();  // ['status' => 'healthy', 'driver' => 'ollama', ...]

// Access driver directly
Brim::driver()->embed('text');  // Returns float[]
Brim::driver('openai')->embedBatch(['text1', 'text2']);

// Access store directly
Brim::store()->deleteOrphaned();

Artisan Commands

# Check system status
php artisan brim:status

# Generate embeddings for a model class
php artisan brim:embed App\\Models\\Article
php artisan brim:embed App\\Models\\Article --batch=100

# Remove orphaned embeddings
php artisan brim:prune
php artisan brim:prune --dry-run

# View telemetry statistics
php artisan brim:telemetry stats --period=24h
php artisan brim:telemetry recent --limit=20
php artisan brim:telemetry prune

# Publish config and migrations
php artisan brim:install

9 Telemetry & Observability

Brim includes a comprehensive telemetry system for monitoring pipeline performance, debugging issues, and understanding how semantic search behaves in production.

Events Dispatched

started
BrimEmbeddingStarted

Model, driver, text length, queued status

completed
BrimEmbeddingCompleted

Duration, chunks, embedding time, storage time

failed
BrimEmbeddingFailed

Exception, retry attempt, duration

completed
BrimSearchCompleted

Embedding time, search time, hydration time, scores

batch
BrimBatchCompleted

Processed count, failed count, throughput

Configuration

// config/brim.php
'telemetry' => [
    'enabled' => true,

    'store' => [
        'enabled' => true,
        'table' => 'brim_telemetry',
        'retention_days' => 30,
    ],

    'logging' => [
        'enabled' => false,
        'channel' => null,
    ],

    'debug' => false,
    'sample_rate' => 1.0,
],

Accessing Telemetry Data

// Get aggregated statistics
$stats = Brim::telemetryStats('24h');
// Returns: embeddings, searches, batches, failures with avg durations

// Access the telemetry collector directly
$collector = app(TelemetryCollector::class);

// Get recent events
$events = $collector->getRecent(50, 'embedding');

// Prune old entries
$deleted = $collector->prune(30); // days

// Listen to events in your app
Event::listen(BrimSearchCompleted::class, function ($event) {
    Log::info('Search took ' . $event->totalDuration . 'ms');
    Log::info('Embedding: ' . $event->embeddingTime . 'ms');
    Log::info('Vector search: ' . $event->searchTime . 'ms');
});

Live Dashboard

View real-time telemetry data, pipeline timing breakdowns, and event streams in the interactive dashboard.

Open Telemetry Dashboard

Current System Status

Driver Status

Driver
ollama
Status
Healthy
Model
nomic-embed-text
Host
http://localhost:11434

Vector Store

Total Embeddings
20
Unique Models
20
Index Type
HNSW

Brim v1.0.0 · Built for Laravel 10/11/12 · MIT License

Named after Brimley the dog 🐕