Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Ai Qdrant Store Laravel Package

symfony/ai-qdrant-store

Qdrant vector store integration for Symfony AI Store, with links to official Qdrant documentation on collections, points, and unified query search, plus Symfony AI contributing and issue resources.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup

  1. Install the Package:

    composer require symfony/ai-qdrant-store
    
  2. Configure Qdrant Store in config/packages/ai.yaml (Symfony) or a Laravel config file if you're wiring it up manually:

    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:
                        timeout: 30
                        retries: 3
    
  3. First Use Case: Vector Storage, Search & Count Inject the store via dependency injection (Symfony autowiring, or bind StoreInterface to the Qdrant store in a Laravel service provider):

    use Symfony\AI\Store\StoreInterface;
    use Symfony\AI\Store\Document\VectorDocumentInterface;
    
    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);
        }
    
        // New in v0.14: cheap document counts (with optional filters)
        public function countPublished(): int {
            return $this->qdrantStore->count([
                'must' => [['key' => 'status', 'match' => ['value' => 'published']]],
            ]);
        }
    }
    
  4. Verify Collection Exists The store will auto-create the collection on first write. Check Qdrant's API:

    curl -X GET http://localhost:6333/collections/your_collection_name
    

Implementation Patterns

Core Workflows

1. Vector CRUD Operations

  • Upsert (Insert/Update) — accepts arrays or objects implementing the new VectorDocumentInterface:
    $this->qdrantStore->upsert([
        [
            'id' => 'doc_123',
            'vector' => [0.1, 0.2, ...],
            'payload' => ['title' => 'Example', 'tags' => ['ai', 'search']],
        ],
    ]);
    
  • Delete by ID:
    $this->qdrantStore->remove(['doc_123']);
    
  • Bulk Upsert: pass an array of raw arrays or of VectorDocumentInterface instances — both are accepted (v0.14 is additive, arrays still work).

2. Typed Documents with VectorDocumentInterface (new in v0.14)

Implement the interface on a domain object to keep embeddings and payloads typed rather than juggling arrays:

use Symfony\AI\Store\Document\VectorDocumentInterface;

final class Article implements VectorDocumentInterface {
    public function __construct(
        private string $id,
        private array $vector,
        private array $payload,
    ) {}

    public function getId(): string { return $this->id; }
    public function getVector(): array { return $this->vector; }
    public function getPayload(): array { return $this->payload; }
}

$this->qdrantStore->upsert([new Article('doc_123', $embedding, ['status' => 'published'])]);

This is the recommended path going forward for anything non-trivial — it gives you IDE completion, validation on construction, and a single place to evolve your document shape.

3. Counting Documents (new in v0.14)

count() is the cheapest way to page, dashboard, or guard against re-indexing. It accepts the same filter payload as search():

$total    = $this->qdrantStore->count();                    // whole collection
$archived = $this->qdrantStore->count([
    'must' => [['key' => 'status', 'match' => ['value' => 'archived']]],
]);

Typical uses: pagination totals, "do we need to re-embed this tenant?" checks, and cheap pre-flight validation before expensive upserts.

4. Semantic Search with Filtering

$results = $this->qdrantStore->search(
    [0.15, 0.25, ...],
    limit: 10,
    filter: [
        'must' => [
            ['key' => 'status', 'match' => ['value' => 'published']],
            ['key' => 'tags', 'match' => ['any' => ['ai']]],
        ],
    ]
);

Use Qdrant's filter syntax (must, should, must_not, range).

5. Batch Processing with Messenger

class EmbeddingProcessor {
    public function __construct(
        private StoreInterface $qdrantStore,
        private MessageBusInterface $bus
    ) {}

    public function processDocuments(array $documents): void {
        $this->bus->dispatch(new ProcessEmbeddingsMessage($documents));
    }
}

6. Hybrid AI Pipelines

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);
    }
}

Integration Tips

1. Configuration Management

  • Keep secrets in env vars (QDRANT_API_URL, QDRANT_API_KEY).
  • For per-tenant collections, instantiate the store directly:
    $store = new QdrantStore(
        new QdrantClient($url, $apiKey),
        'collection_' . $tenantId
    );
    
  • In Laravel, bind a default StoreInterface in a service provider and use contextual bindings (when()->needs()->give()) for tenant-scoped stores.

2. Error Handling

try {
    $this->qdrantStore->upsert($points);
} catch (\Symfony\AI\Exception\StoreException $e) {
    $this->logger->error('Qdrant upsert failed', ['error' => $e->getMessage()]);
    throw new \RuntimeException('Failed to store embeddings', 0, $e);
}

3. Performance Optimization

  • Batch Size: 100–1000 points per request is a good starting range.
  • Vector Dimensions: keep consistent across all operations.
  • Counting over searching: use count() (v0.14) instead of search() when you only need a number — it skips vector scoring entirely.
  • Caching: cache expensive aggregations:
    $key = 'search_results_' . md5(serialize($query));
    if (!$cache->has($key)) {
        $cache->set($key, $this->qdrantStore->search($query), 3600);
    }
    

4. Testing

  • Spin up Qdrant in CI:
    $process = new Process(['docker', 'run', '-d', '-p', '6333:6333', 'qdrant/qdrant']);
    $process->start();
    
  • Mock the store in unit tests — count() is now also part of StoreInterface (or the counting capability interface), so add ->method('count')->willReturn(42) to your mocks.

5. Schema Management

Initialize collections explicitly so the auto-created schema matches expectations:

class CollectionInitializer {
    public function __construct(private QdrantClient $client) {}

    public function initialize(string $collectionName, int $dimensions): void {
        $this->client->recreateCollection($collectionName, [
            'vectors' => ['size' => $dimensions, 'distance' => 'Cosine'],
            'payload_schema' => [
                'title'  => new \stdClass(),
                'tags'   => new \stdClass(),
                'status' => new \stdClass(),
            ],
        ]);
    }
}

Gotchas and Tips

Pitfalls

1. VectorDocumentInterface is Additive, Not Mandatory

  • Issue: v0.14 introduces the interface but raw arrays are still accepted. Teams often adopt it inconsistently, mixing typed and untyped documents in the same codebase.
  • Fix: Pick one style per module. Prefer VectorDocumentInterface for domain objects and raw arrays only for one-off scripts. If you need a shared getter shape, extend your own base class implementing the interface once.

2. count() Filter Semantics vs search()

  • Issue: count() accepts the same filter payload as search(), but it does not take limit/offset/score_threshold. Passing those keys silently does nothing (or throws depending on client version).
  • Fix: Keep filter definitions in one place (a constant or builder) shared between search() and count() so pagination totals and result pages stay consistent. Never eyeball a filter in one call and copy it to the other.

3. count() on a Fresh/Empty Store

  • Issue: count() may trigger the same auto-creation path your first write would — or return 0 for a not-yet-existing collection, depending on the client. Either way, don't use count() === 0 as your "safe to write" guard without understanding which behaviour you're getting.
  • Fix: Explicitly create collections in a bootstrapping step (migration/initializer) before any count() dashboards or guards run.

4. Collection Auto-Creation

  • Issue: Auto-created collections use default vector size/distances and won't match your schema.
  • Fix: Initialize collections explicitly (see Schema Management).

5. Vector Dimension Mismatch

  • Issue: Qdrant rejects operations when vector dimensions don't match the collection schema.
  • Fix: Validate before upsert:
    if (count($vector) !== $dimensions) {
        throw new \InvalidArgumentException("Vector must be {$dimensions} dimensions");
    }
    

6. Filter Syntax Errors

  • Issue: Qdrant's filter syntax is strict; bad keys silently return empty results (which looks identical to "no matches" for both search() and count()).
  • Fix: Test filters in the Qdrant playground first, and add a debug wrapper that logs the raw filter payload alongside the result count.

7. Rate Limiting

  • Issue: Cloud Qdrant throttles; self-hosted instances hit resource limits under heavy upsert bursts.
  • Fix: Use a scoped HTTP client with retry middleware; back off on 429s.

8. Mock Blind Spots After Upgrading

  • Issue: If you mock StoreInterface in tests, adding count() to the store (v0.14) can produce "unexpected method call" failures at runtime rather than compile time.
  • Fix: Re-run your test suite after upgrading, and add explicit count() expectations to any mock that receives the store.

Tips

  • Upgrade path from v0.13 is smooth — the v0.14 features (count(), VectorDocumentInterface) are additive. Adopt VectorDocumentInterface progressively, not in a big-bang refactor.
  • Use count() for pagination UIs — one cheap request per filtered page beats fetching full vectors just to compute a total.
  • Keep a shared filter builder for both search() and count() in your codebase so a filter change can't desync between the two.
  • Log the collection name with every error — most multi-tenant bugs are "wrote to the wrong collection", not an API failure.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
adachsoft/command-executor-lib
tagixo/document-builder
tagixo/page-builder
tagixo/mail-builder
tagixo/form-builder
primix/primix
tagixo/core
alesima/laravel-azure-service-bus
tomshaw/shopcart
likyly/laravel
darthsoup/shoppingcart
tdt/core
labyrinthe/connect-php
encore2/laravel-admin2
cboxdk/laravel-console-kit
shapecode/doctrine-dbal-datetimeutc
gilads-otiannoh254/inertia-protocol
dakiquang/laravel-oauth-refresh-lock
woda/laravel-worktrees
dalefranks/ramasite