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 return

  • score_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 under search_type="hybrid" above).

  • rerank_k (int, optional): Size of the candidate pool fetched before reranking, defaulting to 5 * k (capped at 200). Only has an effect when reranker or reranker_config is supplied — without over-fetching, a reranker can only reorder the top k it 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’s metadata_schema (reserved columns like id and created_at are also allowed); filtering on an undeclared field or using an unsupported operator raises MetadataFilterError (a DatabaseError and ValueError subclass; HTTP 400 INVALID_FILTER over 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 in context_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 how context_window is 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 scores

  • document_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 require hierarchical_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 when search_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