* Add query expansion model finetuning infrastructure - Training scripts for Qwen3-0.6B and 1.7B models - Dataset generation from s-emanuilov/query-expansion - Evaluation scripts comparing finetuned vs baseline models - GRPO RL training script (optional improvement) - Export script for GGUF conversion Results: - 0.6B finetuned: 95% format compliance (lex/vec/hyde) - Baseline: 0% format compliance - Dataset: 5,157 examples on HuggingFace Hub Models available at: - tobil/qmd-query-expansion-0.6B (recommended) - tobil/qmd-query-expansion-train (dataset) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * Fix GRPO training script for TRL API compatibility - Use max_completion_length instead of max_new_tokens - Use processing_class instead of tokenizer - Use args instead of config for GRPOTrainer - Add __name__ attribute to reward function class - Accept **kwargs in reward function for extra TRL args - Add new LoRA adapter after merging SFT weights Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * Update README with final evaluation results - 0.6B SFT: 95% format compliance (best) - 0.6B GRPO: 0% (catastrophic forgetting from RL) - 1.7B v2: training completed, evaluation pending - Added GRPO evaluation results Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * Add comprehensive scoring system for query expansion New scoring criteria (0-100 points): - Format (30): Must have lex: and vec: prefixes - Diversity (30): Multiple types, no echoing query, diverse expansions - Hyde (20): Optional, concise, no newlines, no word repetition - Quality (20): Lex=keywords, vec=natural language See SCORING.md for full documentation. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * Add HuggingFace login and comprehensive scoring to GRPO v2 training - Add explicit HF_TOKEN login before training - Use SCORING.md criteria as RL reward function - Conservative training: LR 1e-6, LoRA rank 4 - Reward scores: good=0.94, bad=0.38 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * Refactor finetune folder: train/rl scripts with YAML configs Major changes: - train.py: Generic SFT training script using YAML config - rl.py: Generic GRPO training script using YAML config - configs/: YAML configs per training run (sft_v4.yaml, grpo_v4.yaml) - dataset/: Data preparation scripts moved here - tui.py: Interactive model testing interface Training results: - SFT v4: 98.8% avg score (all Excellent) - GRPO v4: 0% (failed - model drifted to verbose explanations) Removed per-model scripts (train_0.6B.py, train_1.7B.py, etc) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * Add named entity extraction to GRPO reward function Key changes: - Extract named entities (acronyms, proper nouns, technical terms) - Heavy penalty (-30) when lex queries miss named entities - Penalty (-15) for generic filler phrases like "find information about" - Compound entity detection (TDS motorsports -> both words) - Update GRPO config with KL regularization (beta=0.04) - Lower learning rate (5e-7) and add max_steps (200) Test results: - "who is TDS motorsports" good: 1.00, bad: 0.30 (was 0.75) - "how to use React hooks" good: 0.87, bad: 0.45 (was 0.75) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * Add chat template leakage detection to reward function Zero reward for outputs containing: - <|im_start|>, <|im_end|> tokens - <think>, </think> tags (Qwen3 thinking mode) - Role markers like \nassistant\n, \nuser\n - <|endoftext|> token Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * Strict format validation: every line must be lex:/vec:/hyde: Any line that doesn't start with a valid prefix now returns 0.0 instead of just counting as a penalty. This prevents any prose, explanations, bullet points, or other invalid content. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * Clean up evaluation files - Remove old versioned evaluation files (0.6B, 1.7B, baseline) - Rename evaluation_v4.json -> evaluation_sft.json - Rename evaluation_v4_grpo.json -> evaluation_grpo_failed.json Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * Refactor evals into separate run and score scripts New structure: - evals/run.py: Generate model outputs to JSONL - evals/score.py: Score outputs with detailed breakdown - evals/queries.txt: Test queries (26 total) Features: - Supports both HF Hub and local model paths - Named entity preservation scoring - Chat template leakage detection - Strict format validation (every line must be lex:/vec:/hyde:) - Generic phrase detection Usage: uv run evals/run.py --model tobil/qmd-query-expansion-0.6B-v4 uv run evals/score.py evals/results_*.jsonl Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * Fix GRPO model loading to use SFT base first The GRPO adapter was trained on merged SFT weights, so loading it directly on the base model results in 0% score. Added --sft-model parameter to evals/run.py to load SFT first, then apply GRPO adapter. With correct loading: GRPO scores 89.7% (all 26 queries Excellent). Updated README with correct GRPO score and loading instructions. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * Fix TUI to load GRPO models with SFT base first GRPO adapters were trained on merged SFT weights, so they need SFT loaded and merged first before applying the GRPO adapter. Updated MODELS config to include sft_base path for GRPO models, and load_model() now handles the SFT -> merge -> GRPO flow. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * Update README for unified model repository structure All models (0.6B, 1.7B, 4B) with SFT and GRPO variants now go into a single HuggingFace repo (tobil/qmd-query-expansion) with subfolders for each size and training method. Updated loading examples to show subfolder-based model loading. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * Update README with separate model repos Changed from subfolder approach to separate repos per model since trainer.push_to_hub() doesn't support subfolder argument. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * Add 1.7B and 4B GRPO training and GGUF conversion scripts Training scripts for GRPO fine-tuning: - train_1.7B_grpo.py: GRPO training for Qwen3-1.7B - train_4B_grpo.py: GRPO training for Qwen3-4B GGUF conversion scripts: - convert_1.7B_gguf.py: Merge SFT+GRPO adapters and convert to GGUF - convert_4B_gguf.py: Merge SFT+GRPO adapters and convert to GGUF All scripts use PEP 723 inline dependencies for HuggingFace Jobs. Models published: - tobil/qmd-query-expansion-1.7B-sft - tobil/qmd-query-expansion-1.7B-grpo - tobil/qmd-query-expansion-1.7B-gguf - tobil/qmd-query-expansion-4B-sft - tobil/qmd-query-expansion-4B-grpo - tobil/qmd-query-expansion-4B-gguf Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * Remove beads issue tracking Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * Remove beads reference from CLAUDE.md Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * Fix GRPO reward function to handle think blocks and end tokens - Strip <|im_end|> token from completions (model output includes it) - Change think_penalty to skipped_think bonus (+20 for not using think) - Adjust max_possible to account for bonus (120/140) - Fix typo in chat template artifact check Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * Make TUI model list dynamic from HuggingFace Hub - Fetch available qmd-query-expansion models from tobil/ on Hub - Auto-detect model size (0.6B, 1.7B, 4B) and use correct base model - Group models by type (SFT vs GRPO) in menu - Skip GGUF repos in model listing Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * Fix GRPO training: apply chat template to prompts The SFT model was trained with chat template format but GRPO was passing raw prompts. Now prompts are formatted with tokenizer.apply_chat_template() so the model sees the same format it learned during SFT. Also update extract_query_from_prompt to strip chat template artifacts. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * Finetune 2.0: consolidate and simplify the entire training pipeline Consolidate ~2,800 lines of duplicated code across 12 files into 5 clean, well-documented files targeting Qwen3-1.7B end-to-end. Key changes: - Extract reward function into single source of truth (reward.py) Previously duplicated 3x with divergent bugs across rl.py, train_1.7B_grpo.py, and train_4B_grpo.py - Unify training into one script with sft/grpo subcommands (train.py) Replaces train.py + rl.py + train_1.7B_grpo.py + train_4B_grpo.py - Merge eval generate+score into single eval.py Replaces evals/run.py + evals/score.py - Parameterize GGUF conversion by --size (convert_gguf.py) Replaces convert_1.7B_gguf.py + convert_4B_gguf.py - Fix critical bug: rl.py silently ignored beta/temperature from config, causing the exact catastrophic drift its own comments warned about - Fix prompt consistency: all files use /no_think chat template format - Retarget configs from 0.6B to 1.7B - Comprehensive README documenting the full pipeline Removed: rl.py, train_1.7B_grpo.py, train_4B_grpo.py, convert_1.7B_gguf.py, convert_4B_gguf.py, tui.py, evals/run.py, evals/score.py Net: -3,429 lines, +382 lines Co-Authored-By: Claude (claude-fudge-eap-cc) <noreply@anthropic.com> * Add HF Jobs scripts, temporal query examples, and training results - jobs/sft.py and jobs/grpo.py: self-contained scripts for `hf jobs uv run` (no local GPU needed) - 12 temporal/recency query examples in training data (e.g. "recent news about Shopify" -> lex with years 2025/2026) - 4 temporal test queries in evals/queries.txt - README updated with HF Jobs workflow, training results, and updated file structure - Remove .beads tracking SFT and GRPO successfully trained on A10G via HF Jobs: SFT: eval loss 0.321, token accuracy 92.4% GRPO: mean reward 0.757, 200 steps, KL 0.00048 Co-Authored-By: Claude (claude-fudge-eap-cc) <noreply@anthropic.com> * Deploy fine-tuned GRPO model as default for query expansion Switch from generic Qwen3-1.7B-Q8_0 (~2.2GB) to fine-tuned qmd-query-expansion-1.7B-q4_k_m (~1.1GB). The fine-tuned Q4 scores 91.7% avg with 30/30 Excellent, outperforming the base Q8. - Update default generate model in src/llm.ts - Update README model table, architecture diagram, config block - Add v2 training data, eval scripts, and quantize job - Remove superseded v1 training data (5,742 → 1,000 examples) - Update finetune README with v2 results and file structure Co-Authored-By: Claude (claude-fudge-eap-cc) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com> |
||
|---|---|---|
| finetune | ||
| src | ||
| test | ||
| .gitattributes | ||
| .gitignore | ||
| bun.lock | ||
| CLAUDE.md | ||
| example-index.yml | ||
| flake.lock | ||
| flake.nix | ||
| migrate-schema.ts | ||
| package.json | ||
| qmd | ||
| README.md | ||
| tsconfig.json | ||
QMD - Quick Markdown Search
An on-device search engine for everything you need to remember. Index your markdown notes, meeting transcripts, documentation, and knowledge bases. Search with keywords or natural language. Ideal for your agentic flows.
QMD combines BM25 full-text search, vector semantic search, and LLM re-ranking—all running locally via node-llama-cpp with GGUF models.
Quick Start
# Install globally
bun install -g https://github.com/tobi/qmd
# Create collections for your notes, docs, and meeting transcripts
qmd collection add ~/notes --name notes
qmd collection add ~/Documents/meetings --name meetings
qmd collection add ~/work/docs --name docs
# Add context to help with search results
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"
# Generate embeddings for semantic search
qmd embed
# Search across everything
qmd search "project timeline" # Fast keyword search
qmd vsearch "how to deploy" # Semantic search
qmd query "quarterly planning process" # Hybrid + reranking (best quality)
# Get a specific document
qmd get "meetings/2024-01-15.md"
# Get a document by docid (shown in search results)
qmd get "#abc123"
# Get multiple documents by glob pattern
qmd multi-get "journals/2025-05*.md"
# Search within a specific collection
qmd search "API" -c notes
# Export all matches for an agent
qmd search "API" --all --files --min-score 0.3
Using with AI Agents
QMD's --json and --files output formats are designed for agentic workflows:
# Get structured results for an LLM
qmd search "authentication" --json -n 10
# List all relevant files above a threshold
qmd query "error handling" --all --files --min-score 0.4
# Retrieve full document content
qmd get "docs/api-reference.md" --full
MCP Server
Although the tool works perfectly fine when you just tell your agent to use it on the command line, it also exposes an MCP (Model Context Protocol) server for tighter integration.
Tools exposed:
qmd_search- Fast BM25 keyword search (supports collection filter)qmd_vsearch- Semantic vector search (supports collection filter)qmd_query- Hybrid search with reranking (supports collection filter)qmd_get- Retrieve document by path or docid (with fuzzy matching suggestions)qmd_multi_get- Retrieve multiple documents by glob pattern, list, or docidsqmd_status- Index health and collection info
Claude Desktop configuration (~/Library/Application Support/Claude/claude_desktop_config.json):
{
"mcpServers": {
"qmd": {
"command": "qmd",
"args": ["mcp"]
}
}
}
Claude Code configuration (~/.claude/settings.json):
{
"mcpServers": {
"qmd": {
"command": "qmd",
"args": ["mcp"]
}
}
}
Architecture
┌─────────────────────────────────────────────────────────────────────────────┐
│ QMD Hybrid Search Pipeline │
└─────────────────────────────────────────────────────────────────────────────┘
┌─────────────────┐
│ User Query │
└────────┬────────┘
│
┌──────────────┴──────────────┐
▼ ▼
┌────────────────┐ ┌────────────────┐
│ Query Expansion│ │ Original Query│
│ (fine-tuned) │ │ (×2 weight) │
└───────┬────────┘ └───────┬────────┘
│ │
│ 2 alternative queries │
└──────────────┬──────────────┘
│
┌───────────────────────┼───────────────────────┐
▼ ▼ ▼
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Original Query │ │ Expanded Query 1│ │ Expanded Query 2│
└────────┬────────┘ └────────┬────────┘ └────────┬────────┘
│ │ │
┌───────┴───────┐ ┌───────┴───────┐ ┌───────┴───────┐
▼ ▼ ▼ ▼ ▼ ▼
┌───────┐ ┌───────┐ ┌───────┐ ┌───────┐ ┌───────┐ ┌───────┐
│ BM25 │ │Vector │ │ BM25 │ │Vector │ │ BM25 │ │Vector │
│(FTS5) │ │Search │ │(FTS5) │ │Search │ │(FTS5) │ │Search │
└───┬───┘ └───┬───┘ └───┬───┘ └───┬───┘ └───┬───┘ └───┬───┘
│ │ │ │ │ │
└───────┬───────┘ └──────┬──────┘ └──────┬──────┘
│ │ │
└────────────────────────┼───────────────────────┘
│
▼
┌───────────────────────┐
│ RRF Fusion + Bonus │
│ Original query: ×2 │
│ Top-rank bonus: +0.05│
│ Top 30 Kept │
└───────────┬───────────┘
│
▼
┌───────────────────────┐
│ LLM Re-ranking │
│ (qwen3-reranker) │
│ Yes/No + logprobs │
└───────────┬───────────┘
│
▼
┌───────────────────────┐
│ Position-Aware Blend │
│ Top 1-3: 75% RRF │
│ Top 4-10: 60% RRF │
│ Top 11+: 40% RRF │
└───────────────────────┘
Score Normalization & Fusion
Search Backends
| Backend | Raw Score | Conversion | Range |
|---|---|---|---|
| FTS (BM25) | SQLite FTS5 BM25 | Math.abs(score) |
0 to ~25+ |
| Vector | Cosine distance | 1 / (1 + distance) |
0.0 to 1.0 |
| Reranker | LLM 0-10 rating | score / 10 |
0.0 to 1.0 |
Fusion Strategy
The query command uses Reciprocal Rank Fusion (RRF) with position-aware blending:
- Query Expansion: Original query (×2 for weighting) + 1 LLM variation
- Parallel Retrieval: Each query searches both FTS and vector indexes
- RRF Fusion: Combine all result lists using
score = Σ(1/(k+rank+1))where k=60 - Top-Rank Bonus: Documents ranking #1 in any list get +0.05, #2-3 get +0.02
- Top-K Selection: Take top 30 candidates for reranking
- Re-ranking: LLM scores each document (yes/no with logprobs confidence)
- Position-Aware Blending:
- RRF rank 1-3: 75% retrieval, 25% reranker (preserves exact matches)
- RRF rank 4-10: 60% retrieval, 40% reranker
- RRF rank 11+: 40% retrieval, 60% reranker (trust reranker more)
Why this approach: Pure RRF can dilute exact matches when expanded queries don't match. The top-rank bonus preserves documents that score #1 for the original query. Position-aware blending prevents the reranker from destroying high-confidence retrieval results.
Score Interpretation
| Score | Meaning |
|---|---|
| 0.8 - 1.0 | Highly relevant |
| 0.5 - 0.8 | Moderately relevant |
| 0.2 - 0.5 | Somewhat relevant |
| 0.0 - 0.2 | Low relevance |
Requirements
System Requirements
- Bun >= 1.0.0
- macOS: Homebrew SQLite (for extension support)
brew install sqlite
GGUF Models (via node-llama-cpp)
QMD uses three local GGUF models (auto-downloaded on first use):
| Model | Purpose | Size |
|---|---|---|
embeddinggemma-300M-Q8_0 |
Vector embeddings | ~300MB |
qwen3-reranker-0.6b-q8_0 |
Re-ranking | ~640MB |
qmd-query-expansion-1.7B-q4_k_m |
Query expansion (fine-tuned) | ~1.1GB |
Models are downloaded from HuggingFace and cached in ~/.cache/qmd/models/.
Installation
bun install
Usage
Collection Management
# Create a collection from current directory
qmd collection add . --name myproject
# Create a collection with explicit path and custom glob mask
qmd collection add ~/Documents/notes --name notes --mask "**/*.md"
# List all collections
qmd collection list
# Remove a collection
qmd collection remove myproject
# Rename a collection
qmd collection rename myproject my-project
# List files in a collection
qmd ls notes
qmd ls notes/subfolder
Generate Vector Embeddings
# Embed all indexed documents (800 tokens/chunk, 15% overlap)
qmd embed
# Force re-embed everything
qmd embed -f
Context Management
Context adds descriptive metadata to collections and paths, helping search understand your content.
# Add context to a collection (using qmd:// virtual paths)
qmd context add qmd://notes "Personal notes and ideas"
qmd context add qmd://docs/api "API documentation"
# Add context from within a collection directory
cd ~/notes && qmd context add "Personal notes and ideas"
cd ~/notes/work && qmd context add "Work-related notes"
# Add global context (applies to all collections)
qmd context add / "Knowledge base for my projects"
# List all contexts
qmd context list
# Remove context
qmd context rm qmd://notes/old
Search Commands
┌──────────────────────────────────────────────────────────────────┐
│ Search Modes │
├──────────┬───────────────────────────────────────────────────────┤
│ search │ BM25 full-text search only │
│ vsearch │ Vector semantic search only │
│ query │ Hybrid: FTS + Vector + Query Expansion + Re-ranking │
└──────────┴───────────────────────────────────────────────────────┘
# Full-text search (fast, keyword-based)
qmd search "authentication flow"
# Vector search (semantic similarity)
qmd vsearch "how to login"
# Hybrid search with re-ranking (best quality)
qmd query "user authentication"
Options
# Search options
-n <num> # Number of results (default: 5, or 20 for --files/--json)
-c, --collection # Restrict search to a specific collection
--all # Return all matches (use with --min-score to filter)
--min-score <num> # Minimum score threshold (default: 0)
--full # Show full document content
--line-numbers # Add line numbers to output
--index <name> # Use named index
# Output formats (for search and multi-get)
--files # Output: docid,score,filepath,context
--json # JSON output with snippets
--csv # CSV output
--md # Markdown output
--xml # XML output
# Get options
qmd get <file>[:line] # Get document, optionally starting at line
-l <num> # Maximum lines to return
--from <num> # Start from line number
# Multi-get options
-l <num> # Maximum lines per file
--max-bytes <num> # Skip files larger than N bytes (default: 10KB)
Output Format
Default output is colorized CLI format (respects NO_COLOR env):
docs/guide.md:42 #a1b2c3
Title: Software Craftsmanship
Context: Work documentation
Score: 93%
This section covers the **craftsmanship** of building
quality software with attention to detail.
See also: engineering principles
notes/meeting.md:15 #d4e5f6
Title: Q4 Planning
Context: Personal notes and ideas
Score: 67%
Discussion about code quality and craftsmanship
in the development process.
- Path: Collection-relative path (e.g.,
docs/guide.md) - Docid: Short hash identifier (e.g.,
#a1b2c3) - use withqmd get #a1b2c3 - Title: Extracted from document (first heading or filename)
- Context: Path context if configured via
qmd context add - Score: Color-coded (green >70%, yellow >40%, dim otherwise)
- Snippet: Context around match with query terms highlighted
Examples
# Get 10 results with minimum score 0.3
qmd query -n 10 --min-score 0.3 "API design patterns"
# Output as markdown for LLM context
qmd search --md --full "error handling"
# JSON output for scripting
qmd query --json "quarterly reports"
# Use separate index for different knowledge base
qmd --index work search "quarterly reports"
Index Maintenance
# Show index status and collections with contexts
qmd status
# Re-index all collections
qmd update
# Re-index with git pull first (for remote repos)
qmd update --pull
# Get document by filepath (with fuzzy matching suggestions)
qmd get notes/meeting.md
# Get document by docid (from search results)
qmd get "#abc123"
# Get document starting at line 50, max 100 lines
qmd get notes/meeting.md:50 -l 100
# Get multiple documents by glob pattern
qmd multi-get "journals/2025-05*.md"
# Get multiple documents by comma-separated list (supports docids)
qmd multi-get "doc1.md, doc2.md, #abc123"
# Limit multi-get to files under 20KB
qmd multi-get "docs/*.md" --max-bytes 20480
# Output multi-get as JSON for agent processing
qmd multi-get "docs/*.md" --json
# Clean up cache and orphaned data
qmd cleanup
Data Storage
Index stored in: ~/.cache/qmd/index.sqlite
Schema
collections -- Indexed directories with name and glob patterns
path_contexts -- Context descriptions by virtual path (qmd://...)
documents -- Markdown content with metadata and docid (6-char hash)
documents_fts -- FTS5 full-text index
content_vectors -- Embedding chunks (hash, seq, pos, 800 tokens each)
vectors_vec -- sqlite-vec vector index (hash_seq key)
llm_cache -- Cached LLM responses (query expansion, rerank scores)
Environment Variables
| Variable | Default | Description |
|---|---|---|
XDG_CACHE_HOME |
~/.cache |
Cache directory location |
How It Works
Indexing Flow
Collection ──► Glob Pattern ──► Markdown Files ──► Parse Title ──► Hash Content
│ │ │
│ │ ▼
│ │ Generate docid
│ │ (6-char hash)
│ │ │
└──────────────────────────────────────────────────►└──► Store in SQLite
│
▼
FTS5 Index
Embedding Flow
Documents are chunked into 800-token pieces with 15% overlap:
Document ──► Chunk (800 tokens) ──► Format each chunk ──► node-llama-cpp ──► Store Vectors
│ "title | text" embedBatch()
│
└─► Chunks stored with:
- hash: document hash
- seq: chunk sequence (0, 1, 2...)
- pos: character position in original
Query Flow (Hybrid)
Query ──► LLM Expansion ──► [Original, Variant 1, Variant 2]
│
┌─────────┴─────────┐
▼ ▼
For each query: FTS (BM25)
│ │
▼ ▼
Vector Search Ranked List
│
▼
Ranked List
│
└─────────┬─────────┘
▼
RRF Fusion (k=60)
Original query ×2 weight
Top-rank bonus: +0.05/#1, +0.02/#2-3
│
▼
Top 30 candidates
│
▼
LLM Re-ranking
(yes/no + logprob confidence)
│
▼
Position-Aware Blend
Rank 1-3: 75% RRF / 25% reranker
Rank 4-10: 60% RRF / 40% reranker
Rank 11+: 40% RRF / 60% reranker
│
▼
Final Results
Model Configuration
Models are configured in src/llm.ts as HuggingFace URIs:
const DEFAULT_EMBED_MODEL = "hf:ggml-org/embeddinggemma-300M-GGUF/embeddinggemma-300M-Q8_0.gguf";
const DEFAULT_RERANK_MODEL = "hf:ggml-org/Qwen3-Reranker-0.6B-Q8_0-GGUF/qwen3-reranker-0.6b-q8_0.gguf";
const DEFAULT_GENERATE_MODEL = "hf:tobil/qmd-query-expansion-1.7B-gguf/qmd-query-expansion-1.7B-q4_k_m.gguf";
EmbeddingGemma Prompt Format
// For queries
"task: search result | query: {query}"
// For documents
"title: {title} | text: {content}"
Qwen3-Reranker
Uses node-llama-cpp's createRankingContext() and rankAndSort() API for cross-encoder reranking. Returns documents sorted by relevance score (0.0 - 1.0).
Qwen3 (Query Expansion)
Used for generating query variations via LlamaChatSession.
License
MIT