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.
"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
BrimEmbeddingStarted
Model, driver, text length, queued status
BrimEmbeddingCompleted
Duration, chunks, embedding time, storage time
BrimEmbeddingFailed
Exception, retry attempt, duration
BrimSearchCompleted
Embedding time, search time, hydration time, scores
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 DashboardCurrent 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 🐕