Commit Graph

131 Commits

Author SHA1 Message Date
Tobi Lutke
857a85ab58
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>
2026-01-25 00:20:39 -05:00
Tobi Lutke
dc8f5a2335
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>
2026-01-25 00:08:08 -05:00
Tobi Lutke
2ad507a86e
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>
2026-01-25 00:07:12 -05:00
Tobi Lutke
6062dc769f
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>
2026-01-25 00:05:40 -05:00
Tobi Lutke
32706a720f
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>
2026-01-24 20:26:46 -05:00
Tobi Lutke
d32e13c172
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>
2026-01-24 17:37:49 -05:00
Tobi Lutke
c35dbd6cbd
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>
2026-01-24 11:00:55 -05:00
Tobi Lutke
994a094546
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>
2026-01-24 10:45:48 -05:00
Tobi Lutke
0353994e7d
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>
2026-01-23 22:25:09 -05:00
Tobi Lutke
7cca164dd9
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>
2026-01-23 19:47:06 -05:00
komsit37
88f78314bb
Fix sqlite-vec loading with BREW_PREFIX (#42) 2026-01-22 17:00:48 -05:00
Tobias Lütke
3c7dfad1b6
Make docid lookup more lenient with quotes support (#39)
- Add normalizeDocid() to strip quotes and # prefix
- Add isDocid() to detect docid patterns including quoted forms
- Update findDocumentByDocid, findDocument, getDocument to use new helpers
- All formats now work: #abc123, abc123, "#abc123", "abc123", '#abc123', 'abc123'
- Add 18 unit tests for normalizeDocid and isDocid

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-20 10:33:42 -04:00
Joshua Lelon Mitchell
fbd7fe8c8e
Fix docid lookup in qmd get command (#36)
The `qmd get` command was documented to support docid lookups
(e.g., `qmd get "#abc123"` or `qmd get abc123`), but the
implementation in getDocument() never actually handled docids.

The findDocumentByDocid() function existed in store.ts and worked
correctly, but getDocument() in qmd.ts reimplemented document
lookup without calling it.

This adds docid detection at the start of getDocument() to resolve
docids to virtual paths before other path handling.

Co-authored-by: Joshua Mitchell <jlelonmitchell@gmail.com >
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-20 10:28:34 -04:00
Tobias Lütke
5b1671d2f6
Merge pull request #38 from odysseus0/fix/readme-model-sizes
docs: fix query expansion model size (Qwen3-1.7B, not 0.6B)
2026-01-20 10:25:28 -04:00
George Zhang
c8f72de12e
docs: fix query expansion model size (Qwen3-1.7B, not 0.6B)
The code uses Qwen3-1.7B (~2.2GB) for query expansion, but the README
documented Qwen3-0.6B (~640MB) in three places:
- Model requirements table
- Architecture diagram
- Code configuration sample

This caused confusion when users saw a 2GB+ download instead of 640MB.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-20 04:09:27 +08:00
Tobi Lutke
7817dc11a4
Show embedding notice only once at end of qmd update
Previously, the "Run 'qmd embed' to update embeddings" message was
printed after each collection was indexed, repeating the same global
count multiple times. Now it's shown once at the end of the update.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-19 11:32:59 -04:00
Tobias Lütke
6fbad4e9a6
Merge pull request #15 from gavrix/main 2026-01-18 10:34:44 -05:00
Tobias Lütke
77e1f82cd9
Merge pull request #23 from mbrendan/fix-vsearch-hang 2026-01-18 10:34:33 -05:00
Tobias Lütke
2bde6804fb
Merge pull request #29 from mostlydev/fix-mcp-server-exit 2026-01-18 10:34:13 -05:00
Wojtek
a0ca60bc6f Fix MCP server exiting immediately after startup 2026-01-13 21:41:56 -05:00
Brendan McCord
aea494bb24 Fix regression test to use proper test helpers
Use insertTestDocument and createTestCollection helpers
to match existing test patterns.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-11 22:17:05 -06:00
Brendan McCord
01d74727f7 Add regression test and explanatory comments
- Add detailed comments explaining why two-step query is necessary
- Add regression test for sqlite-vec JOIN hang bug
- Link to PR in comments for future reference

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-11 22:12:56 -06:00
Brendan McCord
216793380a Fix vsearch/query hang caused by sqlite-vec JOIN incompatibility
sqlite-vec virtual tables don't work correctly with JOINs in the same
query - they cause the query to hang indefinitely.

Changes:
- searchVec: Rewrite to use two-step approach
  1. Query vectors_vec table alone (no JOINs)
  2. Look up document info separately using result hash_seqs
- vsearch: Change from Promise.all to sequential for loop
  (node-llama-cpp embedding context doesn't handle concurrent calls)

This fixes vsearch and hybrid query commands that were hanging at
"Searching N vector queries..."

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-11 22:08:32 -06:00
Sergey Gavrilyuk
bebee61bec Fix case sensitivity in Qwen3-1.7B model filename
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-09 21:46:24 -07:00
Tobi Lutke
5fa66fd228
Skip empty files during indexing
Empty files have nothing useful to index or embed. Previously they would
be indexed with an empty body, causing confusing "1 need embedding" status
messages that could never be resolved.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-09 17:44:52 -05:00
Tobi Lutke
5b636b2e4c
Merge pull request #8 from burke/native-realpath
Use native realpathSync instead of spawning subprocess per file
(15.6s -> 1.0s for large collections)
2026-01-09 17:33:12 -05:00
Tobias Lütke
7cec9b8a3a
Merge pull request #7 from burke/main
Fix flake.nix to use correct src/qmd.ts path
2026-01-09 17:31:40 -05:00
Tobias Lütke
fe0fd0859b
Merge pull request #9 from burke/single-tokenize
Optimize chunking to tokenize once per document
2026-01-09 17:24:54 -05:00
Tobi Lutke
4d21c5ab2b
Fix collection filter SQL and support non-ASCII filenames
Issue #11: Collection filter (-c) SQL error
- Fixed searchVec to properly parameterize collection filter
- Changed collectionId check from !== undefined to truthy
- Added test for searchVec with collection filter

Issue #10: Non-ASCII filename support
- Updated handelize() to use Unicode property escapes (\p{L}\p{N})
- Now supports Cyrillic, Japanese, and other Unicode filenames
- Updated tests to verify Unicode filename handling

Also:
- Fixed expandQuery to filter out lex entries when includeLexical=false
- Updated expandQuery tests to match actual behavior

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-08 23:40:36 -05:00
Tobi Lutke
0dfd7a4686
Fix query hang, SQL errors, and missing docid in search results
- Fix SQL syntax error when collectionId is empty string (searchFTS, searchVec)
- Add 1-second timeout to llama.dispose() to prevent indefinite hang
- Add process.exit(0) after cleanup for clean CLI exit
- Include hash/docid in search results mapping
- Update query expansion to use structured Queryable types
- Switch to Qwen3-1.7B model for better query expansion
- Improve bun discovery in qmd wrapper script

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-08 23:24:00 -05:00
Burke Libbey
3974c231dc Optimize chunking to tokenize once per document
Instead of calling countTokens() multiple times during binary search
for chunk boundaries, tokenize the document once upfront and slice
token arrays. This reduces tokenizer calls from O(chunks × iterations)
to O(1) per document.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-04 11:35:52 -05:00
Burke Libbey
cfc5ebd553 Use native realpathSync instead of spawning subprocess per file
The previous implementation spawned a subprocess for every file during
indexing (e.g., 4500 subprocess spawns for a large collection). This
caused resource exhaustion and random hangs. Using Node's native
realpathSync is orders of magnitude faster.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-04 10:53:06 -05:00
Burke Libbey
2410c8ef00 Fix flake.nix to use correct src/qmd.ts path
The installPhase was trying to copy qmd.ts from root, but it's at
src/qmd.ts. Updated to copy the src directory and point the wrapper
to the correct path.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-03 18:18:08 -05:00
Tobi Lutke
431f6e505b
Fix qmd embed crash and resolve all TypeScript errors
- Fix ReferenceError in vectorIndex(): firstResult was used but never
  defined. Added code to embed first chunk to get embedding dimensions.

- Fix 87 TypeScript errors across codebase:
  - formatter.ts: Define MultiGetFile type locally (was missing from store.ts)
  - collections.ts: Add non-null assertion for array access
  - mcp.ts: Fix StatusResult type to match store.ts CollectionInfo,
    add list parameter to ResourceTemplate, fix undefined checks
  - qmd.ts: Fix boolean/string type coercions, undefined array access
  - llm.test.ts: Update expandQuery tests for Queryable[] return type,
    fix array access assertions
  - store.test.ts: Add non-null assertions for array access in tests
  - eval-harness.ts: Fix array access assertion
2025-12-31 13:32:30 -04:00
Tobi Lutke
c85889df12
fixes 2025-12-21 14:50:17 -04:00
Tobi Lutke
c9ac3c1463
Use default createContext() options for better VRAM management
Let node-llama-cpp handle context size and sequences automatically.
The mutex still serializes generation calls for safety.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-21 13:54:30 -04:00
Tobi Lutke
f39db3a593
Fix VRAM and sequence exhaustion issues in generation
- Reduce context sequences from 4 to 1 to minimize VRAM usage
  when multiple models (embed, generate, rerank) are loaded
- Add mutex to serialize generation calls to prevent "No sequences left"
  error when concurrent requests occur with single sequence

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-21 13:51:23 -04:00
Tobi Lutke
10c5ec016f
Simplify disposal: let llama cascade to children, remove test dispose calls
- dispose() now just calls llama.dispose() which cascades to models/contexts
  per node-llama-cpp lifecycle docs
- Remove disposeDefaultLlamaCpp calls from tests - they don't help with
  the Metal cleanup crash
- Use singleton getDefaultLlamaCpp() in llm tests for consistency

The Metal backend crash at process exit is a known llama.cpp issue:
https://github.com/ggml-org/llama.cpp/pull/17869

All tests pass - the abort happens after test completion.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-21 13:47:55 -04:00
Tobi Lutke
edc294db4f
Don't dispose llama in tests - let process exit handle cleanup
The Metal backend crash happens regardless of whether we dispose or not.
It's a known llama.cpp issue during process exit static destructor cleanup:
https://github.com/ggml-org/llama.cpp/pull/17869

All 297 tests pass - the abort happens after tests complete.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-21 13:42:38 -04:00
Tobi Lutke
e8f4dce0b7
Fix Metal backend crash by properly disposing llama resources
The Metal crash was caused by not disposing resources in the right order
at the right time. The fix:

1. Restore proper dispose() that disposes contexts → models → llama in order
2. Move disposeDefaultLlamaCpp() to global afterAll (after all tests complete)
3. Keep disposed flag to prevent double-dispose

The issue was that disposing per-suite broke tests that share llama,
and not disposing at all left orphaned Metal resources at process exit.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-21 13:38:08 -04:00
Tobi Lutke
c26e8ea3ba
Add fusion difficulty queries to test RRF's advantage
Add 6 "fusion" queries designed to test cases where neither BM25 nor
vector search alone succeeds, but combining them with RRF does:

- "how much runway before running out of money" → fundraising
- "datacenter replication sync strategy" → distributed-systems
- "splitting data for training and testing" → machine-learning
- "JSON response codes error messages" → api-design
- "video calls camera async messaging" → remote-work
- "CI/CD pipeline testing coverage" → product-launch

The fusion test verifies:
1. Hybrid achieves ≥50% Hit@3 on these multi-signal queries
2. Hybrid outperforms or matches the best individual method

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-21 13:36:04 -04:00
Tobi Lutke
4131c827de
Make LlamaCpp dispose idempotent and avoid Metal backend crash
- Add disposed flag to prevent double-dispose
- Don't explicitly dispose llama resources in dispose() - just clear refs
- Let process exit handle Metal cleanup naturally
- Remove disposeDefaultLlamaCpp call from eval tests

Note: llama.cpp Metal backend still crashes at process exit due to
ggml-metal cleanup issues. This is a known upstream issue:
https://github.com/ggml-org/llama.cpp/pull/17869

All tests pass (12/12), the abort happens after test completion.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-21 13:34:26 -04:00
Tobi Lutke
d28a89eb5c
Fix eval tests: add missing insertEmbedding params and conditional thresholds
- Pass model and embeddedAt parameters to insertEmbedding
- Convert embedding to Float32Array for sqlite-vec compatibility
- Make hybrid search thresholds conditional on vector availability
  (falls back to BM25-only thresholds when no embeddings exist)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-21 13:29:43 -04:00
Tobi Lutke
56dea7d6ce
Add vector and hybrid (RRF) search evaluation tests
Three test suites with different thresholds:
- BM25: easy≥80%, medium≥15%, hard≥15%, overall≥40%
- Vector: easy≥60%, medium≥40%, hard≥30%, overall≥50%
- Hybrid (RRF): easy≥80%, medium≥50%, hard≥35%, overall≥60%

Hybrid should outperform individual methods on semantic queries.
Vector/hybrid tests have 60-120s timeouts for embedding generation.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-21 13:22:43 -04:00
Tobi Lutke
5343e0e51d
Add production mode guard to prevent tests from using global index
- Add enableProductionMode() that qmd.ts calls at startup
- getDefaultDbPath() throws in test mode unless INDEX_PATH is set
- Update store.test.ts to expect throws for default path tests
- Add eval.test.ts with 18 BM25 quality tests (easy/medium/hard)
- Tests now cannot accidentally write to ~/.cache/qmd/

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-21 13:20:32 -04:00
Tobi Lutke
945d4b4572
Add 6 synthetic evaluation documents
Topics covered:
- API design principles
- Startup fundraising memo
- Distributed systems overview
- Product launch retrospective
- Machine learning primer
- Remote work policy

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-21 13:10:35 -04:00
Tobi Lutke
7828566333
Add evaluation harness with synthetic test documents
- 6 public-style documents covering diverse topics
- 18 test queries: 6 easy, 6 medium, 6 hard
- Easy: exact keyword matches
- Medium: semantic/conceptual queries
- Hard: partial recall, indirect references
- Measures Hit@1, Hit@3, Hit@5 by difficulty
- Tests both search (BM25) and query (hybrid) modes

Run: bun test/eval-harness.ts

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-21 13:10:24 -04:00
Tobi Lutke
a0bceef36a
Add conditional query expansion based on BM25 signal strength
Skip expensive LLM query expansion when initial BM25 search has
strong signals (top result score > 0.7). This saves LLM calls and
latency for queries that already match well.

- Run initial BM25 search first
- Check if top result has strong score
- Skip expansion if signal is strong
- Reuse initial search results for retrieval

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-21 13:05:35 -04:00
Tobi Lutke
fd24df81c9
Rerank multiple chunks per document with score aggregation
Instead of reranking just 1 keyword-matched chunk per doc, now:
- Select top 3 chunks per document (by keyword score)
- Rerank all selected chunks
- Aggregate scores using top-2 average (rewards consistency)
- Use best-scoring chunk for snippet display

This improves ranking for long documents where the keyword-matched
chunk isn't always the most relevant to the query.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-21 13:04:02 -04:00
Tobi Lutke
7847631224
Fix duplicate files in query results with explicit dedup
Added safety net deduplication after reranking to prevent the same
file appearing multiple times in results. Uses Set to keep only
first (highest-scored) occurrence of each file.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-20 18:18:48 -04:00