symfony/ai-qdrant-store
Qdrant Store integrates the Qdrant vector database with Symfony AI Store, enabling you to manage collections and points and run unified vector search with filters. Provides a Symfony-friendly bridge to Qdrant for embedding-based retrieval use cases.
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_API_URL)%' # e.g., http://localhost:6333
api_key: '%env(QDRANT_API_KEY)%' # Optional if using auth
collection: 'your_collection_name'
options:
# Optional Qdrant client options
timeout: 30
retries: 3
First Use Case: Vector Storage & Search Inject the store via dependency injection and use it for basic operations:
use Symfony\AI\Store\StoreInterface;
class DocumentService {
public function __construct(
private StoreInterface $qdrantStore
) {}
public function storeDocument(array $embedding, string $documentId): void {
$this->qdrantStore->upsert([
[
'id' => $documentId,
'vector' => $embedding,
'payload' => ['content' => '...', 'metadata' => [...]],
],
]);
}
public function findSimilarDocuments(array $queryEmbedding, int $limit = 5): array {
return $this->qdrantStore->search($queryEmbedding, limit: $limit);
}
}
Verify Collection Exists The store will auto-create the collection on first use. Check Qdrant’s dashboard or API:
curl -X GET http://localhost:6333/collections/your_collection_name
$this->qdrantStore->upsert([
[
'id' => 'doc_123',
'vector' => [0.1, 0.2, ...], // Your embedding
'payload' => ['title' => 'Example', 'tags' => ['ai', 'search']],
],
]);
$this->qdrantStore->remove(['doc_123']);
$this->qdrantStore->upsert(array_map(fn($doc) => [
'id' => $doc['id'],
'vector' => $doc['embedding'],
'payload' => $doc['metadata'],
], $documents));
Combine vector similarity with metadata filtering:
$results = $this->qdrantStore->search(
[0.15, 0.25, ...], // Query embedding
limit: 10,
filter: [
'must' => [
['key' => 'status', 'match' => ['value' => 'published']],
['key' => 'tags', 'match' => ['any' => ['ai']]],
],
]
);
must, should, range).Leverage Symfony’s Messenger for async operations:
use Symfony\Component\Messenger\MessageBusInterface;
class EmbeddingProcessor {
public function __construct(
private StoreInterface $qdrantStore,
private MessageBusInterface $bus
) {}
public function processDocuments(array $documents): void {
$this->bus->dispatch(new ProcessEmbeddingsMessage($documents));
}
}
// In a worker:
$handler = new class implements MessageHandlerInterface {
public function __invoke(ProcessEmbeddingsMessage $message) {
$this->qdrantStore->upsert($message->getEmbeddings());
}
};
Combine with Symfony AI’s EmbeddingGenerator:
use Symfony\AI\EmbeddingGeneratorInterface;
class SearchService {
public function __construct(
private EmbeddingGeneratorInterface $embeddingGenerator,
private StoreInterface $qdrantStore
) {}
public function semanticSearch(string $query, int $limit = 5): array {
$embedding = $this->embeddingGenerator->generate($query);
return $this->qdrantStore->search($embedding, limit: $limit);
}
}
# .env
QDRANT_API_URL=http://localhost:6333
QDRANT_API_KEY=your_api_key
$store = new QdrantStore(
new QdrantClient($url, $apiKey),
'collection_' . $tenantId
);
Wrap operations in try-catch blocks:
try {
$this->qdrantStore->upsert($points);
} catch (\Symfony\AI\Exception\StoreException $e) {
// Log or retry
$this->logger->error('Qdrant upsert failed', ['error' => $e->getMessage()]);
throw new \RuntimeException('Failed to store embeddings', 0, $e);
}
use Symfony\Component\Cache\Adapter\RedisAdapter;
$cache = RedisAdapter::createConnection('redis://localhost');
$cachedResults = $cache->get('search_results_' . md5(serialize($query)));
if (!$cachedResults) {
$cachedResults = $this->qdrantStore->search($query);
$cache->set('search_results_' . md5(serialize($query)), $cachedResults, 3600);
}
use Symfony\Component\Process\Process;
$process = new Process(['docker', 'run', '-d', '-p', '6333:6333', 'qdrant/qdrant']);
$process->start();
$mockStore = $this->createMock(StoreInterface::class);
$mockStore->method('search')->willReturn([...]);
Define collection schema upfront (e.g., in a CollectionInitializer service):
use Qdrant\Client\QdrantClient;
class CollectionInitializer {
public function __construct(private QdrantClient $client) {}
public function initialize(string $collectionName): void {
$this->client->recreateCollection($collectionName, [
'vectors' => [
'size' => 768, // Dimension of your embeddings
'distance' => 'Cosine', // or 'Dot', 'Euclidean'
],
'payload_schema' => [
'title' => new \stdClass(), // String field
'tags' => new \stdClass(), // Array of strings
'status' => new \stdClass(), // String enum
],
]);
}
}
if (count($vector) !== 768) {
throw new \InvalidArgumentException('Vector must be 768 dimensions');
}
key names) will fail silently or return empty results.ScopingHttpClient with retries:
$httpClient = new ScopingHttpClient(
How can I help you explore Laravel packages today?