ragx-cli 0.5.0__tar.gz → 0.6.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.
- {ragx_cli-0.5.0 → ragx_cli-0.6.0}/CHANGELOG.md +16 -0
- {ragx_cli-0.5.0 → ragx_cli-0.6.0}/PKG-INFO +24 -10
- {ragx_cli-0.5.0 → ragx_cli-0.6.0}/README.md +23 -9
- ragx_cli-0.6.0/docs/proposal-models-command-interactive-flow.md +131 -0
- {ragx_cli-0.5.0 → ragx_cli-0.6.0}/pyproject.toml +1 -1
- {ragx_cli-0.5.0 → ragx_cli-0.6.0}/src/ragx/cli/app.py +54 -0
- {ragx_cli-0.5.0 → ragx_cli-0.6.0}/src/ragx/cli/models_cmd.py +72 -40
- ragx_cli-0.6.0/src/ragx/cli/models_ui.py +117 -0
- {ragx_cli-0.5.0 → ragx_cli-0.6.0}/src/ragx/core/catalog.py +52 -10
- {ragx_cli-0.5.0 → ragx_cli-0.6.0}/tests/test_cli.py +36 -1
- {ragx_cli-0.5.0 → ragx_cli-0.6.0}/tests/test_models.py +123 -0
- {ragx_cli-0.5.0 → ragx_cli-0.6.0}/.agents/skills/zvec/SKILL.md +0 -0
- {ragx_cli-0.5.0 → ragx_cli-0.6.0}/.agents/skills/zvec/api-cheatsheet.md +0 -0
- {ragx_cli-0.5.0 → ragx_cli-0.6.0}/.agents/skills/zvec/collection-management/python.md +0 -0
- {ragx_cli-0.5.0 → ragx_cli-0.6.0}/.agents/skills/zvec/collection-management/typescript.md +0 -0
- {ragx_cli-0.5.0 → ragx_cli-0.6.0}/.agents/skills/zvec/configuration.md +0 -0
- {ragx_cli-0.5.0 → ragx_cli-0.6.0}/.agents/skills/zvec/data-model.md +0 -0
- {ragx_cli-0.5.0 → ragx_cli-0.6.0}/.agents/skills/zvec/data-operations/python.md +0 -0
- {ragx_cli-0.5.0 → ragx_cli-0.6.0}/.agents/skills/zvec/data-operations/typescript.md +0 -0
- {ragx_cli-0.5.0 → ragx_cli-0.6.0}/.agents/skills/zvec/embedding.md +0 -0
- {ragx_cli-0.5.0 → ragx_cli-0.6.0}/.agents/skills/zvec/hybrid-search/python.md +0 -0
- {ragx_cli-0.5.0 → ragx_cli-0.6.0}/.agents/skills/zvec/hybrid-search/typescript.md +0 -0
- {ragx_cli-0.5.0 → ragx_cli-0.6.0}/.agents/skills/zvec/multimodal-search/python.md +0 -0
- {ragx_cli-0.5.0 → ragx_cli-0.6.0}/.agents/skills/zvec/multimodal-search/typescript.md +0 -0
- {ragx_cli-0.5.0 → ragx_cli-0.6.0}/.agents/skills/zvec/quick-start/python.md +0 -0
- {ragx_cli-0.5.0 → ragx_cli-0.6.0}/.agents/skills/zvec/quick-start/typescript.md +0 -0
- {ragx_cli-0.5.0 → ragx_cli-0.6.0}/.agents/skills/zvec/rag-system/python.md +0 -0
- {ragx_cli-0.5.0 → ragx_cli-0.6.0}/.agents/skills/zvec/rag-system/typescript.md +0 -0
- {ragx_cli-0.5.0 → ragx_cli-0.6.0}/.agents/skills/zvec/reranker.md +0 -0
- {ragx_cli-0.5.0 → ragx_cli-0.6.0}/.agents/skills/zvec/troubleshooting.md +0 -0
- {ragx_cli-0.5.0 → ragx_cli-0.6.0}/.agents/skills/zvec/vector-search/python.md +0 -0
- {ragx_cli-0.5.0 → ragx_cli-0.6.0}/.agents/skills/zvec/vector-search/typescript.md +0 -0
- {ragx_cli-0.5.0 → ragx_cli-0.6.0}/.github/workflows/publish.yml +0 -0
- {ragx_cli-0.5.0 → ragx_cli-0.6.0}/.gitignore +0 -0
- {ragx_cli-0.5.0 → ragx_cli-0.6.0}/AGENTS.md +0 -0
- {ragx_cli-0.5.0 → ragx_cli-0.6.0}/CLAUDE.md +0 -0
- {ragx_cli-0.5.0 → ragx_cli-0.6.0}/CONTRACTS-PHASE23.md +0 -0
- {ragx_cli-0.5.0 → ragx_cli-0.6.0}/CONTRACTS.md +0 -0
- {ragx_cli-0.5.0 → ragx_cli-0.6.0}/LICENSE +0 -0
- {ragx_cli-0.5.0 → ragx_cli-0.6.0}/docs/feature-temporal-weighting.md +0 -0
- {ragx_cli-0.5.0 → ragx_cli-0.6.0}/ragx-cli-plan.md +0 -0
- {ragx_cli-0.5.0 → ragx_cli-0.6.0}/research/bge-m3-colbert-multi-vector-late-interaction-for-ragx-rerank-alternative-and-token-level-graph-edges.md +0 -0
- {ragx_cli-0.5.0 → ragx_cli-0.6.0}/research/bge-m3-dense-embeddings-as-ragx-provider-multilingual-quality-and-threshold-calibration.md +0 -0
- {ragx_cli-0.5.0 → ragx_cli-0.6.0}/research/bge-m3-dense-q8-vs-nomic-q4-benchmark-2026-07-12-worktree-eval-results.md +0 -0
- {ragx_cli-0.5.0 → ragx_cli-0.6.0}/research/bge-m3-sparse-lexical-weights-hybrid-retrieval-leg-for-ragx-vs-sqlite-fts5-bm25.md +0 -0
- {ragx_cli-0.5.0 → ragx_cli-0.6.0}/research/bge-m3-subchunk-edge-ablation-2026-07-13-ties-again-and-beta-zero-shortlist-mechanism.md +0 -0
- {ragx_cli-0.5.0 → ragx_cli-0.6.0}/research/fine-grained-sub-chunk-edges-with-coarse-chunk-nodes-multi-granularity-graph-rag-literature-validation.md +0 -0
- {ragx_cli-0.5.0 → ragx_cli-0.6.0}/research/lm-studio-model-download-api-and-curated-embedding-reranker-catalog-for-ragx-models-command.md +0 -0
- {ragx_cli-0.5.0 → ragx_cli-0.6.0}/research/subchunk-edge-ablation-2026-07-12-two-round-experiment-protocol-results-and-verdict.md +0 -0
- {ragx_cli-0.5.0 → ragx_cli-0.6.0}/skills/ragx-cli/SKILL.md +0 -0
- {ragx_cli-0.5.0 → ragx_cli-0.6.0}/skills-lock.json +0 -0
- {ragx_cli-0.5.0 → ragx_cli-0.6.0}/src/ragx/__init__.py +0 -0
- {ragx_cli-0.5.0 → ragx_cli-0.6.0}/src/ragx/cli/__init__.py +0 -0
- {ragx_cli-0.5.0 → ragx_cli-0.6.0}/src/ragx/cli/detect.py +0 -0
- {ragx_cli-0.5.0 → ragx_cli-0.6.0}/src/ragx/cli/eval_cmd.py +0 -0
- {ragx_cli-0.5.0 → ragx_cli-0.6.0}/src/ragx/cli/init_prompt.py +0 -0
- {ragx_cli-0.5.0 → ragx_cli-0.6.0}/src/ragx/cli/inspect_cmd.py +0 -0
- {ragx_cli-0.5.0 → ragx_cli-0.6.0}/src/ragx/cli/output.py +0 -0
- {ragx_cli-0.5.0 → ragx_cli-0.6.0}/src/ragx/cli/pipeline.py +0 -0
- {ragx_cli-0.5.0 → ragx_cli-0.6.0}/src/ragx/core/__init__.py +0 -0
- {ragx_cli-0.5.0 → ragx_cli-0.6.0}/src/ragx/core/chunking.py +0 -0
- {ragx_cli-0.5.0 → ragx_cli-0.6.0}/src/ragx/core/communities.py +0 -0
- {ragx_cli-0.5.0 → ragx_cli-0.6.0}/src/ragx/core/config.py +0 -0
- {ragx_cli-0.5.0 → ragx_cli-0.6.0}/src/ragx/core/discovery.py +0 -0
- {ragx_cli-0.5.0 → ragx_cli-0.6.0}/src/ragx/core/errors.py +0 -0
- {ragx_cli-0.5.0 → ragx_cli-0.6.0}/src/ragx/core/eval.py +0 -0
- {ragx_cli-0.5.0 → ragx_cli-0.6.0}/src/ragx/core/expansion.py +0 -0
- {ragx_cli-0.5.0 → ragx_cli-0.6.0}/src/ragx/core/fusion.py +0 -0
- {ragx_cli-0.5.0 → ragx_cli-0.6.0}/src/ragx/core/graph.py +0 -0
- {ragx_cli-0.5.0 → ragx_cli-0.6.0}/src/ragx/core/indexer.py +0 -0
- {ragx_cli-0.5.0 → ragx_cli-0.6.0}/src/ragx/core/lmstudio.py +0 -0
- {ragx_cli-0.5.0 → ragx_cli-0.6.0}/src/ragx/core/models.py +0 -0
- {ragx_cli-0.5.0 → ragx_cli-0.6.0}/src/ragx/core/query.py +0 -0
- {ragx_cli-0.5.0 → ragx_cli-0.6.0}/src/ragx/core/scoring.py +0 -0
- {ragx_cli-0.5.0 → ragx_cli-0.6.0}/src/ragx/core/store.py +0 -0
- {ragx_cli-0.5.0 → ragx_cli-0.6.0}/src/ragx/core/traversal.py +0 -0
- {ragx_cli-0.5.0 → ragx_cli-0.6.0}/src/ragx/core/vectors.py +0 -0
- {ragx_cli-0.5.0 → ragx_cli-0.6.0}/src/ragx/providers/__init__.py +0 -0
- {ragx_cli-0.5.0 → ragx_cli-0.6.0}/src/ragx/providers/base.py +0 -0
- {ragx_cli-0.5.0 → ragx_cli-0.6.0}/src/ragx/providers/llama_embedder.py +0 -0
- {ragx_cli-0.5.0 → ragx_cli-0.6.0}/src/ragx/providers/llama_process.py +0 -0
- {ragx_cli-0.5.0 → ragx_cli-0.6.0}/src/ragx/providers/llama_server.py +0 -0
- {ragx_cli-0.5.0 → ragx_cli-0.6.0}/src/ragx/providers/openai_compat.py +0 -0
- {ragx_cli-0.5.0 → ragx_cli-0.6.0}/src/ragx/providers/registry.py +0 -0
- {ragx_cli-0.5.0 → ragx_cli-0.6.0}/src/ragx/providers/st_reranker.py +0 -0
- {ragx_cli-0.5.0 → ragx_cli-0.6.0}/tests/__init__.py +0 -0
- {ragx_cli-0.5.0 → ragx_cli-0.6.0}/tests/conftest.py +0 -0
- {ragx_cli-0.5.0 → ragx_cli-0.6.0}/tests/test_chunking.py +0 -0
- {ragx_cli-0.5.0 → ragx_cli-0.6.0}/tests/test_communities.py +0 -0
- {ragx_cli-0.5.0 → ragx_cli-0.6.0}/tests/test_config_rc.py +0 -0
- {ragx_cli-0.5.0 → ragx_cli-0.6.0}/tests/test_discovery.py +0 -0
- {ragx_cli-0.5.0 → ragx_cli-0.6.0}/tests/test_eval.py +0 -0
- {ragx_cli-0.5.0 → ragx_cli-0.6.0}/tests/test_expansion.py +0 -0
- {ragx_cli-0.5.0 → ragx_cli-0.6.0}/tests/test_fusion.py +0 -0
- {ragx_cli-0.5.0 → ragx_cli-0.6.0}/tests/test_graph.py +0 -0
- {ragx_cli-0.5.0 → ragx_cli-0.6.0}/tests/test_inspect.py +0 -0
- {ragx_cli-0.5.0 → ragx_cli-0.6.0}/tests/test_integration.py +0 -0
- {ragx_cli-0.5.0 → ragx_cli-0.6.0}/tests/test_llama_server.py +0 -0
- {ragx_cli-0.5.0 → ragx_cli-0.6.0}/tests/test_providers.py +0 -0
- {ragx_cli-0.5.0 → ragx_cli-0.6.0}/tests/test_store.py +0 -0
- {ragx_cli-0.5.0 → ragx_cli-0.6.0}/tests/test_traversal.py +0 -0
- {ragx_cli-0.5.0 → ragx_cli-0.6.0}/tests/test_vectors.py +0 -0
|
@@ -4,6 +4,22 @@ 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.6.0] — 2026-07-15
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
|
|
11
|
+
- **`ragx-cli models` interactive flow redesigned** around context → choice → consequence →
|
|
12
|
+
commit: a machine header (RAM, platform, `llama-server`/`lms` detection) prints first;
|
|
13
|
+
the quality tier is picked from a numbered comparison table (model, size, languages,
|
|
14
|
+
context, license) and the serving engines from numbered menus with one-line tradeoffs —
|
|
15
|
+
recommended defaults marked `»`, invalid input re-prompts; a plan step (download sizes,
|
|
16
|
+
`already downloaded ✓`, config sections, re-index consequence) shows before the confirm.
|
|
17
|
+
Behavior changes: on <8 GB RAM machines an interactive pick of `balanced`/`best` is
|
|
18
|
+
confirmed and then honored instead of silently downgraded (non-interactive runs keep the
|
|
19
|
+
auto-downgrade); `--quality jina-nano` without `--embed-engine` now locks the embedding
|
|
20
|
+
engine to `llama-server` instead of erroring (an explicit `--embed-engine lm-studio`
|
|
21
|
+
still fails loud). Flags, `--json` output, and the non-interactive contract are unchanged.
|
|
22
|
+
|
|
7
23
|
## [0.5.0] — 2026-07-15
|
|
8
24
|
|
|
9
25
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ragx-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.6.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 --version # version, repo, and the effective models (corpus config + ~/.ragxrc overrides)
|
|
56
57
|
```
|
|
57
58
|
|
|
58
59
|
Runs against any OpenAI-compatible embedding endpoint (LM Studio, Ollama, OpenAI). Reranking
|
|
@@ -324,8 +325,19 @@ Full write-ups: `research/bge-m3-dense-q8-vs-nomic-q4-benchmark-2026-07-12-workt
|
|
|
324
325
|
|
|
325
326
|
`ragx-cli models` recommends an embedding + reranker combo, downloads the embedding model
|
|
326
327
|
**through LM Studio** (`lms get` — one download path, plays well with restricted networks),
|
|
327
|
-
pre-fetches the reranker from Hugging Face, and writes both to `ragx.toml`.
|
|
328
|
-
|
|
328
|
+
pre-fetches the reranker from Hugging Face, and writes both to `ragx.toml`. On a TTY it walks
|
|
329
|
+
three steps — context first, consequences before anything is written:
|
|
330
|
+
|
|
331
|
+
1. **machine header** — detected RAM, platform, whether `llama-server` is on PATH and the
|
|
332
|
+
`lms` CLI was found (this is what drives the recommended defaults);
|
|
333
|
+
2. **numbered menus** — the embedding tier table (model, size, languages, context, license)
|
|
334
|
+
and one menu per serving engine with a one-line tradeoff each; the recommended default is
|
|
335
|
+
marked `»` (Enter accepts), invalid input re-prompts;
|
|
336
|
+
3. **plan** — what will be downloaded (or `already downloaded ✓`) with approximate sizes,
|
|
337
|
+
which config sections change, and whether a re-index is required — all shown *before*
|
|
338
|
+
the final confirm.
|
|
339
|
+
|
|
340
|
+
Scriptable via flags:
|
|
329
341
|
|
|
330
342
|
```bash
|
|
331
343
|
ragx-cli models # interactive
|
|
@@ -335,12 +347,12 @@ ragx-cli models --quality fast --dry-run # recommend only, change
|
|
|
335
347
|
|
|
336
348
|
The curated catalog (every entry verified working end-to-end):
|
|
337
349
|
|
|
338
|
-
| tier | embedding model | notes |
|
|
339
|
-
|
|
340
|
-
| `fast` | EmbeddingGemma 300M | 308M
|
|
341
|
-
| `balanced` | BGE-M3 | ragx's benchmarked production model
|
|
342
|
-
| `best` | Qwen3-Embedding-0.6B | 100+
|
|
343
|
-
| `jina-nano` | Jina embeddings v5 nano | 239M
|
|
350
|
+
| tier | embedding model | size | langs | ctx | notes |
|
|
351
|
+
|---|---|---|---|---|---|
|
|
352
|
+
| `fast` | EmbeddingGemma 300M | 308M | 100+ | 2K | lightest; small corpora, low RAM |
|
|
353
|
+
| `balanced` | BGE-M3 | 568M | ~100 | 8K | ragx's benchmarked production model |
|
|
354
|
+
| `best` | Qwen3-Embedding-0.6B | 0.6B | 100+ | 32K | highest quality; slower indexing |
|
|
355
|
+
| `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) |
|
|
344
356
|
|
|
345
357
|
**Serving engines.** Embeddings run through `--embed-engine`: **`llama-server`** (default when
|
|
346
358
|
llama.cpp is installed — ragx auto-spawns `llama-server --embedding` on the downloaded GGUF and
|
|
@@ -358,7 +370,9 @@ terminates it at exit; LM Studio is only the downloader) or **`lm-studio`** (LM
|
|
|
358
370
|
|
|
359
371
|
With both engines on `llama-server` the whole stack is ragx-managed: LM Studio downloads the
|
|
360
372
|
GGUFs, ragx spawns and reaps the servers, and neither LM Studio nor huggingface.co is needed
|
|
361
|
-
at query time. Under 8 GB RAM the
|
|
373
|
+
at query time. Under 8 GB RAM the recommended default moves to `fast`: interactively, picking
|
|
374
|
+
a heavier tier asks for confirmation and then **honors your choice**; non-interactive runs
|
|
375
|
+
(`--yes`/piped) auto-downgrade to `fast` with a note, since there is nobody to ask. On macOS
|
|
362
376
|
`lms get --yes` picks MLX variants where LM Studio's catalog offers them. Switching the
|
|
363
377
|
embedding model invalidates the index — the command tells you to run `ragx-cli index --full`.
|
|
364
378
|
`init` offers this flow as its final step; rerun `ragx-cli models` anytime to switch.
|
|
@@ -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 --version # version, repo, and the effective models (corpus config + ~/.ragxrc overrides)
|
|
28
29
|
```
|
|
29
30
|
|
|
30
31
|
Runs against any OpenAI-compatible embedding endpoint (LM Studio, Ollama, OpenAI). Reranking
|
|
@@ -296,8 +297,19 @@ Full write-ups: `research/bge-m3-dense-q8-vs-nomic-q4-benchmark-2026-07-12-workt
|
|
|
296
297
|
|
|
297
298
|
`ragx-cli models` recommends an embedding + reranker combo, downloads the embedding model
|
|
298
299
|
**through LM Studio** (`lms get` — one download path, plays well with restricted networks),
|
|
299
|
-
pre-fetches the reranker from Hugging Face, and writes both to `ragx.toml`.
|
|
300
|
-
|
|
300
|
+
pre-fetches the reranker from Hugging Face, and writes both to `ragx.toml`. On a TTY it walks
|
|
301
|
+
three steps — context first, consequences before anything is written:
|
|
302
|
+
|
|
303
|
+
1. **machine header** — detected RAM, platform, whether `llama-server` is on PATH and the
|
|
304
|
+
`lms` CLI was found (this is what drives the recommended defaults);
|
|
305
|
+
2. **numbered menus** — the embedding tier table (model, size, languages, context, license)
|
|
306
|
+
and one menu per serving engine with a one-line tradeoff each; the recommended default is
|
|
307
|
+
marked `»` (Enter accepts), invalid input re-prompts;
|
|
308
|
+
3. **plan** — what will be downloaded (or `already downloaded ✓`) with approximate sizes,
|
|
309
|
+
which config sections change, and whether a re-index is required — all shown *before*
|
|
310
|
+
the final confirm.
|
|
311
|
+
|
|
312
|
+
Scriptable via flags:
|
|
301
313
|
|
|
302
314
|
```bash
|
|
303
315
|
ragx-cli models # interactive
|
|
@@ -307,12 +319,12 @@ ragx-cli models --quality fast --dry-run # recommend only, change
|
|
|
307
319
|
|
|
308
320
|
The curated catalog (every entry verified working end-to-end):
|
|
309
321
|
|
|
310
|
-
| tier | embedding model | notes |
|
|
311
|
-
|
|
312
|
-
| `fast` | EmbeddingGemma 300M | 308M
|
|
313
|
-
| `balanced` | BGE-M3 | ragx's benchmarked production model
|
|
314
|
-
| `best` | Qwen3-Embedding-0.6B | 100+
|
|
315
|
-
| `jina-nano` | Jina embeddings v5 nano | 239M
|
|
322
|
+
| tier | embedding model | size | langs | ctx | notes |
|
|
323
|
+
|---|---|---|---|---|---|
|
|
324
|
+
| `fast` | EmbeddingGemma 300M | 308M | 100+ | 2K | lightest; small corpora, low RAM |
|
|
325
|
+
| `balanced` | BGE-M3 | 568M | ~100 | 8K | ragx's benchmarked production model |
|
|
326
|
+
| `best` | Qwen3-Embedding-0.6B | 0.6B | 100+ | 32K | highest quality; slower indexing |
|
|
327
|
+
| `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) |
|
|
316
328
|
|
|
317
329
|
**Serving engines.** Embeddings run through `--embed-engine`: **`llama-server`** (default when
|
|
318
330
|
llama.cpp is installed — ragx auto-spawns `llama-server --embedding` on the downloaded GGUF and
|
|
@@ -330,7 +342,9 @@ terminates it at exit; LM Studio is only the downloader) or **`lm-studio`** (LM
|
|
|
330
342
|
|
|
331
343
|
With both engines on `llama-server` the whole stack is ragx-managed: LM Studio downloads the
|
|
332
344
|
GGUFs, ragx spawns and reaps the servers, and neither LM Studio nor huggingface.co is needed
|
|
333
|
-
at query time. Under 8 GB RAM the
|
|
345
|
+
at query time. Under 8 GB RAM the recommended default moves to `fast`: interactively, picking
|
|
346
|
+
a heavier tier asks for confirmation and then **honors your choice**; non-interactive runs
|
|
347
|
+
(`--yes`/piped) auto-downgrade to `fast` with a note, since there is nobody to ask. On macOS
|
|
334
348
|
`lms get --yes` picks MLX variants where LM Studio's catalog offers them. Switching the
|
|
335
349
|
embedding model invalidates the index — the command tells you to run `ragx-cli index --full`.
|
|
336
350
|
`init` offers this flow as its final step; rerun `ragx-cli models` anytime to switch.
|
|
@@ -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.
|
|
@@ -4,6 +4,7 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import logging
|
|
6
6
|
import sys
|
|
7
|
+
from importlib import metadata
|
|
7
8
|
from pathlib import Path
|
|
8
9
|
|
|
9
10
|
import typer
|
|
@@ -16,6 +17,8 @@ from ragx.core.config import (
|
|
|
16
17
|
CONFIG_FILE,
|
|
17
18
|
config_path,
|
|
18
19
|
db_path,
|
|
20
|
+
find_root,
|
|
21
|
+
load_rc,
|
|
19
22
|
rc_path,
|
|
20
23
|
require_root,
|
|
21
24
|
write_default_config,
|
|
@@ -23,6 +26,8 @@ from ragx.core.config import (
|
|
|
23
26
|
)
|
|
24
27
|
from ragx.core.errors import RagxError
|
|
25
28
|
|
|
29
|
+
REPO_URL = "https://github.com/alber70g/ragx-cli"
|
|
30
|
+
|
|
26
31
|
app = typer.Typer(add_completion=False, no_args_is_help=True)
|
|
27
32
|
config_app = typer.Typer(add_completion=False, no_args_is_help=True)
|
|
28
33
|
app.add_typer(config_app, name="config")
|
|
@@ -35,6 +40,55 @@ eval_cmd.register(app)
|
|
|
35
40
|
models_cmd.register(app)
|
|
36
41
|
|
|
37
42
|
|
|
43
|
+
def _print_version() -> None:
|
|
44
|
+
try:
|
|
45
|
+
version = metadata.version("ragx-cli")
|
|
46
|
+
except metadata.PackageNotFoundError:
|
|
47
|
+
version = "unknown"
|
|
48
|
+
typer.echo(f"ragx-cli {version}")
|
|
49
|
+
typer.echo(REPO_URL)
|
|
50
|
+
root = find_root()
|
|
51
|
+
if root is not None:
|
|
52
|
+
try:
|
|
53
|
+
cfg = Config.load(root, confirm=migrate_confirm())
|
|
54
|
+
typer.echo(f"config: {config_path(root)}")
|
|
55
|
+
for section in ("embeddings", "expansion", "rerank"):
|
|
56
|
+
line = f" {section}: {cfg.get(f'{section}.provider')}/{cfg.get(f'{section}.model')}"
|
|
57
|
+
if cfg.data[section].get("enabled") is False:
|
|
58
|
+
line += " (disabled)"
|
|
59
|
+
typer.echo(line)
|
|
60
|
+
except RagxError as exc:
|
|
61
|
+
typer.echo(f"config: unreadable ({exc})")
|
|
62
|
+
rc = rc_path()
|
|
63
|
+
if rc.exists():
|
|
64
|
+
typer.echo(f"global: {rc}")
|
|
65
|
+
try:
|
|
66
|
+
for section, values in load_rc().items():
|
|
67
|
+
for key, value in values.items():
|
|
68
|
+
typer.echo(f" {section}.{key} = {value}")
|
|
69
|
+
except RagxError as exc:
|
|
70
|
+
typer.echo(f" unreadable ({exc})")
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _version_option(value: bool) -> None:
|
|
74
|
+
if value:
|
|
75
|
+
_print_version()
|
|
76
|
+
raise typer.Exit()
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
@app.callback()
|
|
80
|
+
def _main(
|
|
81
|
+
version: bool = typer.Option(
|
|
82
|
+
False,
|
|
83
|
+
"--version",
|
|
84
|
+
callback=_version_option,
|
|
85
|
+
is_eager=True,
|
|
86
|
+
help="show version, repo, and configured models (local + ~/.ragxrc)",
|
|
87
|
+
),
|
|
88
|
+
) -> None:
|
|
89
|
+
"""Similarity-graph RAG for your files."""
|
|
90
|
+
|
|
91
|
+
|
|
38
92
|
def _require_root() -> Path:
|
|
39
93
|
try:
|
|
40
94
|
return require_root()
|
|
@@ -4,11 +4,13 @@ onto the main app by app.py; `init` routes here as an optional final step."""
|
|
|
4
4
|
|
|
5
5
|
from __future__ import annotations
|
|
6
6
|
|
|
7
|
+
import shutil
|
|
7
8
|
import sys
|
|
8
9
|
from pathlib import Path
|
|
9
10
|
|
|
10
11
|
import typer
|
|
11
12
|
|
|
13
|
+
from ragx.cli import models_ui as ui
|
|
12
14
|
from ragx.cli.output import emit_json, fail, migrate_confirm
|
|
13
15
|
from ragx.core import lmstudio
|
|
14
16
|
from ragx.core.catalog import QUALITY_TIERS, detect_specs, recommend
|
|
@@ -61,27 +63,40 @@ def run_flow(
|
|
|
61
63
|
"""The actual flow, callable from `init`. Raises RagxError on failure."""
|
|
62
64
|
cfg = Config.load(root, confirm=migrate_confirm())
|
|
63
65
|
interactive = sys.stdin.isatty() and not yes
|
|
66
|
+
specs = detect_specs()
|
|
67
|
+
llama_found = shutil.which("llama-server") is not None
|
|
68
|
+
lms = lmstudio.find_lms() if (interactive or not dry_run) else None
|
|
69
|
+
ui.header(specs, llama_found, lms_found=None if (dry_run and not interactive) else lms is not None)
|
|
70
|
+
|
|
64
71
|
if quality is None:
|
|
65
72
|
if not interactive:
|
|
66
73
|
raise RagxError(f"--quality required when not interactive ({'/'.join(QUALITY_TIERS)})")
|
|
67
|
-
quality =
|
|
68
|
-
|
|
69
|
-
"rerank engine", rerank_engine, ("llama-server", "sentence-transformers"),
|
|
70
|
-
interactive, fallback="sentence-transformers",
|
|
71
|
-
)
|
|
72
|
-
emb_engine = _pick_engine(
|
|
73
|
-
"embedding engine", embed_engine, ("llama-server", "lm-studio"),
|
|
74
|
-
interactive, fallback="lm-studio",
|
|
75
|
-
)
|
|
74
|
+
quality = ui.pick_tier(specs)
|
|
75
|
+
embedding, reranker, notes = recommend(quality, specs, enforce_ram=not interactive)
|
|
76
76
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
77
|
+
if embedding.requires_llama_server and embed_engine != "llama-server":
|
|
78
|
+
if embed_engine is not None:
|
|
79
|
+
raise RagxError(
|
|
80
|
+
f"{embedding.label} can only be served by the llama-server engine "
|
|
81
|
+
"(LM Studio downloads it but cannot serve EuroBERT models) — "
|
|
82
|
+
"rerun with --embed-engine llama-server"
|
|
83
|
+
)
|
|
84
|
+
emb_engine = "llama-server"
|
|
85
|
+
lock_note = f"embedding engine locked to llama-server — LM Studio cannot serve {embedding.label}"
|
|
86
|
+
if interactive:
|
|
87
|
+
ui.echo(f"\n → {lock_note}")
|
|
88
|
+
else:
|
|
89
|
+
notes.append(lock_note)
|
|
90
|
+
else:
|
|
91
|
+
emb_engine = _resolve_engine(
|
|
92
|
+
"embedding engine", embed_engine, ui.EMBED_ENGINES,
|
|
93
|
+
interactive, llama_found, fallback="lm-studio",
|
|
84
94
|
)
|
|
95
|
+
engine = _resolve_engine(
|
|
96
|
+
"rerank engine", rerank_engine, ui.RERANK_ENGINES,
|
|
97
|
+
interactive, llama_found, fallback="sentence-transformers",
|
|
98
|
+
)
|
|
99
|
+
|
|
85
100
|
engine_desc = (
|
|
86
101
|
"llama.cpp llama-server, GGUF downloaded via LM Studio — no huggingface.co needed"
|
|
87
102
|
if engine == "llama-server"
|
|
@@ -90,8 +105,8 @@ def run_flow(
|
|
|
90
105
|
emb_desc = (
|
|
91
106
|
"llama-server auto-spawned by ragx" if emb_engine == "llama-server" else "served by LM Studio"
|
|
92
107
|
)
|
|
93
|
-
|
|
94
|
-
_echo(f"embedding: {embedding.label} ({
|
|
108
|
+
spec_line = f"{embedding.params}, {embedding.languages} languages, {embedding.context} context"
|
|
109
|
+
_echo(f"embedding: {embedding.label} ({spec_line}) — {emb_desc}")
|
|
95
110
|
_echo(f"reranker: {reranker.label} ({reranker.notes}) — via {engine_desc}")
|
|
96
111
|
for note in notes:
|
|
97
112
|
_echo(f"note: {note}")
|
|
@@ -100,23 +115,39 @@ def run_flow(
|
|
|
100
115
|
_finish(json_out, embedding, reranker, notes, engine=engine, emb_engine=emb_engine,
|
|
101
116
|
downloaded=False, config_updated=False, reindex_required=False)
|
|
102
117
|
return
|
|
103
|
-
if interactive and not typer.confirm("download and update ragx.toml?", default=True):
|
|
104
|
-
raise typer.Exit(code=0)
|
|
105
118
|
|
|
106
|
-
lms = lmstudio.find_lms()
|
|
107
119
|
if lms is None:
|
|
108
120
|
raise RagxError(INSTALL_HINT)
|
|
109
121
|
|
|
122
|
+
emb_frag = embedding.gguf_fragment if emb_engine == "llama-server" else embedding.repo_fragment
|
|
123
|
+
emb_pre = lmstudio.find_installed(lms, emb_frag)
|
|
124
|
+
rr_pre = lmstudio.find_installed(lms, reranker.gguf_fragment) if engine == "llama-server" else None
|
|
125
|
+
|
|
126
|
+
if interactive:
|
|
127
|
+
if emb_pre is not None and emb_pre.model_key == cfg.get("embeddings.model"):
|
|
128
|
+
reindex_line = "not needed — embedding model unchanged"
|
|
129
|
+
else:
|
|
130
|
+
reindex_line = "REQUIRED — embedding model changes; run `ragx-cli index --full` after"
|
|
131
|
+
ui.show_plan(embedding=embedding, reranker=reranker, emb_engine=emb_engine,
|
|
132
|
+
rerank_engine=engine, emb_installed=emb_pre is not None,
|
|
133
|
+
rr_installed=rr_pre is not None, reindex_line=reindex_line)
|
|
134
|
+
if not typer.confirm(" proceed — download and update ragx.toml?", default=True, err=True):
|
|
135
|
+
raise typer.Exit(code=0)
|
|
136
|
+
|
|
110
137
|
embed_updates: list[tuple[str, str]]
|
|
111
138
|
if emb_engine == "llama-server":
|
|
112
|
-
installed, downloaded = _ensure_downloaded(
|
|
139
|
+
installed, downloaded = _ensure_downloaded(
|
|
140
|
+
lms, embedding.gguf_ref, embedding.gguf_fragment, pre=emb_pre
|
|
141
|
+
)
|
|
113
142
|
embed_updates = [
|
|
114
143
|
("embeddings.provider", "llama-server"),
|
|
115
144
|
("embeddings.gguf", str(lmstudio.models_root() / installed.path)),
|
|
116
145
|
("embeddings.base_url", "http://127.0.0.1:9813/v1"),
|
|
117
146
|
]
|
|
118
147
|
else:
|
|
119
|
-
installed, downloaded = _ensure_downloaded(
|
|
148
|
+
installed, downloaded = _ensure_downloaded(
|
|
149
|
+
lms, embedding.ref, embedding.repo_fragment, pre=emb_pre
|
|
150
|
+
)
|
|
120
151
|
embed_updates = [
|
|
121
152
|
("embeddings.provider", "openai"),
|
|
122
153
|
("embeddings.gguf", ""),
|
|
@@ -125,7 +156,9 @@ def run_flow(
|
|
|
125
156
|
|
|
126
157
|
rerank_updates: list[tuple[str, str]]
|
|
127
158
|
if engine == "llama-server":
|
|
128
|
-
gguf_model, gguf_downloaded = _ensure_downloaded(
|
|
159
|
+
gguf_model, gguf_downloaded = _ensure_downloaded(
|
|
160
|
+
lms, reranker.gguf_ref, reranker.gguf_fragment, pre=rr_pre
|
|
161
|
+
)
|
|
129
162
|
downloaded = downloaded or gguf_downloaded
|
|
130
163
|
gguf_path = str(lmstudio.models_root() / gguf_model.path)
|
|
131
164
|
rerank_updates = [("rerank.provider", "llama-server"), ("rerank.gguf", gguf_path),
|
|
@@ -156,29 +189,28 @@ def run_flow(
|
|
|
156
189
|
model_key=installed.model_key)
|
|
157
190
|
|
|
158
191
|
|
|
159
|
-
def
|
|
160
|
-
label: str, value: str | None,
|
|
192
|
+
def _resolve_engine(
|
|
193
|
+
label: str, value: str | None, options: tuple[tuple[str, str], ...],
|
|
194
|
+
interactive: bool, llama_found: bool, *, fallback: str,
|
|
161
195
|
) -> str:
|
|
162
|
-
"""Explicit flag wins;
|
|
163
|
-
(fully ragx-managed, no huggingface.co),
|
|
196
|
+
"""Explicit flag wins; interactive shows a numbered menu; otherwise default to
|
|
197
|
+
llama-server when the binary is present (fully ragx-managed, no huggingface.co),
|
|
198
|
+
falling back to `fallback`."""
|
|
199
|
+
names = tuple(name for name, _ in options)
|
|
164
200
|
if value is not None:
|
|
165
|
-
if value not in
|
|
166
|
-
raise RagxError(f"{label} must be one of {'/'.join(
|
|
201
|
+
if value not in names:
|
|
202
|
+
raise RagxError(f"{label} must be one of {'/'.join(names)}")
|
|
167
203
|
return value
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
default = "llama-server" if shutil.which("llama-server") else fallback
|
|
204
|
+
default = "llama-server" if llama_found else fallback
|
|
171
205
|
if interactive:
|
|
172
|
-
|
|
173
|
-
if picked not in engines:
|
|
174
|
-
raise RagxError(f"{label} must be one of {'/'.join(engines)}")
|
|
175
|
-
return picked
|
|
206
|
+
return ui.pick_engine(label, options, default, llama_found)
|
|
176
207
|
return default
|
|
177
208
|
|
|
178
209
|
|
|
179
|
-
def _ensure_downloaded(lms: str, ref: str, fragment: str):
|
|
180
|
-
"""Download `ref` via LM Studio unless a model matching `fragment` is already there
|
|
181
|
-
|
|
210
|
+
def _ensure_downloaded(lms: str, ref: str, fragment: str, pre: lmstudio.InstalledModel | None = None):
|
|
211
|
+
"""Download `ref` via LM Studio unless a model matching `fragment` is already there
|
|
212
|
+
(`pre` = an earlier find_installed result to reuse)."""
|
|
213
|
+
installed = pre if pre is not None else lmstudio.find_installed(lms, fragment)
|
|
182
214
|
if installed is not None:
|
|
183
215
|
_echo(f"already downloaded: {installed.model_key}")
|
|
184
216
|
return installed, False
|