Query Types and Return Modes
LocalVectorDB provides powerful and flexible query capabilities with multiple search types and return modes. This guide covers all available options and when to use them.
Search Types
LocalVectorDB supports three complementary search approaches:
search_type="vector"
Performs semantic similarity search using vector embeddings. Best for finding conceptually related content even when exact keywords don’t match.
# Find documents about machine learning concepts
results = db.query(
"artificial intelligence algorithms",
search_type="vector"
)
When to use: - Finding conceptually similar content - Cross-language or synonym matching - Abstract concept queries - When keyword matching is too restrictive
search_type="keyword"
Uses full-text search (FTS5) to find documents containing specific terms. Ideal for exact phrase matching and traditional text search.
# Find documents containing specific terms
results = db.query(
"machine learning",
search_type="keyword"
)
When to use: - Looking for specific terminology - Exact phrase matching - Technical terms or proper nouns - When you need precise keyword matches
Query syntax
Plain text is treated as a description of what you are looking for: its terms are
combined with OR and the results are ranked by BM25, so a document matching the
rarer terms of your query outranks one matching only its common words. You do not
need every word to appear.
For precise control, three pieces of FTS5 syntax are honoured:
# Exact phrase - the words must appear together, in order
db.query('"randomized controlled trial"', search_type="keyword")
# Require both terms: AND / OR / NOT must be UPPERCASE to be operators
db.query("aspirin AND mortality", search_type="keyword")
# Exclude a term
db.query("aspirin NOT aspirin-resistance", search_type="keyword")
Lowercase and, or and not are ordinary words, not operators. A question
like "does aspirin not reduce risk" searches for those six words, which is almost
certainly what you meant.
Note
Everything else is escaped. FTS5 metacharacters (*, :, ^, NEAR,
parentheses) in your query text are stripped rather than interpreted, so untrusted
input cannot alter the search expression.
search_type="hybrid"
Combines vector and keyword search with configurable weighting. Provides the best of both semantic understanding and precise term matching.
# Balanced semantic and keyword search
results = db.query(
"neural network architectures",
search_type="hybrid",
vector_weight=0.5 # 50% vector, 50% keyword
)
Each leg is min-max normalized within the current query’s candidate pool before the
two are blended, so vector_weight shifts the balance rather than merely deciding
which leg’s raw scale dominates. See Document Scoring Methods for the exact formula.
Warning
A hybrid score is relative to the query’s own candidate pool, so it is comparable
within one result set but not across queries, and not across different values of
k. Use score_threshold on hybrid queries to cut the tail of a ranking, not
to assert an absolute match quality.
When to use: - Most general-purpose searches (recommended default) - When you want both semantic and exact matches - Balancing precision and recall - When unsure which search type is best
Return Types
LocalVectorDB offers five return modes optimized for different use cases.
return_type is optional. Left unset it follows search_level — it gives
you back the unit the query was matched against. For the default chunk search
that means "documents", so the mode below is what you get if you say
nothing; for search_level="sections" it means sections. Set it explicitly to
ask for a different unit — see Hierarchical Embeddings.
return_type="documents"
Returns complete documents with aggregated scores from all matching chunks. Uses document scoring methods to combine chunk-level results.
There are a number of different valid inputs for the document_scoring_method parameter, which modify how the
similarity score for the document is calculated. For a list of possible methods, see document scoring methods.
# Get full documents ranked by relevance
results = db.query(
"machine learning",
return_type="documents",
document_scoring_method="frequency_boost"
)
for doc in results:
print(f"Document: {doc.id}")
print(f"Score: {doc.score}")
print(f"Content: {doc.content[:200]}...")
When to use: - Want complete document context - Need to see full content - Ranking documents by overall relevance - Traditional document retrieval
return_type="chunks"
Returns individual matching chunks with their positions and metadata. Provides fine-grained access to specific relevant passages.
# Get specific matching passages
results = db.query(
"neural networks",
return_type="chunks"
)
for chunk in results:
print(f"Chunk: {chunk.id}")
print(f"Document: {chunk.document_id}")
print(f"Position: {chunk.position}")
print(f"Content: {chunk.content}")
When to use: - Need specific relevant passages - Building search result snippets - Fine-grained relevance analysis - When document context isn’t needed
return_type="context"
Returns matching chunks enhanced with surrounding chunks for better readability. Combines the target chunk with neighboring chunks based on position.
# Get chunks with surrounding context
results = db.query(
"deep learning",
return_type="context",
context_window=2 # Include 2 chunks before/after
)
for result in results:
print(f"Context: {result.id}")
print(f"Original chunk: {result.metadata['_original_chunk_index']}")
print(f"Context spans {result.metadata['_context_chunk_count']} chunks")
print(f"Content: {result.content}")
Sizing context by tokens, words, or characters:
By default context_window counts chunks. Set context_unit to
"tokens", "words", or "characters" to instead treat context_window
as an approximate budget for the assembled context. Neighbouring chunks are added
whole and greedily (the matched chunk is always kept) until the next one would
exceed the budget, so the returned content never overshoots — unless a single
chunk is already larger than the budget.
# "Give me roughly 500 tokens of context around each match"
results = db.query(
"deep learning",
return_type="context",
context_window=500,
context_unit="tokens", # or "words" / "characters"
)
Because whole chunks are kept, the result can fall short of the budget by up to
one chunk. To guarantee a hard upper bound (for example when packing an LLM
context window), set context_truncate=True — the assembled text is then cut to
exactly the budget (tokens via tiktoken; words/characters back off to a
whitespace boundary). Truncated results are flagged with
metadata["_context_truncated"] = True and their end position becomes
approximate.
results = db.query(
"deep learning",
return_type="context",
context_window=500,
context_unit="tokens",
context_truncate=True, # never exceed 500 tokens, even for one big chunk
)
The chosen unit is always recorded on each result as
metadata["_context_unit"].
When to use:
Need readable context around matches
Preserving document flow and coherence
Creating human-readable excerpts
When individual chunks lack sufficient context
Packing a fixed token/character budget (e.g. an LLM prompt) — use
context_unit+context_truncate
return_type="enriched"
New in this release! Returns chunks enhanced with semantically similar chunks from the same document. Uses intra-document similarity to find the most relevant related content.
# Get semantically enriched results
results = db.query(
"machine learning",
return_type="enriched",
context_window=3 # Include up to 3 similar chunks
)
# ...or size the enrichment by a token/word/character budget instead:
results = db.query(
"machine learning",
return_type="enriched",
context_window=400,
context_unit="tokens", # add the most-similar chunks that fit in ~400 tokens
)
for result in results:
print(f"Enriched: {result.id}")
print(f"Matched chunks: {result.metadata['_matched_chunk_indices']}")
print(f"All chunks: {result.metadata['_all_chunk_indices']}")
print(f"Similarity scores: {result.metadata['_similarity_scores']}")
print(f"Content: {result.content}")
Key Features: - One result per document (combines all matches) - Semantic similarity within documents - Automatic deduplication of chunks - Rich metadata about enrichment process
When to use: - Want comprehensive document excerpts - Need related context within documents - Building AI/RAG applications - When topical coherence is important
return_type="sections"
Returns section-level results (one result per matching section, with
type="section"). Requires a database created with
hierarchical_embeddings=True. Pair it with search_level="sections" to
match against the section index directly:
results = db.query(
"how do I rotate the API key?",
search_level="sections",
)
for r in results:
print(r.score, r.metadata["section_heading"])
return_type is optional: left unset it follows search_level, so the
query above returns sections without being told to. Set it to pick a different
unit — search_level="sections", return_type="documents" ranks whole
documents by their best-matching section.
See Hierarchical Embeddings for the full three-level (document → section → chunk) retrieval model, section detection, and section metadata.
Parameters and Options
Common Parameters
All query methods support these parameters:
k(int, default=10): Maximum number of results to returnscore_threshold(float, default=0.0): Minimum similarity score (0-1, higher=better). On hybrid queries scores are normalized within the query’s own candidate pool, so this cuts the tail of this ranking rather than asserting an absolute match quality, and is not a bar you can tune once and reuse across queries (see the warning undersearch_type="hybrid"above).rerank_k(int, optional): Size of the candidate pool fetched before reranking, defaulting to5 * k(capped at 200). Only has an effect whenrerankerorreranker_configis supplied — without over-fetching, a reranker can only reorder the topkit was already given, so it cannot improve recall. See Embeddings for available rerankers.filters(dict, optional): Metadata filters to apply. Filter fields must be declared in the database’smetadata_schema(reserved columns likeidandcreated_atare also allowed); filtering on an undeclared field or using an unsupported operator raisesMetadataFilterError(aDatabaseErrorandValueErrorsubclass; HTTP 400INVALID_FILTERover the server API). See Metadata Filtering Guide.
Search Type Specific
Hybrid Search:
vector_weight(float, default=0.5): Weight for vector vs keyword results (0.0-1.0)
Context and Enriched:
context_window(int, default=2): Size of the assembled context, measured incontext_unit. In the default"chunks"unit this is the number of surrounding/similar chunks to include; with a budget unit it is an approximate token/word/character budget.context_unit(str, default=”chunks”): One of"chunks","tokens","words","characters". Selects howcontext_windowis interpreted.context_truncate(bool, default=False): With a budget unit, hard-truncate the assembled context to exactly the budget (otherwise whole chunks are kept and the result may fall short of the budget).
Document Return Type:
document_scoring_method(str, default=”frequency_boost”): How to aggregate chunk scoresdocument_scoring_options(dict, optional): Parameters for scoring methods
Hierarchical Search:
search_level(str, default=”chunks”): Which retrieval level to query —"chunks","sections","documents", or"fused". The"sections","documents", and"fused"levels requirehierarchical_embeddings=True."fused"blends chunk and section (raw-span) retrieval and is the mode that improves quality on long, structured documents. See Hierarchical Embeddings.section_weight(float, default=0.65): Weight on the section leg whensearch_level="fused"(0 = chunk-only, 1 = section-only). Ignored for other levels.
Advanced Options:
semantic_dedup_threshold(float, optional): Remove semantically similar results
Practical Examples
Multi-Modal Search Strategy
# Start with hybrid search for balanced results
results = db.query("neural network training", search_type="hybrid")
if len(results) < 5:
# Fall back to vector search for broader matches
results = db.query("neural network training", search_type="vector")
if len(results) < 3:
# Use keyword search for exact terms
results = db.query("neural network", search_type="keyword")
Progressive Context Enrichment
# Start with chunks for precision
chunks = db.query("transformer architecture", return_type="chunks")
if chunks:
# Get enriched results for better context
enriched = db.query(
"transformer architecture",
return_type="enriched",
context_window=4
)
# Compare chunk precision vs enriched comprehensiveness
print(f"Precise chunks: {len(chunks)}")
print(f"Enriched results: {len(enriched)}")
Adaptive Scoring
# For research/comprehensive search: reward documents with several relevant chunks
scholarly_results = db.query(
"climate change impacts",
return_type="documents",
document_scoring_method="frequency_boost",
document_scoring_options={
"frequency_bias": 0.4
}
)
# For finding best excerpts
excerpt_results = db.query(
"climate change impacts",
return_type="enriched",
context_window=3,
)
Building RAG Applications
def rag_query(question: str, max_context: int = 2000):
"""Get optimal context for RAG applications"""
# Use enriched results for comprehensive context
results = db.query(
question,
search_type="hybrid",
return_type="enriched",
context_window=4,
k=3
)
# Combine results within token limit
context_parts = []
total_length = 0
for result in results:
if total_length + len(result.content) < max_context:
context_parts.append(result.content)
total_length += len(result.content)
else:
break
return "\n\n".join(context_parts)
Performance Considerations
Vector Search: - Requires embedding generation for queries - Scales with FAISS index size - CPU/GPU intensive for large collections
Keyword Search: - Fast FTS5 queries - Scales well with document count - Limited to exact term matching
Hybrid Search: - Combines both search costs - Benefits from both search strengths - Recommended for most use cases
Return Types Performance:
- documents: Fastest, minimal processing
- chunks: Fast, direct chunk access
- context: Moderate, requires chunk assembly
- enriched: Slower, requires similarity calculations
Best Practices:
- Use enriched for quality over speed
- Use chunks for high-volume applications
- Cache frequently-used enriched results
- Consider semantic_dedup_threshold for large result sets
Streaming Large Result Sets
For large-scale retrieval where loading all results into memory at once is impractical, LocalVectorDB provides
cursor-based streaming. A QueryCursor performs the FAISS/FTS search once and lazily loads content from SQLite
in batches as you iterate:
# Stream results in batches instead of loading all at once
for batch in db.query_stream(
"machine learning",
search_type="hybrid",
return_type="chunks",
k=100,
batch_size=10,
):
for result in batch:
process(result)
# Async streaming with backpressure
async for batch in db.query_stream_async(
"deep learning",
search_type="vector",
return_type="documents",
k=200,
batch_size=25,
):
await process_batch(batch)
For the full streaming API including QueryCursor lifecycle management, async generators, and QueryBuilder
integration, see Streaming Query Results.
See Also
Streaming Query Results - Cursor-based streaming for large result sets
Document Scoring Methods - Document scoring methods reference
Metadata Filtering Guide - Advanced filtering options
Embeddings - Embedding provider configuration