# How the VectorDbContext Class Implements Qdrant Vector Database Operations

> Decipher the VectorDbContext class in NekoImageGallery. Discover how it abstracts Qdrant operations, manages collections, and executes hybrid search for seamless data integration. Learn more now.

- Repository: [EdgeNeko/nekoimagegallery](https://github.com/hv0905/nekoimagegallery)
- Tags: internals
- Published: 2026-03-03

---

**The `VectorDbContext` class in NekoImageGallery serves as a complete abstraction layer over Qdrant, handling client initialization, collection lifecycle, CRUD operations, and hybrid search with RRF fusion while translating between domain `MappedImage` models and Qdrant `PointStruct` objects.**

The `VectorDbContext` class is the core service in the [hv0905/nekoimagegallery](https://github.com/hv0905/nekoimagegallery) repository that manages all vector database interactions. Located in [`app/Services/vector_db_context.py`](https://github.com/hv0905/nekoimagegallery/blob/main/app/Services/vector_db_context.py), this implementation provides a robust async interface to Qdrant, supporting multiple deployment modes and complex multi-vector search strategies.

## Client Selection and Connection Management

The class initializes the Qdrant client based on the `config.qdrant.mode` setting, supporting three distinct deployment scenarios. In [`app/Services/vector_db_context.py`](https://github.com/hv0905/nekoimagegallery/blob/main/app/Services/vector_db_context.py) at lines 31-41, a match statement selects between server, local, or in-memory instances:

```python
match config.qdrant.mode:
    case QdrantMode.SERVER:
        self._client = QdrantClient(host=config.qdrant.host, port=config.qdrant.port)
    case QdrantMode.LOCAL:
        self._client = AsyncQdrantClient(path=config.qdrant.path)
    case QdrantMode.MEMORY:
        self._client = AsyncQdrantClient(":memory:")

```

The selected client is wrapped with retry decorators to ensure resilience against transient network failures. This design allows the gallery application to run against a remote Qdrant cluster, a local persistent instance, or an ephemeral in-memory database for testing.

## Collection Lifecycle and Schema Setup

On application startup, the `on_load()` method triggers `check_collection()` and `initialize_collection()` to ensure the target collection exists with the correct schema. The implementation creates a collection with two vector fields at lines 36-46:

- **`image_vector`**: 768-dimensional float vectors using cosine distance for visual similarity
- **`text_contain_vector`**: 768-dimensional float vectors using cosine distance for OCR text content

```python
await self._client.create_collection(
    collection_name=self._config.qdrant.coll,
    vectors_config={
        self.IMG_VECTOR: models.VectorParams(size=768, distance=models.Distance.COSINE),
        self.TEXT_VECTOR: models.VectorParams(size=768, distance=models.Distance.COSINE)
    }
)

```

This dual-vector architecture enables the application to perform searches based on either visual content or extracted text within the same Qdrant collection.

## CRUD Operations for Vector Data

The class provides thin, async wrappers around Qdrant client calls for all data manipulation tasks, handling automatic conversion between `MappedImage` domain models and Qdrant points.

### Retrieval Operations

The `retrieve_by_id` and `retrieve_by_ids` methods (lines 53-68) fetch points using `client.retrieve` and map the returned `ScoredPoint` objects to `MappedImage` instances:

```python
points = await self._client.retrieve(
    collection_name=self._config.qdrant.coll,
    ids=[id]
)
return self._get_mapped_image_from_point(points[0])

```

### Insertion and Upsert

The `insert_items` method (lines 75-84) converts a list of `MappedImage` objects into `models.PointStruct` instances and performs batch upserts:

```python
points = [
    models.PointStruct(
        id=item.id,
        payload=item.payload,
        vector={self.IMG_VECTOR: item.image_vector, self.TEXT_VECTOR: item.text_contain_vector}
    )
    for item in items
]
await self._client.upsert(collection_name=self._config.qdrant.coll, points=points)

```

### Deletion and Updates

- **Deletion**: `delete_items` (lines 85-92) uses `client.delete` with a `PointIdsList` filter to remove specific IDs
- **Payload Updates**: `update_payload` (lines 94-104) calls `client.set_payload` to modify metadata without changing vectors
- **Vector Updates**: `update_vectors` (lines 106-110) uses `client.update_vectors` to refresh embeddings while preserving IDs and payloads

## Unified Search Interface with Hybrid RRF Fusion

The `query_search` method provides the primary search interface, accepting a high-level `DbQuery` object and supporting both single-criteria and multi-criteria searches. When multiple search bases (vision and OCR) are provided, the implementation performs **hybrid RRF (Reciprocal Rank Fusion)** using Qdrant's prefetch and fusion capabilities.

The conversion logic at lines 103-127 transforms `DbQueryBasis` objects into Qdrant `NearestQuery` or `RecommendQuery` instances via `_convert_basis_to_qdrant_query`. For hybrid searches (lines 141-154), the method constructs `Prefetch` objects for each criterion and combines them with a `FusionQuery`:

```python
prefetches = [
    models.Prefetch(query=vision_query, using=self.IMG_VECTOR),
    models.Prefetch(query=text_query, using=self.TEXT_VECTOR)
]
results = await self._client.query_points(
    collection_name=self._config.qdrant.coll,
    prefetch=prefetches,
    query=models.FusionQuery(fusion=models.Fusion.RRF)
)

```

### Metadata Filtering

The `_get_filters_by_filter_param` method (lines 95-162) translates optional `FilterParams` into Qdrant `models.Filter` objects, supporting range queries, exact matches, text contains, and multi-value any-matches on image metadata fields.

## Data Mapping Between Domain Models and Qdrant Points

The class maintains clean separation between the application's domain logic and Qdrant's transport layer through dedicated conversion helpers at lines 49-69:

- **`_get_vector_from_img_data`**: Extracts specific vector fields from `MappedImage`
- **`_get_point_from_mapped_image`**: Serializes complete `MappedImage` instances to `PointStruct`
- **`_get_mapped_image_from_point`**: Deserializes `ScoredPoint` responses back to `MappedImage` with score attribution

Additional utility methods include `scroll_points` and `get_counts` for pagination and collection statistics, plus `vector_name_for_basis` (lines 85-93) which maps `SearchBasisEnum` values (vision, ocr) to the corresponding vector field names.

## Summary

- **Multi-mode client support**: The class automatically configures Qdrant clients for server, local file, or in-memory operation based on configuration
- **Dual-vector schema**: Creates and manages collections with separate 768-dimensional cosine-similarity vectors for images and text content
- **Complete CRUD coverage**: Provides async methods for retrieve, upsert, delete, payload update, and vector update operations
- **Hybrid search capability**: Implements RRF fusion for multi-modal searches combining vision and text embeddings with optional metadata filtering
- **Domain abstraction**: Handles all translation between `MappedImage` models and Qdrant `PointStruct` objects

## Frequently Asked Questions

### How does VectorDbContext handle Qdrant connection failures?

The implementation wraps the underlying Qdrant client with retry decorators that automatically retry transient failures. Additionally, the `match` statement at initialization (lines 31-41) allows the application to switch between server, local, and in-memory modes without code changes, providing fallback options for different deployment environments.

### What vector similarity metric does NekoImageGallery use?

The collection schema defined in `initialize_collection` uses **cosine distance** for both vector fields. The `image_vector` and `text_contain_vector` fields are configured with `models.Distance.COSINE` at lines 36-46, making this suitable for normalized embedding vectors from modern vision-language models.

### Can I search using both image content and OCR text simultaneously?

Yes. The `query_search` method accepts a `DbQuery` containing multiple criteria in the `criteria` dictionary. When both `SearchBasisEnum.vision` and `SearchBasisEnum.ocr` bases are provided, the class automatically constructs prefetch queries for each vector field and applies RRF fusion (lines 141-154) to combine results into a unified ranked list.

### How are image metadata and payloads managed separately from vectors?

The `MappedImage` class separates `payload` (metadata dictionary) from `image_vector` and `text_contain_vector` (numpy arrays). The `update_payload` method (lines 94-104) calls `client.set_payload` to modify metadata without touching vectors, while `update_vectors` (lines 106-110) updates embeddings via `client.update_vectors` without affecting payloads.