
Search Your Codebase in Plain English: Semantic Code Search with Code Embeddings and Qdrant
Chris Harper
3 min read
Jul 30, 2026 · 04:06 UTC
Index your repo's functions and files as dense vectors with a code-specific embedding model, then retrieve exactly the right code with natural language — even when the variable names don't match your query.
What you'll be able to do after this:
- Encode functions and code files with
jinaai/jina-embeddings-v2-base-codeso natural language queries find code by intent, not just keyword - Build a dual-vector Qdrant collection — one model for NL→code search, one for code→code similarity — and query both from the same index
- Give your AI agent codebase context without loading every file into the prompt
Standard code search — grep, full-text search — finds what you typed. Semantic code search finds what you meant. The query "JWT token validation and expiry check" retrieves the right function even if it's named _check_bearer and never mentions "JWT." This matters for onboarding in a large codebase, for finding where logic lives when you only know what it does, and for giving AI agents efficient read access to a repo.
The dual-model approach
The Qdrant code search tutorial uses two models in one Qdrant collection — one per search direction:
| Model | Search direction |
|---|---|
sentence-transformers/all-MiniLM-L6-v2 | Natural language → code (English description → function) |
jinaai/jina-embeddings-v2-base-code | Code → code (find functions with similar logic) |
Install:
pip install qdrant-client sentence-transformers fastembed
Step 1: Extract function-level chunks
import ast, pathlib
def extract_functions(repo_path):
chunks = []
for path in pathlib.Path(repo_path).rglob("*.py"):
source = path.read_text(errors="ignore")
try:
tree = ast.parse(source)
except SyntaxError:
continue
for node in ast.walk(tree):
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
chunk = ast.get_source_segment(source, node)
if chunk:
chunks.append({"file": str(path), "name": node.name, "code": chunk})
return chunks
Step 2: Create a dual-vector Qdrant collection
from qdrant_client import QdrantClient
from qdrant_client.models import VectorParams, Distance
client = QdrantClient(":memory:") # swap for QdrantClient("http://localhost:6333") to persist
client.create_collection(
"code_search",
vectors_config={
"text": VectorParams(size=384, distance=Distance.COSINE), # all-MiniLM-L6-v2
"code": VectorParams(size=768, distance=Distance.COSINE), # jina-v2-base-code
}
)
Step 3: Encode and upload
from sentence_transformers import SentenceTransformer
from qdrant_client.models import PointStruct
nlp_model = SentenceTransformer("sentence-transformers/all-MiniLM-L6-v2")
code_model = SentenceTransformer("jinaai/jina-embeddings-v2-base-code", trust_remote_code=True)
chunks = extract_functions("./my-repo")
points = [
PointStruct(
id=i,
vector={
"text": nlp_model.encode(c["code"]).tolist(),
"code": code_model.encode(c["code"]).tolist(),
},
payload={"file": c["file"], "name": c["name"], "code": c["code"]},
)
for i, c in enumerate(chunks)
]
client.upload_points("code_search", points)
Step 4: Query in plain English
def search(query, use_code_model=False, top_k=5):
model = code_model if use_code_model else nlp_model
vector_name = "code" if use_code_model else "text"
results = client.search(
"code_search",
query_vector=(vector_name, model.encode(query).tolist()),
limit=top_k,
)
for r in results:
print(f"{r.payload['file']}::{r.payload['name']} score={r.score:.3f}")
print(r.payload["code"][:200])
print("---")
search("JWT token validation and expiry check") # NL → code
search("retry with exponential backoff", use_code_model=True) # code → code
When to use which model
textmodel (NL queries): "find auth middleware," "where is database connection pooled" — translate English intent to codecodemodel (code queries): paste a known function to find similar implementations, detect duplication across files- Layer over your AI agent: pass the top-3 results as context to Claude for tasks in an unfamiliar codebase — much cheaper than loading every file into the prompt
Sources: Semantic Search for Code — Qdrant Tutorial · Code Search Notebook — HuggingFace Open-Source AI Cookbook · jina-embeddings-v2-base-code — Jina AI · qdrant/demo-code-search — GitHub