Semantic Search with Qdrant Vector Database Integration in Code-Graph-RAG
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, 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. 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 include:
settings.QDRANT_URL— Remote Qdrant server endpointsettings.QDRANT_DB_PATH— Local persistent storage directorysettings.QDRANT_COLLECTION_NAME— Target collection identifiersettings.QDRANT_VECTOR_DIM— Embedding dimension (must match your encoder)settings.QDRANT_UPSERT_RETRIES— Maximum retry attempts for upsertssettings.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 implements lazy singleton initialization with automatic collection creation.
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_URLis set, connects to a Qdrant server instance - Local mode: When
QDRANT_DB_PATHis 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.
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 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.
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 implements an adaptive fetch strategy:
- Issue initial query with
top_kresults - Filter hits by project prefix using
_filter_by_project() - If insufficient matches, exponentially expand fetch window up to a hard limit
- 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:
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):
from codebase_rag.vector_store import clear_all_embeddings
clear_all_embeddings() # Drops and recreates the Qdrant collection
Verify which IDs are persisted:
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 |
QdrantVectorStore class, client management, upsert retry, search logic, project-scoped filtering |
codebase_rag/config.py |
AppConfig with all Qdrant settings (QDRANT_DB_PATH, QDRANT_URL, vector dimension, retry policies) |
codebase_rag/utils/dependencies.py |
has_qdrant_client() guards optional qdrant-client import |
codebase_rag/constants.py |
VectorStoreBackend.QDRANT enum for backend selection |
Summary
- Pluggable architecture — Qdrant backend selected via
settings.VECTOR_STORE_BACKENDwith 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-clientimport guarded byhas_qdrant_client() - Graceful cleanup —
atexitregistration prevents shutdown issues withportalocker
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 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.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →