symfony/ai-supabase-store
Supabase vector store integration for Symfony AI Store using PostgreSQL pgvector. Connect your Symfony AI apps to Supabase vector columns and the match_documents RPC for similarity search, with links to Supabase docs and Symfony AI contribution/resources.
Installation
composer require symfony/ai-supabase-store
Requires symfony/ai (≥v0.8.0) and supabase/supabase-php (≥v2.0).
Database Setup
CREATE EXTENSION vector;
embedding):
CREATE TABLE documents (
id UUID PRIMARY KEY,
content TEXT,
embedding vector(1536), -- Adjust dimension to your embedding size
metadata JSONB
);
match_documents RPC (Supabase provides this by default in their AI templates).Laravel Configuration
Bind the store in config/ai.php:
'stores' => [
'supabase' => [
'type' => \Symfony\AI\SupabaseStore\Store::class,
'connection' => 'supabase',
'table' => 'documents',
'embedding_column' => 'embedding',
'id_column' => 'id',
],
],
Register the Supabase connection in config/database.php:
'connections' => [
'supabase' => [
'driver' => 'supabase',
'url' => env('SUPABASE_URL'),
'key' => env('SUPABASE_KEY'),
'table' => env('SUPABASE_TABLE', 'documents'),
],
],
First Use Case: Storing and Querying Embeddings
use Symfony\AI\Store\StoreInterface;
use Illuminate\Support\Facades\AI;
// Inject via Laravel's DI or resolve manually
$store = AI::store('supabase');
// Add an embedding
$store->add(
'doc_123',
[0.1, 0.2, 0.3, ...], // Your embedding array (must match vector dimension)
['source' => 'user_guide', 'category' => 'laravel']
);
// Query similar embeddings
$results = $store->query(
[0.5, 0.4, 0.6, ...], // Query embedding
limit: 5,
filter: ['category' => 'laravel'] // Metadata filter
);
Use the store to fetch context for LLM prompts:
$relevantDocs = $store->query(
$queryEmbedding,
limit: 3,
filter: ['type' => 'faq']
);
$context = implode("\n\n---\n\n", array_map(
fn($doc) => $doc['content'],
$relevantDocs
));
$prompt = "Answer the question using this context: {$context}";
For initial dataset loading, batch inserts:
$batch = [];
foreach ($rawDocuments as $doc) {
$batch[] = [
'id' => $doc['id'],
'embedding' => $doc['embedding'],
'metadata' => $doc['metadata'],
];
}
// Use Supabase's bulk insert or chunked adds
Combine vector search with metadata:
$results = $store->query(
$userQueryEmbedding,
filter: [
'OR' => [
['category' => 'tutorial'],
['tags' => 'advanced']
],
'published' => true
]
);
Create a dedicated service for AI-powered features:
namespace App\Services;
use Symfony\AI\Store\StoreInterface;
use Illuminate\Support\Facades\AI;
class SemanticSearchService {
public function __construct(
protected StoreInterface $store
) {}
public function search(string $query, array $filters = []): array {
$embedding = $this->generateEmbedding($query); // Use your embedding model
return $this->store->query($embedding, ['filter' => $filters]);
}
}
Sync embeddings on content changes:
use Illuminate\Support\Facades\AI;
// After updating a document
$store = AI::store('supabase');
$newEmbedding = $this->generateEmbedding($updatedContent);
$store->add($documentId, $newEmbedding, $documentMetadata);
Dependency Injection
Bind the store in AppServiceProvider:
public function register() {
$this->app->bind(\Symfony\AI\Store\StoreInterface::class, function ($app) {
return AI::store('supabase');
});
}
Artisan Commands Create a command to rebuild embeddings:
use Symfony\AI\Store\StoreInterface;
use Illuminate\Console\Command;
class RebuildEmbeddingsCommand extends Command {
protected $signature = 'ai:rebuild-embeddings';
protected $description = 'Regenerate and update all embeddings';
public function handle(StoreInterface $store) {
foreach ($this->getAllDocuments() as $doc) {
$embedding = $this->generateEmbedding($doc['content']);
$store->add($doc['id'], $embedding, $doc['metadata']);
}
}
}
Testing Use Laravel’s testing helpers:
public function testSemanticSearch() {
$store = AI::store('supabase');
$store->add('test_doc', [0.1, 0.2, 0.3], ['type' => 'test']);
$results = $store->query([0.15, 0.25, 0.35]);
$this->assertCount(1, $results);
}
Indexing Add a GIN index for metadata filtering:
CREATE INDEX idx_documents_metadata ON documents USING GIN (metadata jsonb_path_ops);
Batch Queries
For large datasets, use match_documents directly with Supabase’s client:
$client = new \Supabase\Client($_ENV['SUPABASE_URL'], $_ENV['SUPABASE_KEY']);
$results = $client->rpc('match_documents', [
'query_embedding' => $embedding,
'model' => 'cosine',
'filter' => 'category = \'laravel\'',
'limit' => 10,
]);
Caching Cache frequent queries (e.g., popular search terms):
$cacheKey = 'search:' . md5($query);
return Cache::remember($cacheKey, now()->addHours(1), function () use ($store, $query) {
return $store->query($this->generateEmbedding($query));
});
Vector Dimension Mismatch
if (count($embedding) !== 1536) {
throw new \InvalidArgumentException('Embedding must have 1536 dimensions');
}
Supabase RPC Limitations
match_documents RPC has a default limit (often 100). Exceeding this silently truncates results.limit parameter or call RPC directly with higher limits.Metadata Filtering Quirks
SELECT * FROM documents
WHERE metadata @> '{"category": "laravel"}';
Connection Timeouts
use Symfony\Component\Process\Exception\TimeoutException;
try {
$store->add(...);
} catch (TimeoutException $e) {
sleep(2);
retry();
}
Laravel Caching Conflicts
DB::disableQueryCache();
$results = $store->query(...);
DB::enableQueryCache();
How can I help you explore Laravel packages today?