Hybrid Search
The core query pattern in Purple8 — vector similarity, BM25 full-text, and graph traversal unified in a single Cypher statement.
Why one query matters
In most systems, hybrid search is two queries:
- Vector search → top-k node IDs
- Graph query → pass those IDs in as a filter
The latency budget is doubled. The failure domain is doubled. And the boundary between "find by meaning" and "follow connections" lives in your application code, not the query engine.
Purple8 erases that boundary. CALL db.vector.search is a first-class Cypher procedure — its output feeds directly into the next MATCH clause in the same query planner, same process, same round-trip.
Benchmarked result: 3.53 ms p50 at 100,000 documents (MS MARCO, all-MiniLM-L6-v2).
The three search modalities
1. Vector search (semantic)
CALL db.vector.search('Document', $queryVec, 10) YIELD node, score
WHERE score > 0.80
RETURN node.title, score
ORDER BY score DESC- Searches the HNSW index on the
Documentlabel - Returns up to 10 results with cosine similarity scores (0–1)
WHERE score > 0.80filters post-search
2. Full-text search (BM25)
results = engine.fulltext_search("transformer attention", labels=["Document"], limit=10)Or in Cypher (via the hybrid_text_vector_search engine method):
results = engine.hybrid_text_vector_search(
query_text="transformer attention",
query_vector=query_vec,
label="Document",
vector_weight=0.7, # 70% semantic, 30% keyword
limit=10,
)3. Graph traversal (structural)
MATCH (p:Person {name: 'Alice Chen'})<-[:AUTHORED_BY]-(doc:Document)
MATCH (doc)-[:BELONGS_TO]->(topic:Topic)
RETURN doc.title, topic.nameCombining all three
-- Vector seeds → filter → follow relationships → return enriched results
CALL db.vector.search('Document', $queryVec, 20) YIELD node AS doc, score
WHERE score > 0.75
AND doc.year >= 2020
MATCH (doc)-[:AUTHORED_BY]->(author:Person)
MATCH (author)-[:WORKS_AT]->(org:Organization)
OPTIONAL MATCH (doc)-[:CITES]->(cited:Document)
RETURN
doc.title AS title,
doc.year AS year,
author.name AS author,
org.name AS org,
count(cited) AS citation_count,
score
ORDER BY score DESC, citation_count DESC
LIMIT 10Filtered vector search
Pre-filter by label and post-filter by property — maintained by the engine, not the caller:
# Filter to a specific label before HNSW search (fast — uses label index)
results = engine.vector_search(
query_vector=query_vec,
label="Document",
limit=10,
filter={"region": {"$eq": "APAC"}}, # MongoDB-style operators
)Supported filter operators: $eq, $ne, $lt, $lte, $gt, $gte, $in, $nin, $exists
Named embedding slots
A node can carry multiple embeddings — for example, a document with both a title_embedding and a body_embedding:
engine.add_node("doc1", labels=["Document"], properties={
"title": "Attention is all you need",
"title_embedding": title_vec, # 384-dim
"body_embedding": body_vec, # 384-dim
})
# Search against the title slot
engine.vector_search_slot("title_embedding", query_vec, limit=10)
# Search against the body slot
engine.vector_search_slot("body_embedding", query_vec, limit=10)DiskANN — on-disk vector index (larger than RAM)
When your vector index exceeds available RAM, switch to the DiskANN backend:
pip install "purple8-graph[diskann]"
export P8G_VECTOR_BACKEND=diskannSame public API — zero code changes. DiskANN uses usearch with memory-mapped files. Activate per-process via environment variable.
Quantized vector indexes
Reduce memory footprint at scale with int8 or binary quantization:
export P8G_VECTOR_QUANTIZATION=int8 # or: binary- int8: calibrated scale, pre-screen then full-precision rerank — best accuracy/memory trade-off
- binary: Hamming pre-screen then full-precision — maximum compression, modest accuracy drop
Benchmark results
All runs on all-MiniLM-L6-v2 (384-dim) embeddings.
MS MARCO — Passage Retrieval (MRR@10)
| Scale | Purple8 MRR | BM25 MRR | Purple8 p50 | vs BM25 |
|---|---|---|---|---|
| 2,500 passages | 0.9658 | 0.8233 | 0.93 ms | — |
| 25,000 passages | 0.8810 | 0.7117 | 1.99 ms | — |
| 100,000 passages | 0.7511 | 0.6025 | 3.53 ms | 182× |
Entity disambiguation (Suite A)
| Task | Purple8 | Neo4j + Vector | Kùzu + HNSW | FalkorDB |
|---|---|---|---|---|
| Disambiguate author by graph context | ✅ 100% | ⚠️ 50% | ⚠️ 50% | ⚠️ 50% |
| Multi-hop topic cluster | ✅ 67% | ✅ 67% | ✅ 67% | ✅ 67% |
| Knowledge synthesis | ✅ 100% | ✅ 100% | ✅ 100% | ✅ 100% |
| Temporal chain reconstruction | ✅ 100% | ✅ 100% | ✅ 100% | ✅ 100% |
| Mean accuracy | 🥇 91.7% | 79.2% | 79.2% | 79.2% |