# Semantic Search with Qdrant Vector Database Integration in Code-Graph-RAG

> Integrate Qdrant for semantic search in Code-Graph-RAG. Leverage vector embeddings, project filtering, and robust retry logic for efficient code retrieval.

- Repository: [Vitali Avagyan/code-graph-rag](https://github.com/vitali87/code-graph-rag)
- Tags: how-to-guide
- Published: 2026-08-15

---

**Code-Graph-RAG implements pluggable semantic search by storing code embeddings in Qdrant, with configurable retry logic, project-scoped filtering, and graceful lifecycle management.**

The `vitali87/code-graph-rag` repository provides a retrieval-augmented generation (RAG) system for codebases that leverages **semantic search with Qdrant vector database integration** as its default vector storage backend. This architecture enables fast similarity search over code embeddings while remaining agnostic to the underlying vector store through a clean abstraction layer.

---

## How Qdrant Backend Selection Works

The vector store system uses runtime configuration to determine which backend to instantiate. In [`codebase_rag/vector_store.py`](https://github.com/vitali87/code-graph-rag/blob/main/codebase_rag/vector_store.py), the `_selected_backend()` function inspects `settings.VECTOR_STORE_BACKEND` and returns either `QdrantVectorStore` or `MilvusVectorStore` via `_get_vector_store()`.

The optional dependency on `qdrant-client` is guarded by `has_qdrant_client()` in [`codebase_rag/utils/dependencies.py`](https://github.com/vitali87/code-graph-rag/blob/main/codebase_rag/utils/dependencies.py). This allows the package to import and inspect the vector store module even when Qdrant support is not installed.

Key configuration parameters from [`codebase_rag/config.py`](https://github.com/vitali87/code-graph-rag/blob/main/codebase_rag/config.py) include:

- `settings.QDRANT_URL` — Remote Qdrant server endpoint
- `settings.QDRANT_DB_PATH` — Local persistent storage directory
- `settings.QDRANT_COLLECTION_NAME` — Target collection identifier
- `settings.QDRANT_VECTOR_DIM` — Embedding dimension (must match your encoder)
- `settings.QDRANT_UPSERT_RETRIES` — Maximum retry attempts for upserts
- `settings.QDRANT_RETRY_BASE_DELAY` — Base delay for exponential back-off

---

## Qdrant Client Management and Lifecycle

The `get_qdrant_client()` function in [`codebase_rag/vector_store.py`](https://github.com/vitali87/code-graph-rag/blob/main/codebase_rag/vector_store.py) implements **lazy singleton initialization** with automatic collection creation.

```python
from codebase_rag.vector_store import get_qdrant_client, close_vector_store_client
import atexit

# Client is created on first call

client = get_qdrant_client()

# Automatically registered at interpreter shutdown

atexit.register(close_vector_store_client)

```

The client creation logic distinguishes between:

- **Remote mode**: When `QDRANT_URL` is set, connects to a Qdrant server instance
- **Local mode**: When `QDRANT_DB_PATH` is set, creates a persistent local client

On first access, the system creates the collection with `cosine` distance metric and the configured vector dimension. An `atexit` hook ensures proper cleanup, avoiding the `portalocker` import-time issue that can occur during Python interpreter shutdown.

---

## Storing Code Embeddings with Retry Logic

The `store_embedding_batch()` function converts code node embeddings into Qdrant `PointStruct` objects and persists them with configurable retry behavior.

```python
from codebase_rag.vector_store import store_embedding_batch

# Each tuple: (node_id, embedding_vector, qualified_name)

points = [
    (123, [0.12, 0.34, 0.56, 0.78], "myproject.module.Class.method"),
    (124, [0.98, 0.01, 0.33, 0.44], "myproject.other.Module.function"),
]

stored = store_embedding_batch(points)
print(f"{stored} points stored in Qdrant")

```

Under the hood, `_upsert_with_retry()` in [`vector_store.py`](https://github.com/vitali87/code-graph-rag/blob/main/vector_store.py) implements exponential back-off for transient failures. The retry policy respects `settings.QDRANT_UPSERT_RETRIES` and `settings.QDRANT_RETRY_BASE_DELAY`, making the system resilient to network interruptions or temporary Qdrant unavailability.

---

## Performing Semantic Searches

The `search_embeddings()` method executes similarity queries against stored code embeddings.

```python
from codebase_rag.vector_store import search_embeddings

query_vector = [0.11, 0.22, 0.33, 0.44]  # Must match QDRANT_VECTOR_DIM

top_k = 10

results = search_embeddings(
    query_vector,
    top_k=top_k,
    project="myproject"  # Optional: scope to specific project

)

for node_id, score, qualified_name in results:
    print(f"{qualified_name} – similarity {score:.4f}")

```

The implementation calls `query_points()` on the Qdrant client and extracts structured results containing `node_id`, similarity `score`, and `qualified_name` from each point's payload.

---

## Project-Scoped Retrieval with Automatic Expansion

When the `project` parameter is provided, Code-Graph-RAG applies **client-side filtering** to ensure results belong to the specified codebase. The `_search_project_scoped()` function in [`vector_store.py`](https://github.com/vitali87/code-graph-rag/blob/main/vector_store.py) implements an adaptive fetch strategy:

1. Issue initial query with `top_k` results
2. Filter hits by project prefix using `_filter_by_project()`
3. If insufficient matches, exponentially expand fetch window up to a hard limit
4. Return the best project-matching results

This approach balances query efficiency with result relevance, avoiding excessive network traffic while guaranteeing useful results for multi-project deployments.

---

## Managing Embedded Code Data

The system provides utility functions for common maintenance operations:

**Delete embeddings for a specific project:**

```python
from codebase_rag.vector_store import delete_project_embeddings

node_ids = [123, 124, 125]  # IDs belonging to "myproject"

delete_project_embeddings("myproject", node_ids)

```

**Clear entire collection (testing/reset):**

```python
from codebase_rag.vector_store import clear_all_embeddings

clear_all_embeddings()  # Drops and recreates the Qdrant collection

```

**Verify which IDs are persisted:**

```python
from codebase_rag.vector_store import verify_stored_ids

expected = {123, 124, 125}
found = verify_stored_ids(expected)
missing = expected - found
assert len(missing) == 0, f"IDs not found: {missing}"

```

---

## Key Implementation Files

| File | Purpose |
|------|---------|
| [`codebase_rag/vector_store.py`](https://github.com/vitali87/code-graph-rag/blob/main/codebase_rag/vector_store.py) | `QdrantVectorStore` class, client management, upsert retry, search logic, project-scoped filtering |
| [`codebase_rag/config.py`](https://github.com/vitali87/code-graph-rag/blob/main/codebase_rag/config.py) | `AppConfig` with all Qdrant settings (`QDRANT_DB_PATH`, `QDRANT_URL`, vector dimension, retry policies) |
| [`codebase_rag/utils/dependencies.py`](https://github.com/vitali87/code-graph-rag/blob/main/codebase_rag/utils/dependencies.py) | `has_qdrant_client()` guards optional `qdrant-client` import |
| [`codebase_rag/constants.py`](https://github.com/vitali87/code-graph-rag/blob/main/codebase_rag/constants.py) | `VectorStoreBackend.QDRANT` enum for backend selection |

---

## Summary

- **Pluggable architecture** — Qdrant backend selected via `settings.VECTOR_STORE_BACKEND` with clean fallback handling
- **Resilient ingestion** — `_upsert_with_retry()` provides exponential back-off for reliable embedding storage
- **Adaptive search** — `_search_project_scoped()` expands result windows until project-relevant hits are found
- **Zero hard dependencies** — Optional `qdrant-client` import guarded by `has_qdrant_client()`
- **Graceful cleanup** — `atexit` registration prevents shutdown issues with `portalocker`

---

## Frequently Asked Questions

### What Qdrant deployment modes does Code-Graph-RAG support?

Code-Graph-RAG supports both **local persistent storage** via `QDRANT_DB_PATH` and **remote server connections** via `QDRANT_URL`. The `get_qdrant_client()` function automatically detects which mode to use based on configuration, with local mode suitable for development and remote mode for production deployments.

### How does the project-scoped search handle insufficient matches?

The `_search_project_scoped()` function implements an **expanding window strategy**. It starts with the requested `top_k`, filters by project prefix, and if too few matches remain, it re-queries with progressively larger fetch sizes up to a hard limit. This ensures useful results without requiring per-project collections.

### What happens if Qdrant is temporarily unavailable during embedding upsert?

The `_upsert_with_retry()` function catches transient failures and retries with **exponential back-off** based on `settings.QDRANT_RETRY_BASE_DELAY`. The maximum attempt count is controlled by `settings.QDRANT_UPSERT_RETRIES`. Permanent failures propagate as exceptions after retries are exhausted.

### Can I use Code-Graph-RAG without installing Qdrant dependencies?

Yes. The `has_qdrant_client()` check in [`dependencies.py`](https://github.com/vitali87/code-graph-rag/blob/main/dependencies.py) guards all Qdrant imports. If `qdrant-client` is not installed, the system can still load and use alternative backends like Milvus, or operate in modes that don't require vector storage. The package remains importable and inspectable regardless of optional dependencies.