symfony/ai-qdrant-store
Symfony AI Store integration for Qdrant vector database. Manage collections and points, run unified similarity search with filters, and connect Symfony AI apps to Qdrant for storing and querying embeddings.
Install the Package:
composer require symfony/ai-qdrant-store
Configure Qdrant Store in config/packages/ai.yaml:
framework:
ai:
stores:
qdrant:
type: qdrant
url: '%env(QDRANT_URL)%' # e.g., http://localhost:6333
api_key: '%env(QDRANT_API_KEY)%' # Optional if using auth
collection: 'my_embeddings' # Default collection name
options:
# Optional Qdrant client options (e.g., timeout, retries)
timeout: 30
First Use Case: Upsert a Vector Inject the store into a service and use it to add a vector:
use Symfony\AI\Store\StoreInterface;
class MyService {
public function __construct(private StoreInterface $qdrantStore) {}
public function addEmbedding(array $embedding, array $payload): void {
$this->qdrantStore->upsert(
$embedding['vector'],
$payload,
$embedding['id'] ?? null
);
}
}
Query Vectors with Filtering:
$results = $this->qdrantStore->query(
$queryVector,
limit: 5,
filter: ['must': [['key' => 'status', 'match' => ['value' => 'published']]]]
);
Collection Management:
$this->qdrantStore->createCollection('new_collection', [
'vectors' => ['size' => 768, 'distance' => 'Cosine'],
'payload_schema' => ['text' => 'string', 'timestamp' => 'integer'],
]);
Batch Operations:
upsertMany() for bulk inserts:
$this->qdrantStore->upsertMany([
['vector' => $vector1, 'payload' => $payload1, 'id' => 'id1'],
['vector' => $vector2, 'payload' => $payload2, 'id' => 'id2'],
]);
Hybrid Search:
$results = $this->qdrantStore->query(
$queryVector,
filter: ['must': [
['key' => 'category', 'match' => ['value' => 'tech']],
['key' => 'rating', 'range' => ['gt' => 3]],
]]
);
Dependency Injection:
services:
App\Service\VectorSearchService:
tags: ['ai.store.qdrant']
Custom HTTP Clients:
ScopingHttpClient for observability or retries:
use Symfony\Contracts\HttpClient\ScopedHttpClientInterface;
$client = new ScopingHttpClient(
$baseClient,
['debug' => true, 'timeout' => 10]
);
$store = new QdrantStore($client, 'http://qdrant:6333', 'api_key');
Schema Validation:
use Symfony\Component\Validator\Validator\ValidatorInterface;
$errors = $validator->validate($payload);
if (count($errors) > 0) {
throw new \RuntimeException('Invalid payload');
}
Async Processing:
$message = new VectorUpsertMessage($vector, $payload);
$this->messageBus->dispatch($message);
Collection Schema Mismatches:
createCollection() with explicit settings.Filter Syntax Errors:
must/should clauses) throws cryptic errors.Rate Limiting:
RetryStrategy:
$client->withOptions([
'retry_on_status' => [429, 500, 502, 503, 504],
'timeout' => 30,
]);
ID Collisions:
upsert() may overwrite data unexpectedly.$id = Uuid::v7();
$this->qdrantStore->upsert($vector, $payload, $id);
Enable HTTP Debugging:
# config/packages/dev/http_client.yaml
framework:
http_client:
scoped_clients:
qdrant:
options:
debug: true
Check logs for raw Qdrant API requests/responses.
Query Logging:
$this->logger->debug('Qdrant Query', [
'vector' => $queryVector,
'filter' => $filter,
'limit' => $limit,
]);
Collection Inspection: Use Qdrant’s API to verify collections:
curl -X GET "http://localhost:6333/collections/my_collection"
Custom Query Builders:
QdrantStore to add domain-specific query methods:
class CustomQdrantStore extends QdrantStore {
public function searchByCategory(string $category, int $limit): array {
return $this->query(
$this->getQueryVector(),
filter: ['must' => [['key' => 'category', 'match' => ['value' => $category]]]],
limit: $limit
);
}
}
Event Listeners:
upsert()/remove() to trigger side effects (e.g., analytics):
$store->addListener('upsert', function ($vector, $payload, $id) {
$this->analytics->track('vector_upsert', ['id' => $id]);
});
Fallback Strategies:
try {
return $this->qdrantStore->query(...);
} catch (StoreException $e) {
return $this->fallbackStore->query(...);
}
API Key Handling:
%env(QDRANT_API_KEY)% or a secrets manager.Collection Defaults:
collection is omitted, the store uses a default named default. Override in config:
framework:
ai:
stores:
qdrant:
collection: 'custom_collection'
Vector Distance Metrics:
distance (e.g., Cosine, Dot) matches your use case. Defaults to Cosine:
framework:
ai:
stores:
qdrant:
options:
vectors:
distance: Dot
How can I help you explore Laravel packages today?