ragx-cli 0.5.1__tar.gz → 0.7.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (108) hide show
  1. {ragx_cli-0.5.1 → ragx_cli-0.7.0}/CHANGELOG.md +50 -0
  2. {ragx_cli-0.5.1 → ragx_cli-0.7.0}/CLAUDE.md +16 -4
  3. {ragx_cli-0.5.1 → ragx_cli-0.7.0}/PKG-INFO +79 -14
  4. {ragx_cli-0.5.1 → ragx_cli-0.7.0}/README.md +77 -12
  5. ragx_cli-0.7.0/docs/proposal-models-command-interactive-flow.md +131 -0
  6. {ragx_cli-0.5.1 → ragx_cli-0.7.0}/pyproject.toml +2 -2
  7. {ragx_cli-0.5.1 → ragx_cli-0.7.0}/src/ragx/cli/app.py +3 -1
  8. ragx_cli-0.7.0/src/ragx/cli/doctor_cmd.py +62 -0
  9. {ragx_cli-0.5.1 → ragx_cli-0.7.0}/src/ragx/cli/models_cmd.py +72 -40
  10. ragx_cli-0.7.0/src/ragx/cli/models_ui.py +117 -0
  11. {ragx_cli-0.5.1 → ragx_cli-0.7.0}/src/ragx/cli/pipeline.py +14 -1
  12. {ragx_cli-0.5.1 → ragx_cli-0.7.0}/src/ragx/core/catalog.py +54 -12
  13. {ragx_cli-0.5.1 → ragx_cli-0.7.0}/src/ragx/core/config.py +17 -0
  14. ragx_cli-0.7.0/src/ragx/core/doctor.py +204 -0
  15. {ragx_cli-0.5.1 → ragx_cli-0.7.0}/src/ragx/core/lmstudio.py +49 -13
  16. {ragx_cli-0.5.1 → ragx_cli-0.7.0}/src/ragx/providers/llama_process.py +12 -1
  17. ragx_cli-0.7.0/src/ragx/providers/lmstudio_process.py +120 -0
  18. {ragx_cli-0.5.1 → ragx_cli-0.7.0}/src/ragx/providers/registry.py +11 -17
  19. ragx_cli-0.7.0/tests/conftest.py +24 -0
  20. ragx_cli-0.7.0/tests/test_doctor.py +231 -0
  21. {ragx_cli-0.5.1 → ragx_cli-0.7.0}/tests/test_llama_server.py +43 -0
  22. ragx_cli-0.7.0/tests/test_lmstudio_autostart.py +142 -0
  23. {ragx_cli-0.5.1 → ragx_cli-0.7.0}/tests/test_models.py +127 -4
  24. ragx_cli-0.5.1/tests/conftest.py +0 -13
  25. {ragx_cli-0.5.1 → ragx_cli-0.7.0}/.agents/skills/zvec/SKILL.md +0 -0
  26. {ragx_cli-0.5.1 → ragx_cli-0.7.0}/.agents/skills/zvec/api-cheatsheet.md +0 -0
  27. {ragx_cli-0.5.1 → ragx_cli-0.7.0}/.agents/skills/zvec/collection-management/python.md +0 -0
  28. {ragx_cli-0.5.1 → ragx_cli-0.7.0}/.agents/skills/zvec/collection-management/typescript.md +0 -0
  29. {ragx_cli-0.5.1 → ragx_cli-0.7.0}/.agents/skills/zvec/configuration.md +0 -0
  30. {ragx_cli-0.5.1 → ragx_cli-0.7.0}/.agents/skills/zvec/data-model.md +0 -0
  31. {ragx_cli-0.5.1 → ragx_cli-0.7.0}/.agents/skills/zvec/data-operations/python.md +0 -0
  32. {ragx_cli-0.5.1 → ragx_cli-0.7.0}/.agents/skills/zvec/data-operations/typescript.md +0 -0
  33. {ragx_cli-0.5.1 → ragx_cli-0.7.0}/.agents/skills/zvec/embedding.md +0 -0
  34. {ragx_cli-0.5.1 → ragx_cli-0.7.0}/.agents/skills/zvec/hybrid-search/python.md +0 -0
  35. {ragx_cli-0.5.1 → ragx_cli-0.7.0}/.agents/skills/zvec/hybrid-search/typescript.md +0 -0
  36. {ragx_cli-0.5.1 → ragx_cli-0.7.0}/.agents/skills/zvec/multimodal-search/python.md +0 -0
  37. {ragx_cli-0.5.1 → ragx_cli-0.7.0}/.agents/skills/zvec/multimodal-search/typescript.md +0 -0
  38. {ragx_cli-0.5.1 → ragx_cli-0.7.0}/.agents/skills/zvec/quick-start/python.md +0 -0
  39. {ragx_cli-0.5.1 → ragx_cli-0.7.0}/.agents/skills/zvec/quick-start/typescript.md +0 -0
  40. {ragx_cli-0.5.1 → ragx_cli-0.7.0}/.agents/skills/zvec/rag-system/python.md +0 -0
  41. {ragx_cli-0.5.1 → ragx_cli-0.7.0}/.agents/skills/zvec/rag-system/typescript.md +0 -0
  42. {ragx_cli-0.5.1 → ragx_cli-0.7.0}/.agents/skills/zvec/reranker.md +0 -0
  43. {ragx_cli-0.5.1 → ragx_cli-0.7.0}/.agents/skills/zvec/troubleshooting.md +0 -0
  44. {ragx_cli-0.5.1 → ragx_cli-0.7.0}/.agents/skills/zvec/vector-search/python.md +0 -0
  45. {ragx_cli-0.5.1 → ragx_cli-0.7.0}/.agents/skills/zvec/vector-search/typescript.md +0 -0
  46. {ragx_cli-0.5.1 → ragx_cli-0.7.0}/.github/workflows/publish.yml +0 -0
  47. {ragx_cli-0.5.1 → ragx_cli-0.7.0}/.gitignore +0 -0
  48. {ragx_cli-0.5.1 → ragx_cli-0.7.0}/AGENTS.md +0 -0
  49. {ragx_cli-0.5.1 → ragx_cli-0.7.0}/CONTRACTS-PHASE23.md +0 -0
  50. {ragx_cli-0.5.1 → ragx_cli-0.7.0}/CONTRACTS.md +0 -0
  51. {ragx_cli-0.5.1 → ragx_cli-0.7.0}/LICENSE +0 -0
  52. {ragx_cli-0.5.1 → ragx_cli-0.7.0}/docs/feature-temporal-weighting.md +0 -0
  53. {ragx_cli-0.5.1 → ragx_cli-0.7.0}/ragx-cli-plan.md +0 -0
  54. {ragx_cli-0.5.1 → ragx_cli-0.7.0}/research/bge-m3-colbert-multi-vector-late-interaction-for-ragx-rerank-alternative-and-token-level-graph-edges.md +0 -0
  55. {ragx_cli-0.5.1 → ragx_cli-0.7.0}/research/bge-m3-dense-embeddings-as-ragx-provider-multilingual-quality-and-threshold-calibration.md +0 -0
  56. {ragx_cli-0.5.1 → ragx_cli-0.7.0}/research/bge-m3-dense-q8-vs-nomic-q4-benchmark-2026-07-12-worktree-eval-results.md +0 -0
  57. {ragx_cli-0.5.1 → ragx_cli-0.7.0}/research/bge-m3-sparse-lexical-weights-hybrid-retrieval-leg-for-ragx-vs-sqlite-fts5-bm25.md +0 -0
  58. {ragx_cli-0.5.1 → ragx_cli-0.7.0}/research/bge-m3-subchunk-edge-ablation-2026-07-13-ties-again-and-beta-zero-shortlist-mechanism.md +0 -0
  59. {ragx_cli-0.5.1 → ragx_cli-0.7.0}/research/fine-grained-sub-chunk-edges-with-coarse-chunk-nodes-multi-granularity-graph-rag-literature-validation.md +0 -0
  60. {ragx_cli-0.5.1 → ragx_cli-0.7.0}/research/lm-studio-model-download-api-and-curated-embedding-reranker-catalog-for-ragx-models-command.md +0 -0
  61. {ragx_cli-0.5.1 → ragx_cli-0.7.0}/research/subchunk-edge-ablation-2026-07-12-two-round-experiment-protocol-results-and-verdict.md +0 -0
  62. {ragx_cli-0.5.1 → ragx_cli-0.7.0}/skills/ragx-cli/SKILL.md +0 -0
  63. {ragx_cli-0.5.1 → ragx_cli-0.7.0}/skills-lock.json +0 -0
  64. {ragx_cli-0.5.1 → ragx_cli-0.7.0}/src/ragx/__init__.py +0 -0
  65. {ragx_cli-0.5.1 → ragx_cli-0.7.0}/src/ragx/cli/__init__.py +0 -0
  66. {ragx_cli-0.5.1 → ragx_cli-0.7.0}/src/ragx/cli/detect.py +0 -0
  67. {ragx_cli-0.5.1 → ragx_cli-0.7.0}/src/ragx/cli/eval_cmd.py +0 -0
  68. {ragx_cli-0.5.1 → ragx_cli-0.7.0}/src/ragx/cli/init_prompt.py +0 -0
  69. {ragx_cli-0.5.1 → ragx_cli-0.7.0}/src/ragx/cli/inspect_cmd.py +0 -0
  70. {ragx_cli-0.5.1 → ragx_cli-0.7.0}/src/ragx/cli/output.py +0 -0
  71. {ragx_cli-0.5.1 → ragx_cli-0.7.0}/src/ragx/core/__init__.py +0 -0
  72. {ragx_cli-0.5.1 → ragx_cli-0.7.0}/src/ragx/core/chunking.py +0 -0
  73. {ragx_cli-0.5.1 → ragx_cli-0.7.0}/src/ragx/core/communities.py +0 -0
  74. {ragx_cli-0.5.1 → ragx_cli-0.7.0}/src/ragx/core/discovery.py +0 -0
  75. {ragx_cli-0.5.1 → ragx_cli-0.7.0}/src/ragx/core/errors.py +0 -0
  76. {ragx_cli-0.5.1 → ragx_cli-0.7.0}/src/ragx/core/eval.py +0 -0
  77. {ragx_cli-0.5.1 → ragx_cli-0.7.0}/src/ragx/core/expansion.py +0 -0
  78. {ragx_cli-0.5.1 → ragx_cli-0.7.0}/src/ragx/core/fusion.py +0 -0
  79. {ragx_cli-0.5.1 → ragx_cli-0.7.0}/src/ragx/core/graph.py +0 -0
  80. {ragx_cli-0.5.1 → ragx_cli-0.7.0}/src/ragx/core/indexer.py +0 -0
  81. {ragx_cli-0.5.1 → ragx_cli-0.7.0}/src/ragx/core/models.py +0 -0
  82. {ragx_cli-0.5.1 → ragx_cli-0.7.0}/src/ragx/core/query.py +0 -0
  83. {ragx_cli-0.5.1 → ragx_cli-0.7.0}/src/ragx/core/scoring.py +0 -0
  84. {ragx_cli-0.5.1 → ragx_cli-0.7.0}/src/ragx/core/store.py +0 -0
  85. {ragx_cli-0.5.1 → ragx_cli-0.7.0}/src/ragx/core/traversal.py +0 -0
  86. {ragx_cli-0.5.1 → ragx_cli-0.7.0}/src/ragx/core/vectors.py +0 -0
  87. {ragx_cli-0.5.1 → ragx_cli-0.7.0}/src/ragx/providers/__init__.py +0 -0
  88. {ragx_cli-0.5.1 → ragx_cli-0.7.0}/src/ragx/providers/base.py +0 -0
  89. {ragx_cli-0.5.1 → ragx_cli-0.7.0}/src/ragx/providers/llama_embedder.py +0 -0
  90. {ragx_cli-0.5.1 → ragx_cli-0.7.0}/src/ragx/providers/llama_server.py +0 -0
  91. {ragx_cli-0.5.1 → ragx_cli-0.7.0}/src/ragx/providers/openai_compat.py +0 -0
  92. {ragx_cli-0.5.1 → ragx_cli-0.7.0}/src/ragx/providers/st_reranker.py +0 -0
  93. {ragx_cli-0.5.1 → ragx_cli-0.7.0}/tests/__init__.py +0 -0
  94. {ragx_cli-0.5.1 → ragx_cli-0.7.0}/tests/test_chunking.py +0 -0
  95. {ragx_cli-0.5.1 → ragx_cli-0.7.0}/tests/test_cli.py +0 -0
  96. {ragx_cli-0.5.1 → ragx_cli-0.7.0}/tests/test_communities.py +0 -0
  97. {ragx_cli-0.5.1 → ragx_cli-0.7.0}/tests/test_config_rc.py +0 -0
  98. {ragx_cli-0.5.1 → ragx_cli-0.7.0}/tests/test_discovery.py +0 -0
  99. {ragx_cli-0.5.1 → ragx_cli-0.7.0}/tests/test_eval.py +0 -0
  100. {ragx_cli-0.5.1 → ragx_cli-0.7.0}/tests/test_expansion.py +0 -0
  101. {ragx_cli-0.5.1 → ragx_cli-0.7.0}/tests/test_fusion.py +0 -0
  102. {ragx_cli-0.5.1 → ragx_cli-0.7.0}/tests/test_graph.py +0 -0
  103. {ragx_cli-0.5.1 → ragx_cli-0.7.0}/tests/test_inspect.py +0 -0
  104. {ragx_cli-0.5.1 → ragx_cli-0.7.0}/tests/test_integration.py +0 -0
  105. {ragx_cli-0.5.1 → ragx_cli-0.7.0}/tests/test_providers.py +0 -0
  106. {ragx_cli-0.5.1 → ragx_cli-0.7.0}/tests/test_store.py +0 -0
  107. {ragx_cli-0.5.1 → ragx_cli-0.7.0}/tests/test_traversal.py +0 -0
  108. {ragx_cli-0.5.1 → ragx_cli-0.7.0}/tests/test_vectors.py +0 -0
@@ -4,6 +4,56 @@ All notable changes to `ragx-cli` are documented here. The format follows
4
4
  [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); versions follow
5
5
  [Semantic Versioning](https://semver.org/).
6
6
 
7
+ ## [0.7.0] — 2026-09-17
8
+
9
+ ### Added
10
+
11
+ - **`ragx-cli doctor`**: runs the configured providers for real — embeds a probe string,
12
+ scores a probe pair with the reranker, and checks the index manifest and corpus drift —
13
+ then reports every stage with an actionable hint. Starts whatever ragx manages, exactly
14
+ as `index`/`query` would. Exit `0` healthy, `1` a check failed, `2` doctor could not run
15
+ (no corpus, unreadable config); warnings (no index yet, drift, a model the server does
16
+ not list but may JIT-load) do not fail the run. `--json` emits `ragx.doctor.v1` with a
17
+ `status`/`detail`/`hint`/`elapsed_ms` entry per check.
18
+ - **LM Studio autostart**: with `provider = "openai"` and a local `base_url`, `index`,
19
+ `query`, and `doctor` now run `lms server start` and `lms load <model>` before their
20
+ first request, so a cold machine needs no manual setup. ragx already spawned the
21
+ `llama-server` engines; LM Studio is an app ragx hands off to, so it is started but
22
+ never stopped. Only local URLs are touched (a cloud endpoint, including one set via
23
+ `OPENAI_BASE_URL`, is never started), an already-running server is left alone, and a
24
+ model that is not downloaded fails loud naming the models the machine does have.
25
+ Opt out with `embeddings.autostart` / `expansion.autostart` (default `true`, settable
26
+ machine-wide via `ragx-cli config set --global`).
27
+
28
+ ### Fixed
29
+
30
+ - **`llama-server` returned 500 on every real query.** The server was spawned without
31
+ `-b`/`-ub`/`-c`, leaving llama.cpp's default 512-token physical batch in place; a rerank
32
+ or embedding input is non-causal and cannot be split across ubatches, so ragx's
33
+ ~800-token chunks overflowed it ("input is too large to process"). Two-document smoke
34
+ tests passed, which is why it went unnoticed. Now spawned with
35
+ `--parallel 1 -c 0 -b 8192 -ub 8192` — one slot with the model's full trained context.
36
+ Fixes the `llama-server` embedding engine at default chunk size as well as rerank.
37
+ - **`ragx-cli models --quality balanced` could not download BGE-M3.** `gaianet/bge-m3-GGUF`
38
+ now answers 401 from the Hugging Face API; both refs point at `gpustack/bge-m3-GGUF@Q8_0`
39
+ (same model, already the catalog's reranker source, pinned to the benchmarked quant).
40
+
41
+ ## [0.6.0] — 2026-07-15
42
+
43
+ ### Changed
44
+
45
+ - **`ragx-cli models` interactive flow redesigned** around context → choice → consequence →
46
+ commit: a machine header (RAM, platform, `llama-server`/`lms` detection) prints first;
47
+ the quality tier is picked from a numbered comparison table (model, size, languages,
48
+ context, license) and the serving engines from numbered menus with one-line tradeoffs —
49
+ recommended defaults marked `»`, invalid input re-prompts; a plan step (download sizes,
50
+ `already downloaded ✓`, config sections, re-index consequence) shows before the confirm.
51
+ Behavior changes: on <8 GB RAM machines an interactive pick of `balanced`/`best` is
52
+ confirmed and then honored instead of silently downgraded (non-interactive runs keep the
53
+ auto-downgrade); `--quality jina-nano` without `--embed-engine` now locks the embedding
54
+ engine to `llama-server` instead of erroring (an explicit `--embed-engine lm-studio`
55
+ still fails loud). Flags, `--json` output, and the non-interactive contract are unchanged.
56
+
7
57
  ## [0.5.0] — 2026-07-15
8
58
 
9
59
  ### Added
@@ -27,6 +27,11 @@ API contracts for every core module. Deferred feature specs live in `docs/`.
27
27
  with the llama-server rerank engine, LM Studio is downloader-only at query time.
28
28
  Env fallbacks: `OPENAI_BASE_URL` (only while base_url is still the default) and
29
29
  `OPENAI_API_KEY` (only when `api_key_env` is unset); explicit config always wins.
30
+ With `provider="openai"` and a LOCAL base_url, `index`/`query`/`doctor` autostart LM
31
+ Studio first (`providers/lmstudio_process.py`: `lms server start` + `lms load` when the
32
+ model isn't in `lms ps`; never stops it — it's an app, not our child). Remote URLs and
33
+ `<section>.autostart=false` skip it. Tests are disarmed by an autouse conftest fixture
34
+ that stubs `ensure_for_section` — never let a test reach the real `lms`.
30
35
  - Machine-level provider settings live in `~/.ragxrc` (TOML; ONLY the embeddings/
31
36
  expansion/rerank sections, unknown keys fail loud). Precedence: DEFAULTS < corpus
32
37
  ragx.toml < ~/.ragxrc — the rc OVERRIDES corpus values and logs a stderr warning
@@ -63,17 +68,20 @@ src/ragx/
63
68
  indexer.py # run_index: discover->hash->chunk->embed->HNSW+store->kNN edges
64
69
  query.py # run_query: expansion->fan-out->RRF->traversal->rerank->combine; JSON serializers
65
70
  eval.py # recall@5/@10 + MRR over file-level ranking, injected query_fn
71
+ doctor.py # run_doctor: config/embeddings/expansion/rerank/index health checks
72
+ # (injected provider factories; backends get started here too)
66
73
  catalog.py # curated embedding/reranker catalog (tiers, prefixes) + spec detect
67
74
  lmstudio.py # `lms` CLI wrapper: find/download (`lms get --yes`)/`lms ls --json`;
68
75
  # LM Studio can't download plain safetensors — rerankers stay on HF
69
76
  providers/ # base.py protocols (Embedder/Generator/Reranker); openai_compat.py;
70
- # st_reranker.py; llama_process.py (managed llama-server lifecycle) +
77
+ # st_reranker.py; lmstudio_process.py (LM Studio autostart);
78
+ # llama_process.py (managed llama-server lifecycle) +
71
79
  # llama_server.py (rerank) + llama_embedder.py (embeddings);
72
80
  # registry.py factories (env-var + api_key_env resolution)
73
81
  cli/ # thin shells only: app.py (init/status/config + registration),
74
82
  # pipeline.py (index/query), inspect_cmd.py, eval_cmd.py, models_cmd.py
75
- # (recommend/download/configure models), output.py
76
- tests/ # 177 tests; mocked HTTP (respx), FakeEmbedder integration tests, no live network
83
+ # (recommend/download/configure models), doctor_cmd.py, output.py
84
+ tests/ # 244 tests; mocked HTTP (respx), FakeEmbedder integration tests, no live network
77
85
  ```
78
86
 
79
87
  ## Flows
@@ -132,6 +140,10 @@ mostly on Dutch/multilingual queries. Details: `research/bge-m3-dense-q8-vs-nomi
132
140
 
133
141
  ## Gotchas
134
142
 
143
+ - llama-server spawns MUST pass `--parallel 1 -c 0 -b/-ub 8192` (`llama_process.py`):
144
+ llama.cpp's default 512-token physical batch cannot hold a ~800-token chunk, and a
145
+ non-causal rerank/embedding input can't split across ubatches — without them every real
146
+ query 500s ("input is too large to process") while a 2-doc smoke test passes.
135
147
  - Reasoning models as expansion LLM: answers land in `content` only after thinking;
136
148
  expansion uses max_tokens=4096 to leave room for that. Don't lower it.
137
149
  - Changing `embeddings.model` invalidates the index; run_query fails loud on manifest
@@ -158,7 +170,7 @@ mostly on Dutch/multilingual queries. Details: `research/bge-m3-dense-q8-vs-nomi
158
170
 
159
171
  ## Dev loop
160
172
 
161
- `uv sync --group dev --extra rerank` · `uv run pytest -q` (177 pass, ~5 s) ·
173
+ `uv sync --group dev --extra rerank` · `uv run pytest -q` (244 pass, ~5 s) ·
162
174
  `uv run ruff check src tests` · file soft cap ~150 lines · expected failures raise `RagxError`
163
175
  (CLI maps to exit 2). Live smoke: LM Studio must be running with the configured embedding model.
164
176
  Changes that impact usage (CLI flags, config keys/precedence, output schemas, install steps)
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: ragx-cli
3
- Version: 0.5.1
3
+ Version: 0.7.0
4
4
  Summary: Similarity-graph RAG CLI: index a corpus into a chunk-level embedding graph and query it
5
5
  Project-URL: Repository, https://github.com/alber70g/ragx-cli
6
6
  Project-URL: Issues, https://github.com/alber70g/ragx-cli/issues
@@ -53,6 +53,7 @@ ragx-cli index # chunk -> embed -> HNSW + kNN similarity graph (
53
53
  ragx-cli query "why did we switch build tools?" --json --files-only
54
54
  ragx-cli index --full # full rebuild: re-chunk + re-embed everything
55
55
  ragx-cli models # recommend + download an embedding/reranker combo via LM Studio
56
+ ragx-cli doctor # check the configured providers start and answer, and the index matches
56
57
  ragx-cli --version # version, repo, and the effective models (corpus config + ~/.ragxrc overrides)
57
58
  ```
58
59
 
@@ -70,6 +71,7 @@ your files (gitignore it) — like `.git/`, delete `.ragx/` and the corpus is un
70
71
  - [Does it actually help? (benchmarks)](#does-it-actually-help-benchmarks)
71
72
  - [Parameter study: what each knob actually does (2026-07)](#parameter-study-what-each-knob-actually-does-2026-07)
72
73
  - [BGE-M3 embedding study (2026-07-12)](#bge-m3-embedding-study-2026-07-12)
74
+ - [Checking your setup: `ragx-cli doctor`](#checking-your-setup-ragx-cli-doctor)
73
75
  - [Agent-first conventions](#agent-first-conventions)
74
76
  - [Using ragx-cli from a coding agent (CLAUDE.md / AGENTS.md)](#using-ragx-cli-from-a-coding-agent-claudemd--agentsmd)
75
77
  - [Pointing ragx-cli at your LLM — local or online](#pointing-ragx-cli-at-your-llm--local-or-online)
@@ -325,8 +327,19 @@ Full write-ups: `research/bge-m3-dense-q8-vs-nomic-q4-benchmark-2026-07-12-workt
325
327
 
326
328
  `ragx-cli models` recommends an embedding + reranker combo, downloads the embedding model
327
329
  **through LM Studio** (`lms get` — one download path, plays well with restricted networks),
328
- pre-fetches the reranker from Hugging Face, and writes both to `ragx.toml`. Interactive on a
329
- TTY (quality tier + engine questions, RAM auto-detected); scriptable via flags:
330
+ pre-fetches the reranker from Hugging Face, and writes both to `ragx.toml`. On a TTY it walks
331
+ three steps — context first, consequences before anything is written:
332
+
333
+ 1. **machine header** — detected RAM, platform, whether `llama-server` is on PATH and the
334
+ `lms` CLI was found (this is what drives the recommended defaults);
335
+ 2. **numbered menus** — the embedding tier table (model, size, languages, context, license)
336
+ and one menu per serving engine with a one-line tradeoff each; the recommended default is
337
+ marked `»` (Enter accepts), invalid input re-prompts;
338
+ 3. **plan** — what will be downloaded (or `already downloaded ✓`) with approximate sizes,
339
+ which config sections change, and whether a re-index is required — all shown *before*
340
+ the final confirm.
341
+
342
+ Scriptable via flags:
330
343
 
331
344
  ```bash
332
345
  ragx-cli models # interactive
@@ -336,12 +349,12 @@ ragx-cli models --quality fast --dry-run # recommend only, change
336
349
 
337
350
  The curated catalog (every entry verified working end-to-end):
338
351
 
339
- | tier | embedding model | notes |
340
- |---|---|---|
341
- | `fast` | EmbeddingGemma 300M | 308M, 100+ languages, 2048-token context |
342
- | `balanced` | BGE-M3 | ragx's benchmarked production model (~100 languages) |
343
- | `best` | Qwen3-Embedding-0.6B | 100+ languages, 32K context |
344
- | `jina-nano` | Jina embeddings v5 nano | 239M, best-in-class sub-500M; **CC-BY-NC (non-commercial)**; llama-server engine only — LM Studio downloads it but can't serve EuroBERT |
352
+ | tier | embedding model | size | langs | ctx | notes |
353
+ |---|---|---|---|---|---|
354
+ | `fast` | EmbeddingGemma 300M | 308M | 100+ | 2K | lightest; small corpora, low RAM |
355
+ | `balanced` | BGE-M3 | 568M | ~100 | 8K | ragx's benchmarked production model |
356
+ | `best` | Qwen3-Embedding-0.6B | 0.6B | 100+ | 32K | highest quality; slower indexing |
357
+ | `jina-nano` | Jina embeddings v5 nano | 239M | multi | 8K | best sub-500M; **CC-BY-NC (non-commercial)**; llama-server engine only — LM Studio downloads it but can't serve EuroBERT (picking it locks the embedding engine to llama-server automatically; an explicit `--embed-engine lm-studio` still fails loud) |
345
358
 
346
359
  **Serving engines.** Embeddings run through `--embed-engine`: **`llama-server`** (default when
347
360
  llama.cpp is installed — ragx auto-spawns `llama-server --embedding` on the downloaded GGUF and
@@ -359,7 +372,9 @@ terminates it at exit; LM Studio is only the downloader) or **`lm-studio`** (LM
359
372
 
360
373
  With both engines on `llama-server` the whole stack is ragx-managed: LM Studio downloads the
361
374
  GGUFs, ragx spawns and reaps the servers, and neither LM Studio nor huggingface.co is needed
362
- at query time. Under 8 GB RAM the embedding tier drops to `fast`; on macOS
375
+ at query time. Under 8 GB RAM the recommended default moves to `fast`: interactively, picking
376
+ a heavier tier asks for confirmation and then **honors your choice**; non-interactive runs
377
+ (`--yes`/piped) auto-downgrade to `fast` with a note, since there is nobody to ask. On macOS
363
378
  `lms get --yes` picks MLX variants where LM Studio's catalog offers them. Switching the
364
379
  embedding model invalidates the index — the command tells you to run `ragx-cli index --full`.
365
380
  `init` offers this flow as its final step; rerun `ragx-cli models` anytime to switch.
@@ -368,16 +383,66 @@ Why not `jina-embeddings-v5-text-nano`? Excellent model, but its EuroBERT archit
368
383
  Jina's llama.cpp fork — stock LM Studio can't load it (and the license is CC-BY-NC). See
369
384
  `research/lm-studio-model-download-api-and-curated-embedding-reranker-catalog-for-ragx-models-command.md`.
370
385
 
386
+ ## Checking your setup: `ragx-cli doctor`
387
+
388
+ `ragx-cli doctor` runs the configured pipeline's providers for real — it embeds a probe string,
389
+ scores a probe pair with the reranker, and checks the index on disk against the config — then
390
+ reports each stage:
391
+
392
+ ```
393
+ ✓ config /corpus/ragx.toml — embeddings=openai/text-embedding-bge-m3 · expansion=openai/qwen3.5-9b · rerank=sentence-transformers/BAAI/bge-reranker-v2-m3
394
+ ✗ embeddings request to http://localhost:1234/v1/embeddings failed after 3 attempts: [Errno 61] Connection refused
395
+ → ragx does not start this server — launch it at http://localhost:1234/v1 (LM Studio: `lms server start`),
396
+ or hand the process to ragx with `ragx-cli models --embed-engine llama-server`
397
+ ✗ expansion no server answered at http://localhost:1234/v1/models
398
+ → turn it off with `ragx-cli config set expansion.enabled false`
399
+ ✓ rerank sentence-transformers/BAAI/bge-reranker-v2-m3 scored a probe pair (-8.14, 4.72) 3.1s
400
+ ✓ index 634 files, 1247 chunks, built with 'text-embedding-bge-m3', no drift
401
+ ```
402
+
403
+ Exit codes: `0` healthy, `1` at least one check failed, `2` doctor itself could not run (no
404
+ corpus, unreadable config). Warnings (no index yet, corpus drift, a model the server does not
405
+ list but may JIT-load) do not fail the run. `--json` emits a `ragx.doctor.v1` document with a
406
+ `status`/`detail`/`hint`/`elapsed_ms` entry per check.
407
+
408
+ **Which servers does ragx start?** All of the local ones:
409
+
410
+ | engine (`ragx-cli models`) | config | lifecycle |
411
+ | --- | --- | --- |
412
+ | `llama-server` | `provider="llama-server"` (embeddings and/or rerank) | spawned on demand from the configured GGUF and **killed at exit** — ragx owns the process |
413
+ | `lm-studio` | `embeddings.provider="openai"` + a local `base_url` | `lms server start` + `lms load <model>` when nothing answers — LM Studio **keeps running** afterwards (it is an app, not ragx's child) |
414
+ | `sentence-transformers` | `rerank.provider="sentence-transformers"` | in-process, no server |
415
+
416
+ `index`, `query`, and `doctor` all do this before their first request, so a cold machine needs
417
+ no manual setup. What was started is logged to stderr and shown in doctor's report:
418
+
419
+ ```
420
+ ✓ embeddings started the LM Studio server on port 1234; loaded 'text-embedding-nomic-embed-text-v1.5' into LM Studio — openai/text-embedding-nomic-embed-text-v1.5 answered, dim=768 14.8s
421
+ ```
422
+
423
+ Rules it follows: only a **local** `base_url` is ever touched (a cloud endpoint, including one
424
+ set via `OPENAI_BASE_URL`, is never ragx's to start); an already-running server is left alone,
425
+ and if LM Studio isn't installed but something else answers on that port, ragx just uses it; a
426
+ model that isn't downloaded fails loud naming the models the machine does have. Turn it off per
427
+ section with `ragx-cli config set embeddings.autostart false` (or machine-wide via
428
+ `--global`) — then ragx only ever talks to servers you started.
429
+
430
+ `ragx-cli models` still only downloads models and writes `ragx.toml`; it never launches
431
+ anything. If a run dies with "connection refused", `doctor` names the stage and the fix.
432
+
371
433
  ## Agent-first conventions
372
434
 
373
435
  - `--json` emits exactly one JSON document on stdout (versioned schemas: `ragx.query.v1`,
374
- `ragx.files.v1`, `ragx.status.v1`, `ragx.eval.v1`, `ragx.inspect.*.v1`); logs go to stderr.
436
+ `ragx.files.v1`, `ragx.status.v1`, `ragx.doctor.v1`, `ragx.eval.v1`, `ragx.inspect.*.v1`); logs go
437
+ to stderr.
375
438
  - Exit codes: `0` results, `1` success-but-empty, `2` error.
376
439
  - Every chunk carries `file`, `line_start/line_end`, `byte_start/byte_end` — agents jump to the
377
440
  exact source location and read the full text themselves (JSON chunk text is truncated).
378
441
  - `--files-only` aggregates chunk scores per file (sum of top-3) — the mode coding agents use most.
379
442
  - `ragx-cli status` includes a `drift` object (`new`/`changed`/`deleted` file counts vs the index) —
380
443
  agents check it to decide whether to run `ragx-cli index` before querying.
444
+ - `ragx-cli doctor --json` verifies the providers answer before a batch of queries (`ok: false`
445
+ plus a per-check `hint` when something is down); exit `1` = a check failed.
381
446
  - `ragx-cli query -` reads the query from stdin; `ragx-cli inspect chunk|file|neighbors|communities|community`
382
447
  debugs the graph.
383
448
 
@@ -556,8 +621,8 @@ below — Enter accepts them all. With piped stdin (agents), `--yes`, or
556
621
  | `[communities]` | `resolution=1.0`, `seed=42` — recomputed every index run; changing these never invalidates the index |
557
622
  | `[fusion]` | `rrf_k=60`, `per_query_top=20` |
558
623
  | `[scoring]` | `alpha_rerank=0.6`, `beta_heat=0.25`, `gamma_vector=0.15` |
559
- | `[embeddings]` | `provider="openai"`, `base_url="http://localhost:1234/v1"`, prefixes for nomic-style models, `api_key_env=""` |
560
- | `[expansion]` | optional LLM for multi-query/HyDE; reasoning models supported (4096-token budget); `api_key_env=""` |
624
+ | `[embeddings]` | `provider="openai"`, `base_url="http://localhost:1234/v1"`, prefixes for nomic-style models, `api_key_env=""`, `autostart=true` (start LM Studio + load the model when the URL is local) |
625
+ | `[expansion]` | optional LLM for multi-query/HyDE; reasoning models supported (4096-token budget); `api_key_env=""`, `autostart=true` |
561
626
  | `[rerank]` | `BAAI/bge-reranker-v2-m3` via sentence-transformers (`uv tool install 'ragx-cli[rerank]'`) |
562
627
 
563
628
  ## Features & roadmap
@@ -25,6 +25,7 @@ ragx-cli index # chunk -> embed -> HNSW + kNN similarity graph (
25
25
  ragx-cli query "why did we switch build tools?" --json --files-only
26
26
  ragx-cli index --full # full rebuild: re-chunk + re-embed everything
27
27
  ragx-cli models # recommend + download an embedding/reranker combo via LM Studio
28
+ ragx-cli doctor # check the configured providers start and answer, and the index matches
28
29
  ragx-cli --version # version, repo, and the effective models (corpus config + ~/.ragxrc overrides)
29
30
  ```
30
31
 
@@ -42,6 +43,7 @@ your files (gitignore it) — like `.git/`, delete `.ragx/` and the corpus is un
42
43
  - [Does it actually help? (benchmarks)](#does-it-actually-help-benchmarks)
43
44
  - [Parameter study: what each knob actually does (2026-07)](#parameter-study-what-each-knob-actually-does-2026-07)
44
45
  - [BGE-M3 embedding study (2026-07-12)](#bge-m3-embedding-study-2026-07-12)
46
+ - [Checking your setup: `ragx-cli doctor`](#checking-your-setup-ragx-cli-doctor)
45
47
  - [Agent-first conventions](#agent-first-conventions)
46
48
  - [Using ragx-cli from a coding agent (CLAUDE.md / AGENTS.md)](#using-ragx-cli-from-a-coding-agent-claudemd--agentsmd)
47
49
  - [Pointing ragx-cli at your LLM — local or online](#pointing-ragx-cli-at-your-llm--local-or-online)
@@ -297,8 +299,19 @@ Full write-ups: `research/bge-m3-dense-q8-vs-nomic-q4-benchmark-2026-07-12-workt
297
299
 
298
300
  `ragx-cli models` recommends an embedding + reranker combo, downloads the embedding model
299
301
  **through LM Studio** (`lms get` — one download path, plays well with restricted networks),
300
- pre-fetches the reranker from Hugging Face, and writes both to `ragx.toml`. Interactive on a
301
- TTY (quality tier + engine questions, RAM auto-detected); scriptable via flags:
302
+ pre-fetches the reranker from Hugging Face, and writes both to `ragx.toml`. On a TTY it walks
303
+ three steps — context first, consequences before anything is written:
304
+
305
+ 1. **machine header** — detected RAM, platform, whether `llama-server` is on PATH and the
306
+ `lms` CLI was found (this is what drives the recommended defaults);
307
+ 2. **numbered menus** — the embedding tier table (model, size, languages, context, license)
308
+ and one menu per serving engine with a one-line tradeoff each; the recommended default is
309
+ marked `»` (Enter accepts), invalid input re-prompts;
310
+ 3. **plan** — what will be downloaded (or `already downloaded ✓`) with approximate sizes,
311
+ which config sections change, and whether a re-index is required — all shown *before*
312
+ the final confirm.
313
+
314
+ Scriptable via flags:
302
315
 
303
316
  ```bash
304
317
  ragx-cli models # interactive
@@ -308,12 +321,12 @@ ragx-cli models --quality fast --dry-run # recommend only, change
308
321
 
309
322
  The curated catalog (every entry verified working end-to-end):
310
323
 
311
- | tier | embedding model | notes |
312
- |---|---|---|
313
- | `fast` | EmbeddingGemma 300M | 308M, 100+ languages, 2048-token context |
314
- | `balanced` | BGE-M3 | ragx's benchmarked production model (~100 languages) |
315
- | `best` | Qwen3-Embedding-0.6B | 100+ languages, 32K context |
316
- | `jina-nano` | Jina embeddings v5 nano | 239M, best-in-class sub-500M; **CC-BY-NC (non-commercial)**; llama-server engine only — LM Studio downloads it but can't serve EuroBERT |
324
+ | tier | embedding model | size | langs | ctx | notes |
325
+ |---|---|---|---|---|---|
326
+ | `fast` | EmbeddingGemma 300M | 308M | 100+ | 2K | lightest; small corpora, low RAM |
327
+ | `balanced` | BGE-M3 | 568M | ~100 | 8K | ragx's benchmarked production model |
328
+ | `best` | Qwen3-Embedding-0.6B | 0.6B | 100+ | 32K | highest quality; slower indexing |
329
+ | `jina-nano` | Jina embeddings v5 nano | 239M | multi | 8K | best sub-500M; **CC-BY-NC (non-commercial)**; llama-server engine only — LM Studio downloads it but can't serve EuroBERT (picking it locks the embedding engine to llama-server automatically; an explicit `--embed-engine lm-studio` still fails loud) |
317
330
 
318
331
  **Serving engines.** Embeddings run through `--embed-engine`: **`llama-server`** (default when
319
332
  llama.cpp is installed — ragx auto-spawns `llama-server --embedding` on the downloaded GGUF and
@@ -331,7 +344,9 @@ terminates it at exit; LM Studio is only the downloader) or **`lm-studio`** (LM
331
344
 
332
345
  With both engines on `llama-server` the whole stack is ragx-managed: LM Studio downloads the
333
346
  GGUFs, ragx spawns and reaps the servers, and neither LM Studio nor huggingface.co is needed
334
- at query time. Under 8 GB RAM the embedding tier drops to `fast`; on macOS
347
+ at query time. Under 8 GB RAM the recommended default moves to `fast`: interactively, picking
348
+ a heavier tier asks for confirmation and then **honors your choice**; non-interactive runs
349
+ (`--yes`/piped) auto-downgrade to `fast` with a note, since there is nobody to ask. On macOS
335
350
  `lms get --yes` picks MLX variants where LM Studio's catalog offers them. Switching the
336
351
  embedding model invalidates the index — the command tells you to run `ragx-cli index --full`.
337
352
  `init` offers this flow as its final step; rerun `ragx-cli models` anytime to switch.
@@ -340,16 +355,66 @@ Why not `jina-embeddings-v5-text-nano`? Excellent model, but its EuroBERT archit
340
355
  Jina's llama.cpp fork — stock LM Studio can't load it (and the license is CC-BY-NC). See
341
356
  `research/lm-studio-model-download-api-and-curated-embedding-reranker-catalog-for-ragx-models-command.md`.
342
357
 
358
+ ## Checking your setup: `ragx-cli doctor`
359
+
360
+ `ragx-cli doctor` runs the configured pipeline's providers for real — it embeds a probe string,
361
+ scores a probe pair with the reranker, and checks the index on disk against the config — then
362
+ reports each stage:
363
+
364
+ ```
365
+ ✓ config /corpus/ragx.toml — embeddings=openai/text-embedding-bge-m3 · expansion=openai/qwen3.5-9b · rerank=sentence-transformers/BAAI/bge-reranker-v2-m3
366
+ ✗ embeddings request to http://localhost:1234/v1/embeddings failed after 3 attempts: [Errno 61] Connection refused
367
+ → ragx does not start this server — launch it at http://localhost:1234/v1 (LM Studio: `lms server start`),
368
+ or hand the process to ragx with `ragx-cli models --embed-engine llama-server`
369
+ ✗ expansion no server answered at http://localhost:1234/v1/models
370
+ → turn it off with `ragx-cli config set expansion.enabled false`
371
+ ✓ rerank sentence-transformers/BAAI/bge-reranker-v2-m3 scored a probe pair (-8.14, 4.72) 3.1s
372
+ ✓ index 634 files, 1247 chunks, built with 'text-embedding-bge-m3', no drift
373
+ ```
374
+
375
+ Exit codes: `0` healthy, `1` at least one check failed, `2` doctor itself could not run (no
376
+ corpus, unreadable config). Warnings (no index yet, corpus drift, a model the server does not
377
+ list but may JIT-load) do not fail the run. `--json` emits a `ragx.doctor.v1` document with a
378
+ `status`/`detail`/`hint`/`elapsed_ms` entry per check.
379
+
380
+ **Which servers does ragx start?** All of the local ones:
381
+
382
+ | engine (`ragx-cli models`) | config | lifecycle |
383
+ | --- | --- | --- |
384
+ | `llama-server` | `provider="llama-server"` (embeddings and/or rerank) | spawned on demand from the configured GGUF and **killed at exit** — ragx owns the process |
385
+ | `lm-studio` | `embeddings.provider="openai"` + a local `base_url` | `lms server start` + `lms load <model>` when nothing answers — LM Studio **keeps running** afterwards (it is an app, not ragx's child) |
386
+ | `sentence-transformers` | `rerank.provider="sentence-transformers"` | in-process, no server |
387
+
388
+ `index`, `query`, and `doctor` all do this before their first request, so a cold machine needs
389
+ no manual setup. What was started is logged to stderr and shown in doctor's report:
390
+
391
+ ```
392
+ ✓ embeddings started the LM Studio server on port 1234; loaded 'text-embedding-nomic-embed-text-v1.5' into LM Studio — openai/text-embedding-nomic-embed-text-v1.5 answered, dim=768 14.8s
393
+ ```
394
+
395
+ Rules it follows: only a **local** `base_url` is ever touched (a cloud endpoint, including one
396
+ set via `OPENAI_BASE_URL`, is never ragx's to start); an already-running server is left alone,
397
+ and if LM Studio isn't installed but something else answers on that port, ragx just uses it; a
398
+ model that isn't downloaded fails loud naming the models the machine does have. Turn it off per
399
+ section with `ragx-cli config set embeddings.autostart false` (or machine-wide via
400
+ `--global`) — then ragx only ever talks to servers you started.
401
+
402
+ `ragx-cli models` still only downloads models and writes `ragx.toml`; it never launches
403
+ anything. If a run dies with "connection refused", `doctor` names the stage and the fix.
404
+
343
405
  ## Agent-first conventions
344
406
 
345
407
  - `--json` emits exactly one JSON document on stdout (versioned schemas: `ragx.query.v1`,
346
- `ragx.files.v1`, `ragx.status.v1`, `ragx.eval.v1`, `ragx.inspect.*.v1`); logs go to stderr.
408
+ `ragx.files.v1`, `ragx.status.v1`, `ragx.doctor.v1`, `ragx.eval.v1`, `ragx.inspect.*.v1`); logs go
409
+ to stderr.
347
410
  - Exit codes: `0` results, `1` success-but-empty, `2` error.
348
411
  - Every chunk carries `file`, `line_start/line_end`, `byte_start/byte_end` — agents jump to the
349
412
  exact source location and read the full text themselves (JSON chunk text is truncated).
350
413
  - `--files-only` aggregates chunk scores per file (sum of top-3) — the mode coding agents use most.
351
414
  - `ragx-cli status` includes a `drift` object (`new`/`changed`/`deleted` file counts vs the index) —
352
415
  agents check it to decide whether to run `ragx-cli index` before querying.
416
+ - `ragx-cli doctor --json` verifies the providers answer before a batch of queries (`ok: false`
417
+ plus a per-check `hint` when something is down); exit `1` = a check failed.
353
418
  - `ragx-cli query -` reads the query from stdin; `ragx-cli inspect chunk|file|neighbors|communities|community`
354
419
  debugs the graph.
355
420
 
@@ -528,8 +593,8 @@ below — Enter accepts them all. With piped stdin (agents), `--yes`, or
528
593
  | `[communities]` | `resolution=1.0`, `seed=42` — recomputed every index run; changing these never invalidates the index |
529
594
  | `[fusion]` | `rrf_k=60`, `per_query_top=20` |
530
595
  | `[scoring]` | `alpha_rerank=0.6`, `beta_heat=0.25`, `gamma_vector=0.15` |
531
- | `[embeddings]` | `provider="openai"`, `base_url="http://localhost:1234/v1"`, prefixes for nomic-style models, `api_key_env=""` |
532
- | `[expansion]` | optional LLM for multi-query/HyDE; reasoning models supported (4096-token budget); `api_key_env=""` |
596
+ | `[embeddings]` | `provider="openai"`, `base_url="http://localhost:1234/v1"`, prefixes for nomic-style models, `api_key_env=""`, `autostart=true` (start LM Studio + load the model when the URL is local) |
597
+ | `[expansion]` | optional LLM for multi-query/HyDE; reasoning models supported (4096-token budget); `api_key_env=""`, `autostart=true` |
533
598
  | `[rerank]` | `BAAI/bge-reranker-v2-m3` via sentence-transformers (`uv tool install 'ragx-cli[rerank]'`) |
534
599
 
535
600
  ## Features & roadmap
@@ -0,0 +1,131 @@
1
+ # Proposal: `ragx-cli models` interactive flow redesign
2
+
3
+ **Status:** implemented 2026-07-15 (`cli/models_ui.py` + `cli/models_cmd.py`); kept as the
4
+ design rationale. The open decision below was resolved as proposed: warn-and-honor
5
+ interactively, auto-downgrade non-interactively.
6
+
7
+ ## Problem
8
+
9
+ The current interactive flow (`src/ragx/cli/models_cmd.py:62-104`) asks all three
10
+ questions **before** showing any of the information needed to answer them:
11
+
12
+ 1. **Blind prompts.** `quality (fast/balanced/best/jina-nano) [balanced]:` offers four
13
+ opaque words. The catalog knows each tier's model name, parameter count, languages,
14
+ context length, and license — none of it is shown at prompt time. Machine specs print
15
+ *after* the answers.
16
+ 2. **Engine prompts don't state tradeoffs.** "llama-server vs sentence-transformers"
17
+ means nothing unless you already know one avoids huggingface.co entirely and the
18
+ other needs the `[rerank]` extra. That context lives only in `--help` flag text.
19
+ 3. **Silent downgrade.** Pick `best` on a <8 GB machine and `recommend()`
20
+ (`catalog.py:133-135`) substitutes `fast`, telling you only afterwards. No way to
21
+ override or reconsider.
22
+ 4. **The jina-nano trap.** Choosing `jina-nano` with the default `lm-studio` embed
23
+ engine hard-errors at the end ("rerun with `--embed-engine llama-server`") — even
24
+ though the catalog already encodes the constraint (`requires_llama_server`) and
25
+ could drive the default.
26
+ 5. **Fragile input.** A typo in the free-text tier name raises `RagxError`, exit 2,
27
+ start over.
28
+ 6. **Consequences surface too late.** Download sizes are never shown; "your index is
29
+ now invalid, run `index --full`" prints *after* `ragx.toml` is written.
30
+
31
+ ## Proposed flow
32
+
33
+ Principle: **context → choice → consequence → commit.** Detect everything first, show
34
+ it, ask with numbered menus, and present a plan before touching anything.
35
+
36
+ ```
37
+ $ ragx-cli models
38
+
39
+ machine: 32 GB RAM · macOS · llama-server: on PATH · LM Studio (lms): found
40
+
41
+ ── Step 1/3 · embedding model ─────────────────────────────────────────
42
+ Determines retrieval quality. Changing it later invalidates the
43
+ index (full re-index required).
44
+
45
+ # tier model size langs ctx notes
46
+ 1 fast EmbeddingGemma 300M 308M 100+ 2K lightest; small corpora, low RAM
47
+ » 2 balanced BGE-M3 568M ~100 8K ragx's benchmarked production model
48
+ 3 best Qwen3-Embedding-0.6B 0.6B 100+ 32K highest quality; slower indexing
49
+ 4 jina-nano Jina v5 nano 239M multi 8K best <500M quality · CC-BY-NC
50
+ (non-commercial) · llama-server only
51
+ select 1-4 [2]:
52
+
53
+ ── Step 2/3 · engines ─────────────────────────────────────────────────
54
+ embedding serving:
55
+ » 1 llama-server ragx auto-spawns llama.cpp; LM Studio only
56
+ downloads (detected on PATH)
57
+ 2 lm-studio LM Studio app must be running to serve
58
+ select 1-2 [1]:
59
+
60
+ reranker:
61
+ » 1 llama-server GGUF via LM Studio — no huggingface.co needed
62
+ 2 sentence-transformers downloads from Hugging Face; needs the
63
+ ragx-cli[rerank] extra
64
+ select 1-2 [1]:
65
+
66
+ ── Step 3/3 · plan ────────────────────────────────────────────────────
67
+ embedding BGE-M3 Q8 GGUF (~600 MB) already downloaded ✓
68
+ reranker bge-reranker-v2-m3 Q8 (~600 MB) will download via LM Studio
69
+ config ragx.toml → embeddings.*, rerank.* (7 keys)
70
+ re-index REQUIRED — embedding model changes; run `ragx-cli index --full` after
71
+
72
+ proceed? [Y/n]:
73
+ ```
74
+
75
+ ### How each problem maps to a fix
76
+
77
+ - **Specs shown first**, including whether `llama-server` was found on PATH — the
78
+ engine defaults stop being invisible magic.
79
+ - **Numbered menus with a re-prompt loop** instead of one-shot free text. `»` marks
80
+ the recommended default; Enter accepts it. Invalid input re-prompts instead of
81
+ exiting.
82
+ - **RAM guard becomes visible, not substituting.** On a <8 GB machine the `»` marker
83
+ moves to `fast` and heavy rows get a `⚠ needs ≥8 GB` annotation. If the user still
84
+ picks `best`, honor it after a one-line confirm — a balanced choice means respecting
85
+ an informed override. Non-interactive `--quality best` keeps today's auto-downgrade
86
+ + note (an agent can't be asked); this interactive/non-interactive asymmetry should
87
+ be stated in the README.
88
+ - **jina-nano stops being a trap:** picking it prints
89
+ `→ embedding engine locked to llama-server (LM Studio cannot serve this model)` and
90
+ skips that question instead of erroring at the end.
91
+ - **The plan step** shows download sizes, marks what is already downloaded (the
92
+ `_ensure_downloaded` check moves before the confirm), lists which config keys
93
+ change, and states the re-index consequence *before* the write.
94
+
95
+ ## Implementation sketch
96
+
97
+ Small and contained — no behavior change for flags / `--json` / non-interactive:
98
+
99
+ 1. **`core/catalog.py`** — add structured fields to the choices (`params`, `context`,
100
+ `languages`, `download_mb`) instead of parsing prose out of `notes`.
101
+ Verify: existing tests pass; table renders from fields.
102
+ 2. **`cli/models_cmd.py`** — a `_pick_numbered(title, rows, default)` helper
103
+ (validation loop, `»` marker) replacing `typer.prompt`/`_pick_engine` in
104
+ interactive mode; reorder so specs + `lms`/`llama-server` detection and the
105
+ already-downloaded check happen before any question.
106
+ Verify: interactive smoke run against a scratch corpus.
107
+ 3. **`recommend()`** — split recommendation from enforcement: return annotations
108
+ (RAM warning, license note, engine lock) rather than silently swapping tiers in
109
+ interactive mode.
110
+ Verify: unit tests for low-RAM interactive vs non-interactive paths.
111
+ 4. **README** — update the `models` section in the same change (CLAUDE.md rule:
112
+ usage-visible changes ship with their docs).
113
+
114
+ Estimated diff: ~150 lines, centered on `models_cmd.py`.
115
+
116
+ ### Rendering choice
117
+
118
+ Plain f-string column alignment is enough. `rich` is already available (transitive
119
+ dependency of typer ≥0.12) if fancier tables are ever wanted, but no new dependency
120
+ and no speculative widgets now.
121
+
122
+ ### Deliberate non-goal
123
+
124
+ No full TUI (arrow-key selection à la questionary). Numbered menus keep the code
125
+ testable with piped stdin and keep the agent-first stdin/stdout contract intact.
126
+
127
+ ## Open decision
128
+
129
+ Interactive RAM-override behavior: warn-and-honor (proposed) vs keep the current
130
+ auto-downgrade everywhere. Proposal argues warn-and-honor interactively, downgrade
131
+ non-interactively.
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "ragx-cli"
3
- version = "0.5.1"
3
+ version = "0.7.0"
4
4
  description = "Similarity-graph RAG CLI: index a corpus into a chunk-level embedding graph and query it"
5
5
  readme = "README.md"
6
6
  license = "MIT"
@@ -44,7 +44,7 @@ build-backend = "hatchling.build"
44
44
  packages = ["src/ragx"]
45
45
 
46
46
  [dependency-groups]
47
- dev = ["pytest>=8", "ruff>=0.6", "respx>=0.21"]
47
+ dev = ["pytest>=8", "ruff>=0.15,<0.16", "respx>=0.21"]
48
48
 
49
49
  [tool.ruff]
50
50
  line-length = 100
@@ -32,12 +32,13 @@ app = typer.Typer(add_completion=False, no_args_is_help=True)
32
32
  config_app = typer.Typer(add_completion=False, no_args_is_help=True)
33
33
  app.add_typer(config_app, name="config")
34
34
 
35
- from ragx.cli import eval_cmd, inspect_cmd, models_cmd, pipeline # noqa: E402 (needs `app` defined above)
35
+ from ragx.cli import doctor_cmd, eval_cmd, inspect_cmd, models_cmd, pipeline # noqa: E402 (needs `app` defined above)
36
36
 
37
37
  pipeline.register(app)
38
38
  inspect_cmd.register(app)
39
39
  eval_cmd.register(app)
40
40
  models_cmd.register(app)
41
+ doctor_cmd.register(app)
41
42
 
42
43
 
43
44
  def _print_version() -> None:
@@ -225,4 +226,5 @@ def config_set(
225
226
 
226
227
  def main() -> None:
227
228
  logging.basicConfig(level=logging.INFO, stream=sys.stderr, format="%(levelname)s: %(message)s")
229
+ logging.getLogger("httpx").setLevel(logging.WARNING) # one INFO line per embedding batch
228
230
  app()
@@ -0,0 +1,62 @@
1
+ """`doctor`: run the config health checks and render them. Registered by app.py."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+
7
+ import typer
8
+
9
+ from ragx.cli.output import emit_json, fail, migrate_confirm
10
+ from ragx.core.config import Config, require_root
11
+ from ragx.core.doctor import FAIL, OK, SKIP, WARN, DoctorReport, run_doctor, to_doctor_json
12
+ from ragx.core.errors import RagxError
13
+ from ragx.providers.registry import make_embedder, make_generator, make_reranker
14
+
15
+ MARKS = {OK: "✓", WARN: "!", FAIL: "✗", SKIP: "-"}
16
+
17
+
18
+ def register(app: typer.Typer) -> None:
19
+ app.command()(doctor)
20
+
21
+
22
+ def doctor(
23
+ path: Path | None = typer.Argument(None),
24
+ json_out: bool = typer.Option(False, "--json"),
25
+ ) -> None:
26
+ """Check that the configured providers start and answer, and that the index matches.
27
+
28
+ Starts whatever ragx itself manages (the llama-server engines), exactly as `index`
29
+ and `query` would. Exit 0 = healthy, 1 = a check failed, 2 = doctor could not run.
30
+ """
31
+ try:
32
+ root = require_root(path)
33
+ cfg = Config.load(root, confirm=migrate_confirm())
34
+ except RagxError as exc:
35
+ fail(str(exc))
36
+ report = run_doctor(
37
+ root,
38
+ cfg,
39
+ embedder_factory=make_embedder,
40
+ generator_factory=make_generator,
41
+ reranker_factory=lambda c: make_reranker(c, strict=True),
42
+ )
43
+ if json_out:
44
+ emit_json(to_doctor_json(report))
45
+ else:
46
+ _render(report)
47
+ if not report.ok:
48
+ raise typer.Exit(code=1)
49
+
50
+
51
+ def _render(report: DoctorReport) -> None:
52
+ for check in report.checks:
53
+ secs = f" {check.elapsed_ms / 1000:.1f}s" if check.elapsed_ms >= 100 else ""
54
+ typer.echo(f"{MARKS[check.status]} {check.name:<11} {check.detail}{secs}")
55
+ if check.hint:
56
+ typer.echo(f" → {check.hint}")
57
+ failed = sum(1 for c in report.checks if c.status == FAIL)
58
+ warned = sum(1 for c in report.checks if c.status == WARN)
59
+ if failed or warned:
60
+ typer.echo(f"\n{failed} failed, {warned} warning(s)")
61
+ else:
62
+ typer.echo("\nall checks passed")