How Semantic Search with Vector Embeddings Works in code-review-graph

The code-review-graph project implements semantic search by combining BM25-based full-text search with dense vector similarity search, merging both result sets using Reciprocal Rank Fusion (RRF) to deliver accurate code discovery even when query keywords don't literally match the source text.

The code-review-graph repository provides a hybrid search architecture designed for intelligent code understanding. By integrating vector embeddings with traditional keyword matching through SQLite's FTS5 extension, the system surfaces conceptually related functions and classes even when identifiers and docstrings don't share overlapping terms. This approach ensures users receive relevant results whether they search with exact technical terminology or natural language descriptions.

Hybrid Search Architecture

The semantic search system operates as a multi-layered pipeline that gracefully degrades when components are unavailable. Rather than relying solely on vector similarity—which requires pre-computed embeddings—the architecture maintains a fast keyword search path while seamlessly blending semantic signals when present.

According to the source code in code_review_graph/search.py (lines 9-35), three primary components orchestrate the workflow:

  • semantic_search_nodes – The public API entry point defined in code_review_graph/tools/query.py (lines 699-707) that validates inputs, selects search modes, and formats final responses with contextual hints.
  • hybrid_search – The core engine that executes parallel FTS5 and embedding queries, applies RRF merging, and tracks which search path contributed to each result.
  • _embedding_search – The optional vector retrieval layer that loads an EmbeddingStore and maps nearest-neighbor vectors back to node IDs.

How Embedding Generation Works

Before semantic search can operate, the system must generate dense vector representations for every node in the code graph. This process is handled by the embed_graph tool in code_review_graph/tools/docs.py (lines 20-35).

When embed_graph executes, it instantiates an EmbeddingStore configured with either a local sentence-transformers model or a remote API provider. Each node's textual representation—including its qualified name, signature, and docstring—is passed through the provider to generate a fixed-dimensional vector, which is then persisted in a SQLite-backed embedding table. This indexing step runs once per repository or whenever the codebase changes significantly.

Hybrid Query Execution and RRF Merging

When semantic_search_nodes receives a query, it delegates to hybrid_search, which executes two distinct search paths simultaneously:

  1. Full-Text Search (FTS5) via _fts_search, generating (node_id, bm25_score) tuples from the virtual nodes_fts table.
  2. Vector Similarity Search via _embedding_search (lines 13-44 in code_review_graph/search.py), retrieving nearest-neighbor vectors and producing (node_id, similarity_score) tuples.

These result sets are merged using Reciprocal Rank Fusion (RRF), implemented in the rrf_merge function. RRF assigns each result a reciprocal rank score calculated as 1 / (k + rank), where the constant k defaults to 60. Nodes appearing in both result sets receive higher combined scores, effectively boosting items that satisfy both keyword and semantic relevance.

After merging, the system applies additional heuristics: results are boosted based on node kind (e.g., functions vs. classes), qualified-name pattern matching, and proximity to context_files specified in the query.

Fallback Mechanisms and Result Packaging

The architecture includes robust degradation paths. If _embedding_search returns an empty list—either because the EmbeddingStore is uninitialized or provider dependencies are missing—hybrid_search falls back to FTS5-only mode. If FTS5 also fails, it degrades further to a simple LIKE keyword search against node names.

The _out_mode flag populated by hybrid_search indicates which path succeeded: "semantic" for embeddings-only, "fts" for keyword-only, or "hybrid" when both contributed. The semantic_search_nodes tool uses this to annotate responses, attaching autogenerated hints that explain why specific nodes matched the query.

Implementation Examples

Populate the embedding index before running semantic queries. This creates the vector representations stored in code_review_graph/embeddings.EmbeddingStore:

from code_review_graph.tools.docs import embed_graph

embed_status = embed_graph(repo_root="/path/to/repo")
print(embed_status["summary"])

# → "Embedded 1245 new node(s). Total embeddings: 4523. Semantic search is now active."

Execute a semantic search with context-aware boosting. The context_files parameter prioritizes results from specific modules:

from code_review_graph.tools.query import semantic_search_nodes

result = semantic_search_nodes(
    query="authenticate user",
    kind="Function",
    limit=10,
    context_files=["src/auth.py"],
    model="all-MiniLM-L6-v2",
    provider="local",
)
print(result["search_mode"])  # → "hybrid" or "semantic"

print(result["results"][:3])   # Top 3 matches with hybrid scores

For manual control over the search pipeline, invoke hybrid_search directly with a GraphStore instance:

from code_review_graph.search import hybrid_search
from code_review_graph.graph import GraphStore

store = GraphStore("/path/to/repo/.crg.db")
results = hybrid_search(
    store,
    query="payment processor",
    kind="Class",
    limit=5,
    model="all-MiniLM-L6-v2",
    provider="local",
    _out_mode=None,
)

for r in results:
    print(f"{r['qualified_name']}: {r['score']}")
store.close()

Summary

  • Hybrid approach: The system combines SQLite FTS5 (BM25) with dense vector similarity to capture both literal and conceptual matches.
  • RRF merging: Reciprocal Rank Fusion blends result sets from both search paths, using a default constant of 60 to calculate combined relevance scores.
  • Graceful degradation: Search automatically falls back from hybrid → FTS5-only → keyword LIKE when embeddings or full-text indexes are unavailable.
  • Key files: Implementation resides in code_review_graph/search.py (hybrid engine), code_review_graph/tools/query.py (public API), and code_review_graph/tools/docs.py (embedding generation).

Frequently Asked Questions

What embedding providers does code-review-graph support?

The system supports local sentence-transformers models via the EmbeddingStore class and can be extended to remote API providers. Configure the provider using the provider="local" parameter and specify model names like all-MiniLM-L6-v2 when calling embed_graph or semantic_search_nodes.

Why does the system use Reciprocal Rank Fusion instead of averaging scores?

RRF effectively normalizes scores from heterogeneous ranking systems—BM25 and cosine similarity—whose scales are incompatible. By converting ranks to reciprocal values with a constant k=60, the method gives high weight to top-ranked items from either system without requiring score calibration or training data.

How do I handle repositories too large for in-memory embedding stores?

The EmbeddingStore abstraction in code_review_graph/embeddings.py uses SQLite as the backing database, persisting vectors to disk rather than memory. For very large codebases, you can incrementally index subsets using embed_graph and rely on the nearest-neighbor search to retrieve relevant vectors without loading the entire embedding space into RAM.

What happens if my query matches no embeddings?

If _embedding_search returns no results, hybrid_search automatically falls back to the FTS5 path. Should FTS5 also return empty results, the system performs a final fallback to a simple SQL LIKE query on node qualified names, ensuring users always receive some form of relevant output even in sparse indexing scenarios.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →