QMD (Query Markup Documents)

repository·main·Indexed 12 days ago

https://github.com/tobi/qmd

An on-device search engine for indexing markdown-based content such as notes and transcripts. QMD provides a hybrid search experience combining BM25 full-text search, vector semantic search, and LLM re-ranking for agentic workflows. It includes a CLI, an MCP server for AI agents, and tools for fine-tuning small language models (like Qwen3-1.7B) for query expansion using SFT and experimental GRPO training.

Tokens
38.3K
Snippets
129
Records
172
Agent score
96%

What's inside QMD

  1. Overview of QMD (Query Markup Documents)

    main

    QMD is an on-device search engine designed for indexing markdown notes, meeting transcripts, documentation, and knowledge bases. It is optimized for agentic workflows by combining three search technologies:

    1. BM25 full-text search for keyword matching.
    2. Vector semantic search for natural language understanding.
    3. LLM re-ranking to refine results.

    All search components run locally using node-llama-cpp with GGUF models, ensuring privacy and low latency.

  2. Fine-tune small language models for QMD query expansion

    main

    The qmd-finetune package allows you to train small language models (like Qwen/Qwen3-1.7B) to expand raw search queries into structured formats compatible with QMD's hybrid retrieval pipeline.

    The model produces three types of expansions:

    • lex:: Short, keyword-focused lines for BM25 full-text search.
    • vec:: Natural language phrases for vector similarity search.
    • hyde:: A hypothetical document passage for embedding-based retrieval.

    Example output for a query like "auth config":

    hyde: Authentication can be configured by setting the AUTH_SECRET environment variable.
    lex: authentication configuration
    lex: auth settings setup
    vec: how to configure authentication settings
    vec: authentication configuration options
  3. How the QMD Hybrid Search Pipeline works

    main

    QMD uses a hybrid search pipeline that combines keyword-based (FTS5 BM25) and semantic (Vector) search. The process follows these stages:

    1. Query Expansion: The original query is weighted (×2) and supplemented with two alternative queries generated via a fine-tuned LLM.
    2. Parallel Retrieval: Each query (original and expanded) is run against both the FTS and Vector indexes.
    3. RRF Fusion: Results are combined using Reciprocal Rank Fusion (RRF) with a constant $k=60$. A Top-Rank Bonus is applied to preserve high-confidence matches: documents ranking #1 in any list get +0.05, and #2-3 get +0.02.
    4. LLM Re-ranking: The top 30 candidates are passed to an LLM reranker (qwen3-reranker) which provides a yes/no relevance score with logprobs.
    5. Position-Aware Blending: The final score is a weighted blend of the RRF rank and the reranker score to prevent the reranker from overriding high-confidence retrieval results:
      • RRF rank 1-3: 75% retrieval, 25% reranker
      • RRF rank 4-10: 60% retrieval, 40% reranker
      • RRF rank 11+: 40% retrieval, 60% reranker
  4. Understand the QMD Data Storage and Schema

    main

    QMD stores its index in a SQLite database located at ~/.cache/qmd/index.sqlite. The schema is organized into several specialized tables to support hybrid search:

    • collections: Stores indexed directories with their names and glob patterns.
    • path_contexts: Stores context descriptions mapped to virtual paths (qmd://...).
    • documents: Contains Markdown content, metadata, and a 6-character docid hash.
    • documents_fts: A Full-Text Search (FTS5) index for keyword matching.
    • content_vectors: Stores embedding chunks (approx. 900 tokens each) with hash, seq, and pos metadata.
    • vectors_vec: A sqlite-vec vector index using hash_seq keys.
    • llm_cache: Caches LLM responses for query expansion and rerank scores.
  5. Preserve Named Entities in Query Expansions

    main

    Named entities (proper nouns, brand names, technical terms, acronyms) are critical for retrieval. They MUST appear in lex: queries to prevent generic expansions that lose the specific topic.

    Entity Types to Protect:

    • All-caps acronyms: TDS, API, GPU, AWS
    • Capitalized proper nouns: React, Docker, Bob
    • Technical terms: node.js, C++, .NET
    • CamelCase: JavaScript, TypeScript
    • Compound names: TDS motorsports (both words are entities)
    • Project names: Project Atlas

    Critical Rules:

    1. Presence: If a query mentions an entity, it MUST appear in the lex: or vec: output. Dropping a name (especially a person's name) incurs a heavy penalty.
    2. Avoid Generic Fillers: Do NOT use phrases like find information about, search for, or details about in lex: lines. These are considered banned filler.
    3. Quoting: Use "quoted phrases" in lex: output for multi-word technical terms or proper nouns (e.g., "machine learning") to aid BM25 exact matching.
    Query: python memory leak debugging
    Good lex: "memory leak" python -java -javascript
    Good lex: tracemalloc "garbage collector" profiler
  6. Understand the Hybrid Query Flow

    main

    QMD uses a sophisticated hybrid search and re-ranking pipeline to return high-quality results:

    1. LLM Expansion: The original query is expanded into multiple variants.
    2. Dual Search: For each query variant, QMD performs both Vector Search and FTS (BM25) Full-Text Search.
    3. RRF Fusion: Results from both searches are fused using Reciprocal Rank Fusion (RRF) with $k=60$. The original query is given $\times 2$ weight.
    4. LLM Re-ranking: The top 30 candidates are passed to an LLM reranker to confirm relevance.
    5. Position-Aware Blend: The final results are a weighted blend of RRF and reranker scores, where the weight of the reranker increases as the rank decreases (Rank 1-3 is 25% reranker, Rank 11+ is 60% reranker).
  7. How QMD Indexing and Embedding Works

    main

    Indexing Flow

    QMD follows a pipeline from collections to a searchable SQLite database:

    1. Collection $\rightarrow$ Glob Pattern $\rightarrow$ Markdown Files
    2. Parse Title $\rightarrow$ Hash Content $\rightarrow$ Generate docid (6-char hash)
    3. Store in SQLite $\rightarrow$ FTS5 Index

    Embedding and Smart Chunking

    Documents are split into $\sim$900-token chunks with a 15% overlap. QMD uses a scoring algorithm to find natural markdown break points (like headings or code blocks) rather than cutting at arbitrary token boundaries. This ensures semantic units stay together.

    Break Point Scores:

    • # Heading (H1): 100
    • ## Heading (H2): 90
    • ### Heading (H3): 80
    • ``` (Code block): 80
    • --- / *** (Horizontal rule): 60
    • Blank line: 20
    • List item: 5
    • Line break: 1

    Code File Support: When using --chunk-strategy auto, QMD uses tree-sitter to parse supported languages (.ts, .tsx, .js, .jsx, .py, .go, .rs) and adds AST-derived break points (e.g., Class/Interface: 100, Function/Method: 90).

    # Example command to enable AST-aware chunking for code files
    qmd index --chunk-strategy auto
  8. Understand QMD Query Syntax and Types

    main

    QMD queries are structured documents that allow you to combine different search methodologies in a single request. You can use three primary search types:

    • lex: Keyword search using BM25 for exact matching and prefix support.
    • vec: Semantic similarity search using natural language questions.
    • hyde: Hypothetical Document Embedding. You provide a 50-100 word passage describing what you expect the answer to look like.

    Queries can be a single-line 'expand query' (which automatically generates all three types) or a multi-line 'query document' where you explicitly define each line with a type prefix.

    lex: CAP theorem consistency
    vec: how does the rate limiter handle burst traffic
    hyde: The rate limiter uses a sliding window algorithm with a 60-second window...
  9. Provide context using Intent

    main

    The intent: line provides background context to disambiguate queries (e.g., distinguishing between 'performance' in web vitals vs. team health).

    Rules for Intent:

    • At most one intent: line per query document.
    • It cannot appear alone; it must be accompanied by at least one lex:, vec:, or hyde: line.
    • It steers expansion, reranking, and snippet extraction but does not perform a search itself.
    • Can be provided inline in a query document, via the --intent CLI flag, or via the intent parameter in MCP.
    intent: web page load times and Core Web Vitals
    lex: performance
    vec: how to improve performance
  10. Format for QMD Query Expansion

    main

    When generating query expansions for retrieval optimization, use a prefixed line format. Each line must start with a specific prefix indicating its purpose.

    PrefixPurposeRequiredCount
    lex:BM25 keyword variations (shorter, keyword-focused)Yes1-3
    vec:Semantic reformulations (natural language)Yes1-3
    hyde:Hypothetical document passageOptional0-1

    Example Output:

    hyde: Authentication can be configured by setting the AUTH_SECRET environment variable and enabling the auth middleware in your application's config file.
    lex: authentication configuration
    lex: auth settings setup
    vec: how to configure authentication settings
    vec: authentication configuration options
    hyde: Authentication can be configured by setting the AUTH_SECRET environment variable and enabling the auth middleware in your application's config file.
    lex: authentication configuration
    lex: auth settings setup
    vec: how to configure authentication settings
    vec: authentication configuration options
  11. Add Context to improve search relevance

    main

    A key feature of QMD is the ability to add hierarchical context to collections or specific paths. This context is returned alongside matching sub-documents, helping LLMs make better contextual choices when selecting documents.

    Use qmd context add <qmd_uri> "<context_string>" to add context.

    Example:

    qmd context add qmd://notes "Personal notes and ideas"
    qmd context add qmd://notes "Personal notes and ideas"
    qmd context add qmd://meetings "Meeting transcripts and notes"
    qmd context add qmd://docs "Work documentation"
  12. Pick the right search mode: BM25 vs. Structured Query

    main

    QMD provides two primary ways to find information. Choosing the right one depends on what you know about the target content:

    Use this when you know exact words, titles, names, code symbols, or rare phrases. It is faster and often more accurate for verbatim lookups.

    # Search for exact terms
    qmd search "cockpit OKR Goodhart" -n 10
    
    # Search within a specific collection
    qmd search '"AI Before Headcount"' -c concepts -n 5

    2. Structured Query (qmd query)

    Use this when the user describes an idea indirectly or uses different wording than the source. This is the default mode for conceptual recall. Do not rely on the built-in expansion model; author the fields yourself to provide context the model lacks.

    Structured Fields:

    • intent:: States what you are looking for and what to avoid. This is critical for steering ranking away from similar but incorrect concepts.
    • lex:: Your own keyword expansion (exact terms, aliases, titles).
    • vec:: Natural language paraphrases of the idea.
    • hyde:: A description of the hypothetical document or answer that would satisfy the request.
    qmd query $'intent: Find the concept note about metrics as instruments without letting OKRs replace judgment.\nlex: cockpit instruments OKR Goodhart metrics judgment\nvec: data informed not metric driven product judgment\nhyde: A concept note says metrics are useful like cockpit instruments...'