# How Semantic Search Uses Qdrant for Vector-Based Code Retrieval in code-graph-rag

> Discover how code-graph-rag leverages Qdrant for vector-based code retrieval. This semantic search approach efficiently finds relevant code snippets using natural language queries.

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

---

**Semantic search in code-graph-rag embeds natural language queries as vectors and searches a Qdrant collection to retrieve the most similar code nodes, then fetches full metadata from the graph database.**

The **code-graph-rag** repository implements a hybrid retrieval system that combines **vector similarity search** via Qdrant with **graph-based metadata retrieval**. This article explains exactly how the `semantic_code_search` tool transforms a plain-text query into relevant code results using Qdrant's approximate nearest neighbor (ANN) capabilities.

## Embedding the Query with embed_code

Every semantic search begins with vectorization. The `semantic_code_search` function calls `embed_code` from [`codebase_rag/embedder.py`](https://github.com/vitali87/code-graph-rag/blob/main/codebase_rag/embedder.py) to convert the natural language query into a dense embedding:

- **Output dimension**: 768-dimensional vector (for UniXcoder)
- **Supported providers**: OpenAI (`text-embedding-3-small`) or UniXcoder via `sentence-transformers`
- **Source**: `embed_code` function at lines 80-89 of [`codebase_rag/embedder.py`](https://github.com/vitali87/code-graph-rag/blob/main/codebase_rag/embedder.py)

```python
from codebase_rag.embedder import embed_code

query = "how to parse JSON from a string in Python"
query_embedding = embed_code(query)  # Returns: numpy array (768,)

```

The embedding model is configured via `settings.EMBEDDING_PROVIDER` and loaded lazily for reuse across requests.

## Configuring Qdrant as the Vector Store Backend

The repository uses an abstraction layer that supports multiple vector stores, with **Qdrant as the default**.

In [`codebase_rag/constants/providers.py`](https://github.com/vitali87/code-graph-rag/blob/main/codebase_rag/constants/providers.py), the `VectorStoreBackend` enum defines available backends:

```python
class VectorStoreBackend(str, Enum):
    QDRANT = "qdrant"
    CHROMA = "chroma"  # Optional alternative

```

The global `settings.VECTOR_STORE_BACKEND` defaults to `VectorStoreBackend.QDRANT`. When `_get_vector_store()` is invoked in [`codebase_rag/vector_store.py`](https://github.com/vitali87/code-graph-rag/blob/main/codebase_rag/vector_store.py) (lines 30-38), it instantiates a `QdrantVectorStore` object.

## Initializing the Qdrant Client

The `QdrantVectorStore` manages client lifecycle through `get_qdrant_client()` at lines 50-68 of [`vector_store.py`](https://github.com/vitali87/code-graph-rag/blob/main/vector_store.py):

```python
def get_qdrant_client() -> QdrantClient:
    """Lazily create Qdrant client with auto-collection setup."""
    if settings.QDRANT_URL:
        client = QdrantClient(url=settings.QDRANT_URL, api_key=settings.QDRANT_API_KEY)
    else:
        client = QdrantClient(path=settings.QDRANT_DB_PATH)  # Local mode

    
    # Ensure collection exists with correct vector size

    _ensure_collection(client)
    return client

```

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

| Setting | Purpose | Default |
|---------|---------|---------|
| `QDRANT_URL` | Remote Qdrant server URL | `None` (local mode) |
| `QDRANT_DB_PATH` | Local database directory | `"./qdrant_db"` |
| `QDRANT_COLLECTION_NAME` | Target collection | `"code_embeddings"` |
| `VECTOR_SIZE` | Embedding dimension | `768` |

## Executing the Vector Search

The core retrieval happens in `QdrantVectorStore.search_embeddings()` at lines 76-87 of [`vector_store.py`](https://github.com/vitali87/code-graph-rag/blob/main/vector_store.py). This method:

1. Retrieves the query embedding
2. Calls `client.query_points()` with Qdrant's ANN index
3. Extracts `(node_id, score)` pairs from results
4. Optionally filters by project prefix

```python

# Inside vector_store.py — QdrantVectorStore.search_embeddings()

def search_embeddings(
    self,
    query_embedding: list[float],
    top_k: int = 10,
    project: str | None = None,
) -> list[tuple[str, float]]:
    client = get_qdrant_client()
    
    result = client.query_points(
        collection_name=settings.QDRANT_COLLECTION_NAME,
        query=query_embedding,
        limit=top_k,
        with_payload=True,  # Retrieve stored metadata

    )
    
    # Extract node IDs and similarity scores

    matches = [
        (point.payload["node_id"], point.score)
        for point in result.points
        if not project or point.payload.get("qualified_name", "").startswith(project)
    ]
    return matches

```

The **payload** stored with each Qdrant point contains:

- `node_id` — Unique identifier for the graph node (primary key)
- `qualified_name` — Fully qualified symbol name (e.g., `mypackage.module.function`)
- `project` — Repository/project prefix for filtering

## Mapping Vectors Back to Graph Nodes

Raw Qdrant results lack full code context. The `semantic_code_search` tool at lines 41-48 of [`codebase_rag/tools/semantic_search.py`](https://github.com/vitali87/code-graph-rag/blob/main/codebase_rag/tools/semantic_search.py) bridges this gap by querying the graph database:

```python

# Inside tools/semantic_search.py

from codebase_rag.query_builder import build_nodes_by_ids_query

def semantic_code_search(ingestor, query: str, top_k: int = 5, project: str | None = None):
    # 1. Embed query

    query_embedding = embed_code(query)
    
    # 2. Search Qdrant

    vector_results = search_embeddings(query_embedding, top_k=top_k, project=project)
    node_ids = [node_id for node_id, _ in vector_results]
    scores = {node_id: round(score, 4) for node_id, score in vector_results}
    
    # 3. Fetch full node details from graph

    cypher_query = build_nodes_by_ids_query(node_ids)
    graph_results = ingestor.run_query(cypher_query)
    
    # 4. Combine and format

    return [
        SemanticSearchResult(
            node_id=r["id"],
            qualified_name=r["qualified_name"],
            type=r["type"],  # FUNCTION, CLASS, METHOD, etc.

            score=scores[r["id"]],
        )
        for r in graph_results
    ]

```

This **hybrid architecture** separates concerns:

| Component | Responsibility | Technology |
|-----------|--------------|------------|
| Embedding | Convert text → dense vector | OpenAI/UniXcoder |
| Vector search | Find similar vectors fast | Qdrant (HNSW index) |
| Graph storage | Rich code metadata, relationships | Neo4j or compatible backend |

## Complete Usage Example

```python
from codebase_rag.tools.semantic_search import semantic_code_search

# ingestor: QueryProtocol instance with graph connection

results = semantic_code_search(
    ingestor=ingestor,
    query="async file download with retry logic",
    top_k=5,
    project="myproject",  # Optional: restrict to specific codebase

)

for r in results:
    print(f"{r.qualified_name} ({r.type}) — similarity: {r.score}")
    # Output: myproject.utils.http.download_with_retry (FUNCTION) — similarity: 0.8912

```

## Performance and Scaling Considerations

- **Index type**: Qdrant uses HNSW (Hierarchical Navigable Small World) for sub-10ms ANN queries at scale
- **Batching**: Use `embed_code_batch` for multiple queries to amortize embedding costs
- **Local vs. remote**: Local Qdrant (`QDRANT_DB_PATH`) suits single-node deployments; use `QDRANT_URL` for production clusters
- **Collection sizing**: Each code entity (function, class, method) becomes one Qdrant point; expect ~3-5x storage overhead for HNSW index

## Summary

- **`embed_code`** in [`codebase_rag/embedder.py`](https://github.com/vitali87/code-graph-rag/blob/main/codebase_rag/embedder.py) produces 768-dimensional query vectors using OpenAI or UniXcoder models
- **`QdrantVectorStore`** in [`codebase_rag/vector_store.py`](https://github.com/vitali87/code-graph-rag/blob/main/codebase_rag/vector_store.py) wraps the Qdrant client and executes `query_points()` ANN searches
- **`semantic_code_search`** in [`codebase_rag/tools/semantic_search.py`](https://github.com/vitali87/code-graph-rag/blob/main/codebase_rag/tools/semantic_search.py) orchestrates the full pipeline: embed → search Qdrant → fetch graph metadata → return formatted results
- **Configuration** defaults to local Qdrant but supports remote servers via environment variables in [`codebase_rag/config.py`](https://github.com/vitali87/code-graph-rag/blob/main/codebase_rag/config.py)
- The design intentionally separates vector search (similarity) from graph retrieval (relationships and metadata) for flexibility and performance

## Frequently Asked Questions

### How does code-graph-rag handle Qdrant authentication for remote servers?

Remote Qdrant instances are supported via the `QDRANT_URL` and `QDRANT_API_KEY` settings in [`codebase_rag/config.py`](https://github.com/vitali87/code-graph-rag/blob/main/codebase_rag/config.py). When `QDRANT_URL` is set, `get_qdrant_client()` creates a `QdrantClient` with URL and API key rather than local path. This enables managed Qdrant Cloud or self-hosted deployments with authentication.

### What embedding models does semantic_search support?

The `embed_code` function supports two providers configurable via `settings.EMBEDDING_PROVIDER`: **OpenAI** (`text-embedding-3-small`, 1536 dimensions) and **UniXcoder** via `sentence-transformers` (768 dimensions). The vector size in Qdrant is set during collection creation to match the active provider's output dimension.

### Can I use a different vector store instead of Qdrant?

Yes, though Qdrant is the default. The `VectorStoreBackend` enum in [`codebase_rag/constants/providers.py`](https://github.com/vitali87/code-graph-rag/blob/main/codebase_rag/constants/providers.py) allows switching to Chroma or other backends by implementing the same interface. You must update `settings.VECTOR_STORE_BACKEND` and ensure the alternative store's client code exists in [`vector_store.py`](https://github.com/vitali87/code-graph-rag/blob/main/vector_store.py).

### Why does semantic search query both Qdrant and the graph database?

This separation optimizes for different access patterns. Qdrant excels at fast approximate similarity search over high-dimensional vectors but stores only lightweight payloads (node_id, qualified_name). The graph database contains rich relationships (calls, inherits, contains) and full node properties. Querying both gives accurate similarity rankings plus complete, traversable code context.