symfony/ai-surreal-db-store
SurrealDB vector store integration for Symfony AI Store. Use SurrealDB’s vector indexing and search (MTREE/HNSW) to store embeddings and perform similarity queries, leveraging SurrealQL vector functions for retrieval in Symfony AI applications.
Install Dependencies
composer require symfony/ai surreal/surrealdb symfony/ai-surreal-db-store
Ensure symfony/ai (v0.8+) is installed as the base package.
Configure SurrealDB Connection
Add to .env:
SURREAL_DSN="surreal://user:password@host:port/database?namespace=ns&scope=scope"
SURREAL_INDEX="vector_index_name" # Predefined MTREE/HNSW index
Register the Store in Laravel
Bind the store in a service provider (e.g., AppServiceProvider):
use Symfony\AI\Store\SurrealDbStore;
use Symfony\AI\Store\StoreInterface;
public function register()
{
$this->app->singleton(StoreInterface::class, function ($app) {
return new SurrealDbStore(
new \Surreal\SurrealClient($app['config']['surrealdb.dsn']),
$app['config']['surrealdb.index']
);
});
}
First Use Case: Store/Retrieve Embeddings
use Symfony\AI\Store\StoreInterface;
public function storeEmbedding(StoreInterface $store)
{
$embedding = [0.1, 0.2, ..., 0.768]; // Example 768D vector
$id = $store->add('doc_id', $embedding, ['metadata' => 'value']);
$results = $store->find('doc_id', $embedding, 5); // Top 5 similar
return $results->getIds();
}
DEFINE TABLE documents SCOPE public;
DEFINE INDEX vector_index ON TABLE documents FIELDS vector TYPE MTREE DIMENSIONS 768;
Embedding Management
add() with metadata (e.g., user ID, timestamp).
$store->add('user_123', $embedding, ['type' => 'article', 'published' => true]);
SurrealClient directly (not exposed by the store).add() (idempotent by ID).Similarity Search
$results = $store->find('user_123', $queryEmbedding, 3, [
'type' => 'article',
'published' => true,
]);
cosine; override via SurrealQL:
SELECT * FROM documents WHERE vector ANNIHILATE $query_vector TYPE L2;
Filtering and CRUD
filter parameter (maps to SurrealQL WHERE).
$store->find($id, $embedding, 5, ['author' => 'John Doe']);
$store->remove('doc_id'); // By ID
$store->removeMany(['author' => 'John Doe']); // By filter
class Document extends Model
{
public function getEmbeddingAttribute()
{
return $this->store->get($this->id)?->getEmbedding();
}
}
ModelCreated).Cache::remember("similar_{$queryId}", now()->addHours(1), fn() =>
$store->find($id, $embedding, 5)
);
SELECT * FROM documents
WHERE vector ANNIHILATE $query_vector
AND content CONTAINS 'laravel';
REDEFINE INDEX vector_index ON TABLE documents FIELDS vector TYPE MTREE DIMENSIONS 768;
class ExtendedSurrealDbStore extends SurrealDbStore
{
public function customQuery(string $query, array $params): array
{
return $this->client->query($query, $params)->toArray();
}
}
Index Mismatches
Dimension mismatch if embedding size ≠ index dimensions.DIMENSIONS in DEFINE INDEX matches your embeddings (e.g., 768 for OpenAI’s text-embedding-ada-002).INDEX_NOT_FOUND errors.SurrealQL Syntax Quirks
ANNIHILATE for cosine similarity, NEAREST for exact matches.-- Faster
SELECT * FROM documents WHERE vector ANNIHILATE $query_vector AND metadata = 'X';
-- Slower (filters table first)
SELECT * FROM documents WHERE metadata = 'X' AND vector ANNIHILATE $query_vector;
Connection Handling
default_socket_timeout:
ini_set('default_socket_timeout', 30);
use Symfony\Component\Process\Exception\ProcessFailedException;
try {
$results = $store->find($id, $embedding);
} catch (ProcessFailedException $e) {
if (str_contains($e->getMessage(), 'connection refused')) {
$this->reconnectSurrealDb();
retry();
}
}
Metadata Limitations
WHERE doesn’t support nested JSON queries (e.g., metadata.tags = 'ai'). Flatten metadata or use CONTAINS:
-- Works
WHERE metadata LIKE '%"tags": "ai"%'
-- Avoid (fails)
WHERE metadata.tags = 'ai'
Enable SurrealDB Logging
Add to config/surrealdb.php:
'logging' => true,
Check logs for failed queries (e.g., ERROR: INDEX_NOT_FOUND).
Query Inspection
Use SurrealClient::query() to log raw SurrealQL:
$query = $store->client->query('SELECT * FROM documents LIMIT 1');
\Log::debug('Raw Query:', [$query->getQuery(), $query->getVariables()]);
Performance Profiling
Compare query times with microtime():
$start = microtime(true);
$results = $store->find($id, $embedding);
\Log::info('Query Time:', [microtime(true) - $start]);
Custom Distance Metrics
Override the store’s find() to use L2 distance:
public function findWithL2(string $id, array $embedding, int $limit = 5, array $filter = []): Results
{
$query = "SELECT * FROM {$id} WHERE vector NEAREST $embedding TYPE L2 LIMIT {$limit}";
return new Results($this->client->query($query, $filter)->toArray());
}
Batch Operations Add bulk methods to the store:
public function addMany(array $data): array
{
$ids = [];
foreach ($data as $item) {
$ids[] = $this->add($item['id'], $item['embedding'], $item['metadata']);
}
return $ids;
}
Async Writes Use Laravel Queues
How can I help you explore Laravel packages today?