ragx-cli 0.6.0__tar.gz → 0.8.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.6.0 → ragx_cli-0.8.0}/CHANGELOG.md +67 -0
- {ragx_cli-0.6.0 → ragx_cli-0.8.0}/CLAUDE.md +27 -8
- {ragx_cli-0.6.0 → ragx_cli-0.8.0}/PKG-INFO +94 -21
- {ragx_cli-0.6.0 → ragx_cli-0.8.0}/README.md +92 -19
- {ragx_cli-0.6.0 → ragx_cli-0.8.0}/pyproject.toml +2 -2
- {ragx_cli-0.6.0 → ragx_cli-0.8.0}/skills/ragx-cli/SKILL.md +14 -9
- {ragx_cli-0.6.0 → ragx_cli-0.8.0}/src/ragx/cli/app.py +11 -1
- ragx_cli-0.8.0/src/ragx/cli/doctor_cmd.py +62 -0
- {ragx_cli-0.6.0 → ragx_cli-0.8.0}/src/ragx/cli/eval_cmd.py +2 -4
- {ragx_cli-0.6.0 → ragx_cli-0.8.0}/src/ragx/cli/pipeline.py +35 -11
- {ragx_cli-0.6.0 → ragx_cli-0.8.0}/src/ragx/core/catalog.py +2 -2
- {ragx_cli-0.6.0 → ragx_cli-0.8.0}/src/ragx/core/config.py +24 -2
- ragx_cli-0.8.0/src/ragx/core/corpus_globs.py +29 -0
- ragx_cli-0.8.0/src/ragx/core/doctor.py +204 -0
- ragx_cli-0.8.0/src/ragx/core/grouping.py +31 -0
- {ragx_cli-0.6.0 → ragx_cli-0.8.0}/src/ragx/core/lmstudio.py +49 -13
- {ragx_cli-0.6.0 → ragx_cli-0.8.0}/src/ragx/core/models.py +11 -2
- {ragx_cli-0.6.0 → ragx_cli-0.8.0}/src/ragx/core/query.py +42 -39
- {ragx_cli-0.6.0 → ragx_cli-0.8.0}/src/ragx/providers/llama_process.py +12 -1
- ragx_cli-0.8.0/src/ragx/providers/lmstudio_process.py +120 -0
- {ragx_cli-0.6.0 → ragx_cli-0.8.0}/src/ragx/providers/registry.py +11 -17
- ragx_cli-0.8.0/tests/conftest.py +24 -0
- {ragx_cli-0.6.0 → ragx_cli-0.8.0}/tests/test_cli.py +7 -1
- {ragx_cli-0.6.0 → ragx_cli-0.8.0}/tests/test_discovery.py +22 -0
- ragx_cli-0.8.0/tests/test_doctor.py +231 -0
- {ragx_cli-0.6.0 → ragx_cli-0.8.0}/tests/test_eval.py +3 -1
- ragx_cli-0.8.0/tests/test_grouping.py +55 -0
- {ragx_cli-0.6.0 → ragx_cli-0.8.0}/tests/test_integration.py +15 -11
- {ragx_cli-0.6.0 → ragx_cli-0.8.0}/tests/test_llama_server.py +43 -0
- ragx_cli-0.8.0/tests/test_lmstudio_autostart.py +142 -0
- {ragx_cli-0.6.0 → ragx_cli-0.8.0}/tests/test_models.py +5 -5
- ragx_cli-0.8.0/tests/test_query_cmd.py +53 -0
- ragx_cli-0.6.0/tests/conftest.py +0 -13
- {ragx_cli-0.6.0 → ragx_cli-0.8.0}/.agents/skills/zvec/SKILL.md +0 -0
- {ragx_cli-0.6.0 → ragx_cli-0.8.0}/.agents/skills/zvec/api-cheatsheet.md +0 -0
- {ragx_cli-0.6.0 → ragx_cli-0.8.0}/.agents/skills/zvec/collection-management/python.md +0 -0
- {ragx_cli-0.6.0 → ragx_cli-0.8.0}/.agents/skills/zvec/collection-management/typescript.md +0 -0
- {ragx_cli-0.6.0 → ragx_cli-0.8.0}/.agents/skills/zvec/configuration.md +0 -0
- {ragx_cli-0.6.0 → ragx_cli-0.8.0}/.agents/skills/zvec/data-model.md +0 -0
- {ragx_cli-0.6.0 → ragx_cli-0.8.0}/.agents/skills/zvec/data-operations/python.md +0 -0
- {ragx_cli-0.6.0 → ragx_cli-0.8.0}/.agents/skills/zvec/data-operations/typescript.md +0 -0
- {ragx_cli-0.6.0 → ragx_cli-0.8.0}/.agents/skills/zvec/embedding.md +0 -0
- {ragx_cli-0.6.0 → ragx_cli-0.8.0}/.agents/skills/zvec/hybrid-search/python.md +0 -0
- {ragx_cli-0.6.0 → ragx_cli-0.8.0}/.agents/skills/zvec/hybrid-search/typescript.md +0 -0
- {ragx_cli-0.6.0 → ragx_cli-0.8.0}/.agents/skills/zvec/multimodal-search/python.md +0 -0
- {ragx_cli-0.6.0 → ragx_cli-0.8.0}/.agents/skills/zvec/multimodal-search/typescript.md +0 -0
- {ragx_cli-0.6.0 → ragx_cli-0.8.0}/.agents/skills/zvec/quick-start/python.md +0 -0
- {ragx_cli-0.6.0 → ragx_cli-0.8.0}/.agents/skills/zvec/quick-start/typescript.md +0 -0
- {ragx_cli-0.6.0 → ragx_cli-0.8.0}/.agents/skills/zvec/rag-system/python.md +0 -0
- {ragx_cli-0.6.0 → ragx_cli-0.8.0}/.agents/skills/zvec/rag-system/typescript.md +0 -0
- {ragx_cli-0.6.0 → ragx_cli-0.8.0}/.agents/skills/zvec/reranker.md +0 -0
- {ragx_cli-0.6.0 → ragx_cli-0.8.0}/.agents/skills/zvec/troubleshooting.md +0 -0
- {ragx_cli-0.6.0 → ragx_cli-0.8.0}/.agents/skills/zvec/vector-search/python.md +0 -0
- {ragx_cli-0.6.0 → ragx_cli-0.8.0}/.agents/skills/zvec/vector-search/typescript.md +0 -0
- {ragx_cli-0.6.0 → ragx_cli-0.8.0}/.github/workflows/publish.yml +0 -0
- {ragx_cli-0.6.0 → ragx_cli-0.8.0}/.gitignore +0 -0
- {ragx_cli-0.6.0 → ragx_cli-0.8.0}/AGENTS.md +0 -0
- {ragx_cli-0.6.0 → ragx_cli-0.8.0}/CONTRACTS-PHASE23.md +0 -0
- {ragx_cli-0.6.0 → ragx_cli-0.8.0}/CONTRACTS.md +0 -0
- {ragx_cli-0.6.0 → ragx_cli-0.8.0}/LICENSE +0 -0
- {ragx_cli-0.6.0 → ragx_cli-0.8.0}/docs/feature-temporal-weighting.md +0 -0
- {ragx_cli-0.6.0 → ragx_cli-0.8.0}/docs/proposal-models-command-interactive-flow.md +0 -0
- {ragx_cli-0.6.0 → ragx_cli-0.8.0}/ragx-cli-plan.md +0 -0
- {ragx_cli-0.6.0 → ragx_cli-0.8.0}/research/bge-m3-colbert-multi-vector-late-interaction-for-ragx-rerank-alternative-and-token-level-graph-edges.md +0 -0
- {ragx_cli-0.6.0 → ragx_cli-0.8.0}/research/bge-m3-dense-embeddings-as-ragx-provider-multilingual-quality-and-threshold-calibration.md +0 -0
- {ragx_cli-0.6.0 → ragx_cli-0.8.0}/research/bge-m3-dense-q8-vs-nomic-q4-benchmark-2026-07-12-worktree-eval-results.md +0 -0
- {ragx_cli-0.6.0 → ragx_cli-0.8.0}/research/bge-m3-sparse-lexical-weights-hybrid-retrieval-leg-for-ragx-vs-sqlite-fts5-bm25.md +0 -0
- {ragx_cli-0.6.0 → ragx_cli-0.8.0}/research/bge-m3-subchunk-edge-ablation-2026-07-13-ties-again-and-beta-zero-shortlist-mechanism.md +0 -0
- {ragx_cli-0.6.0 → ragx_cli-0.8.0}/research/fine-grained-sub-chunk-edges-with-coarse-chunk-nodes-multi-granularity-graph-rag-literature-validation.md +0 -0
- {ragx_cli-0.6.0 → ragx_cli-0.8.0}/research/lm-studio-model-download-api-and-curated-embedding-reranker-catalog-for-ragx-models-command.md +0 -0
- {ragx_cli-0.6.0 → ragx_cli-0.8.0}/research/subchunk-edge-ablation-2026-07-12-two-round-experiment-protocol-results-and-verdict.md +0 -0
- {ragx_cli-0.6.0 → ragx_cli-0.8.0}/skills-lock.json +0 -0
- {ragx_cli-0.6.0 → ragx_cli-0.8.0}/src/ragx/__init__.py +0 -0
- {ragx_cli-0.6.0 → ragx_cli-0.8.0}/src/ragx/cli/__init__.py +0 -0
- {ragx_cli-0.6.0 → ragx_cli-0.8.0}/src/ragx/cli/detect.py +0 -0
- {ragx_cli-0.6.0 → ragx_cli-0.8.0}/src/ragx/cli/init_prompt.py +0 -0
- {ragx_cli-0.6.0 → ragx_cli-0.8.0}/src/ragx/cli/inspect_cmd.py +0 -0
- {ragx_cli-0.6.0 → ragx_cli-0.8.0}/src/ragx/cli/models_cmd.py +0 -0
- {ragx_cli-0.6.0 → ragx_cli-0.8.0}/src/ragx/cli/models_ui.py +0 -0
- {ragx_cli-0.6.0 → ragx_cli-0.8.0}/src/ragx/cli/output.py +0 -0
- {ragx_cli-0.6.0 → ragx_cli-0.8.0}/src/ragx/core/__init__.py +0 -0
- {ragx_cli-0.6.0 → ragx_cli-0.8.0}/src/ragx/core/chunking.py +0 -0
- {ragx_cli-0.6.0 → ragx_cli-0.8.0}/src/ragx/core/communities.py +0 -0
- {ragx_cli-0.6.0 → ragx_cli-0.8.0}/src/ragx/core/discovery.py +0 -0
- {ragx_cli-0.6.0 → ragx_cli-0.8.0}/src/ragx/core/errors.py +0 -0
- {ragx_cli-0.6.0 → ragx_cli-0.8.0}/src/ragx/core/eval.py +0 -0
- {ragx_cli-0.6.0 → ragx_cli-0.8.0}/src/ragx/core/expansion.py +0 -0
- {ragx_cli-0.6.0 → ragx_cli-0.8.0}/src/ragx/core/fusion.py +0 -0
- {ragx_cli-0.6.0 → ragx_cli-0.8.0}/src/ragx/core/graph.py +0 -0
- {ragx_cli-0.6.0 → ragx_cli-0.8.0}/src/ragx/core/indexer.py +0 -0
- {ragx_cli-0.6.0 → ragx_cli-0.8.0}/src/ragx/core/scoring.py +0 -0
- {ragx_cli-0.6.0 → ragx_cli-0.8.0}/src/ragx/core/store.py +0 -0
- {ragx_cli-0.6.0 → ragx_cli-0.8.0}/src/ragx/core/traversal.py +0 -0
- {ragx_cli-0.6.0 → ragx_cli-0.8.0}/src/ragx/core/vectors.py +0 -0
- {ragx_cli-0.6.0 → ragx_cli-0.8.0}/src/ragx/providers/__init__.py +0 -0
- {ragx_cli-0.6.0 → ragx_cli-0.8.0}/src/ragx/providers/base.py +0 -0
- {ragx_cli-0.6.0 → ragx_cli-0.8.0}/src/ragx/providers/llama_embedder.py +0 -0
- {ragx_cli-0.6.0 → ragx_cli-0.8.0}/src/ragx/providers/llama_server.py +0 -0
- {ragx_cli-0.6.0 → ragx_cli-0.8.0}/src/ragx/providers/openai_compat.py +0 -0
- {ragx_cli-0.6.0 → ragx_cli-0.8.0}/src/ragx/providers/st_reranker.py +0 -0
- {ragx_cli-0.6.0 → ragx_cli-0.8.0}/tests/__init__.py +0 -0
- {ragx_cli-0.6.0 → ragx_cli-0.8.0}/tests/test_chunking.py +0 -0
- {ragx_cli-0.6.0 → ragx_cli-0.8.0}/tests/test_communities.py +0 -0
- {ragx_cli-0.6.0 → ragx_cli-0.8.0}/tests/test_config_rc.py +0 -0
- {ragx_cli-0.6.0 → ragx_cli-0.8.0}/tests/test_expansion.py +0 -0
- {ragx_cli-0.6.0 → ragx_cli-0.8.0}/tests/test_fusion.py +0 -0
- {ragx_cli-0.6.0 → ragx_cli-0.8.0}/tests/test_graph.py +0 -0
- {ragx_cli-0.6.0 → ragx_cli-0.8.0}/tests/test_inspect.py +0 -0
- {ragx_cli-0.6.0 → ragx_cli-0.8.0}/tests/test_providers.py +0 -0
- {ragx_cli-0.6.0 → ragx_cli-0.8.0}/tests/test_store.py +0 -0
- {ragx_cli-0.6.0 → ragx_cli-0.8.0}/tests/test_traversal.py +0 -0
- {ragx_cli-0.6.0 → ragx_cli-0.8.0}/tests/test_vectors.py +0 -0
|
@@ -4,6 +4,73 @@ 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.8.0] — 2026-10-20
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
|
|
11
|
+
- **`query` defaults to vector search + graph traversal.** Query expansion and
|
|
12
|
+
cross-encoder rerank are now opt-in via `--expand` and `--rerank` (`QueryOptions`
|
|
13
|
+
defaults `expand=False`, `rerank=False`). A default query makes no LLM call and loads
|
|
14
|
+
no reranker. `--no-graph` is unchanged. `--no-expand` and `--no-rerank` still parse
|
|
15
|
+
as deprecated no-ops with a stderr warning. For the previous behaviour pass
|
|
16
|
+
`--expand --rerank`.
|
|
17
|
+
- **Default corpus globs index source code and text docs only.** `corpus.include` lists
|
|
18
|
+
~70 source extensions, `Dockerfile`, `Makefile`, and `*.md`/`*.mdx`/`*.rst`/`*.txt`/
|
|
19
|
+
`*.adoc`/`*.org`; `corpus.exclude` skips lockfiles, `requirements*.txt`,
|
|
20
|
+
`constraints*.txt`, licenses, minified files, source maps, and `dist/`, `build/`,
|
|
21
|
+
`target/`, `coverage/`, `vendor/`. Applies to new `ragx-cli init` configs; an existing
|
|
22
|
+
`ragx.toml` keeps its lists.
|
|
23
|
+
|
|
24
|
+
- **Query results are one entry per file (`ragx.query.v2`, breaking).** A file whose
|
|
25
|
+
chunks would appear several times in the ranked list becomes one entry with up to 3
|
|
26
|
+
sub-entries; its score is their sum, so it ranks higher. Only chunks within the visible
|
|
27
|
+
window (ranked chunks until `--top` distinct files are seen) count. JSON results are
|
|
28
|
+
`{file, score, chunks: [...]}`; chunks no longer repeat `file`. `--files-only`
|
|
29
|
+
(`ragx.files.v1`) keeps its shape and now returns the same entries. On the notes eval
|
|
30
|
+
corpus: baseline MRR .646 → .665, graph config r@5 .808 → .923 and MRR .432 → .653.
|
|
31
|
+
- **`--top` counts result entries (files), default 20** (`query.top`, was 8 chunks).
|
|
32
|
+
Existing `ragx.toml` files keep their own `query.top`. `ragx-cli eval` no longer
|
|
33
|
+
over-fetches `top × 3` chunks.
|
|
34
|
+
|
|
35
|
+
### Added
|
|
36
|
+
|
|
37
|
+
- `ragx-cli init` prints a note that an AI agent can tailor `corpus.include`/`exclude`
|
|
38
|
+
to the project and add a CLAUDE.md/AGENTS.md section on using `ragx-cli query`.
|
|
39
|
+
|
|
40
|
+
## [0.7.0] — 2026-09-17
|
|
41
|
+
|
|
42
|
+
### Added
|
|
43
|
+
|
|
44
|
+
- **`ragx-cli doctor`**: runs the configured providers for real — embeds a probe string,
|
|
45
|
+
scores a probe pair with the reranker, and checks the index manifest and corpus drift —
|
|
46
|
+
then reports every stage with an actionable hint. Starts whatever ragx manages, exactly
|
|
47
|
+
as `index`/`query` would. Exit `0` healthy, `1` a check failed, `2` doctor could not run
|
|
48
|
+
(no corpus, unreadable config); warnings (no index yet, drift, a model the server does
|
|
49
|
+
not list but may JIT-load) do not fail the run. `--json` emits `ragx.doctor.v1` with a
|
|
50
|
+
`status`/`detail`/`hint`/`elapsed_ms` entry per check.
|
|
51
|
+
- **LM Studio autostart**: with `provider = "openai"` and a local `base_url`, `index`,
|
|
52
|
+
`query`, and `doctor` now run `lms server start` and `lms load <model>` before their
|
|
53
|
+
first request, so a cold machine needs no manual setup. ragx already spawned the
|
|
54
|
+
`llama-server` engines; LM Studio is an app ragx hands off to, so it is started but
|
|
55
|
+
never stopped. Only local URLs are touched (a cloud endpoint, including one set via
|
|
56
|
+
`OPENAI_BASE_URL`, is never started), an already-running server is left alone, and a
|
|
57
|
+
model that is not downloaded fails loud naming the models the machine does have.
|
|
58
|
+
Opt out with `embeddings.autostart` / `expansion.autostart` (default `true`, settable
|
|
59
|
+
machine-wide via `ragx-cli config set --global`).
|
|
60
|
+
|
|
61
|
+
### Fixed
|
|
62
|
+
|
|
63
|
+
- **`llama-server` returned 500 on every real query.** The server was spawned without
|
|
64
|
+
`-b`/`-ub`/`-c`, leaving llama.cpp's default 512-token physical batch in place; a rerank
|
|
65
|
+
or embedding input is non-causal and cannot be split across ubatches, so ragx's
|
|
66
|
+
~800-token chunks overflowed it ("input is too large to process"). Two-document smoke
|
|
67
|
+
tests passed, which is why it went unnoticed. Now spawned with
|
|
68
|
+
`--parallel 1 -c 0 -b 8192 -ub 8192` — one slot with the model's full trained context.
|
|
69
|
+
Fixes the `llama-server` embedding engine at default chunk size as well as rerank.
|
|
70
|
+
- **`ragx-cli models --quality balanced` could not download BGE-M3.** `gaianet/bge-m3-GGUF`
|
|
71
|
+
now answers 401 from the Hugging Face API; both refs point at `gpustack/bge-m3-GGUF@Q8_0`
|
|
72
|
+
(same model, already the catalog's reranker source, pinned to the benchmarked quant).
|
|
73
|
+
|
|
7
74
|
## [0.6.0] — 2026-07-15
|
|
8
75
|
|
|
9
76
|
### Changed
|
|
@@ -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,22 +68,26 @@ 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;
|
|
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/ #
|
|
83
|
+
# (recommend/download/configure models), doctor_cmd.py, output.py
|
|
84
|
+
tests/ # 253 tests; mocked HTTP (respx), FakeEmbedder integration tests, no live network
|
|
77
85
|
```
|
|
78
86
|
|
|
79
87
|
## Flows
|
|
80
88
|
|
|
81
|
-
**Index** (`ragx-cli index [--changed]`): discover files (include/exclude globs
|
|
89
|
+
**Index** (`ragx-cli index [--changed]`): discover files (include/exclude globs — defaults in
|
|
90
|
+
`core/corpus_globs.py`: source + text docs, minus lockfiles/requirements/build output — root .gitignore,
|
|
82
91
|
skip node_modules/.venv/hidden/binaries/>2MB) → xxhash diff (`--changed` = incremental, default
|
|
83
92
|
= full rebuild) → chunk (~800 tok target, chunks are exact byte slices, sub-100-char fragments
|
|
84
93
|
merge into neighbors) → embed batched → HNSW add → kNN edges per new chunk (k=8, cos ≥ 0.55)
|
|
@@ -108,8 +117,13 @@ no-op on failure) → embed all variants, HNSW top-20 each → RRF merge = seeds
|
|
|
108
117
|
(2 hops, decay 0.5, heat = max not sum, neighbor admitted only if cos(query) ≥ 0.35 floor,
|
|
109
118
|
frontier ≤ 150) → cross-encoder rerank of top-100 shortlist → final =
|
|
110
119
|
0.6·rerank + 0.25·heat + 0.15·vector (each min-max normalized; weights renormalize when a
|
|
111
|
-
stage is off).
|
|
112
|
-
|
|
120
|
+
stage is off). Since 0.8.0 expansion and rerank are OPT-IN (`--expand`/`--rerank`;
|
|
121
|
+
`QueryOptions` defaults expand=False, rerank=False; `--no-expand/--no-rerank` are hidden
|
|
122
|
+
deprecated no-ops); graph stays on, `--no-graph` skips it. Output is folded per file
|
|
123
|
+
(`core/grouping.py`, schema `ragx.query.v2`): walk ranked chunks until `top` (default 20)
|
|
124
|
+
distinct files are seen, each file keeps its ≤3 chunks inside that window, entry score = their
|
|
125
|
+
sum. The window is load-bearing — summing ALL candidates let weak graph-neighbour chunks pad
|
|
126
|
+
files (graph MRR .43 → .38; windowed .65). `--files-only` = same entries, no text; `--explain` attaches seed→parent→edge_weight→hop traces.
|
|
113
127
|
|
|
114
128
|
## State & benchmarks (as of 2026-07-10)
|
|
115
129
|
|
|
@@ -121,7 +135,8 @@ full .759/**.889**/**.613**. Key finding: graph-only HURTS precision (near-dupli
|
|
|
121
135
|
displace direct hits — parameter sweeps plateau below baseline MRR; corpus property, not a
|
|
122
136
|
tuning miss), but graph+rerank together deliver the recall win — one Dutch query's target is
|
|
123
137
|
unreachable by vector search or rerank-alone, surfaced only via a hop-1 edge then reranked
|
|
124
|
-
19→4.
|
|
138
|
+
19→4. Graph+rerank belong together for quality (`--rerank`); the 0.8.0 default (graph, no
|
|
139
|
+
rerank) is the measured "graph-only" row — chosen for speed, not quality.
|
|
125
140
|
|
|
126
141
|
UPDATE 2026-07-13: production embedding model is now `text-embedding-bge-m3` (Q8 GGUF, empty
|
|
127
142
|
doc/query prefixes) — notes-repo corpus config + index flipped in place from the worktree-built
|
|
@@ -132,6 +147,10 @@ mostly on Dutch/multilingual queries. Details: `research/bge-m3-dense-q8-vs-nomi
|
|
|
132
147
|
|
|
133
148
|
## Gotchas
|
|
134
149
|
|
|
150
|
+
- llama-server spawns MUST pass `--parallel 1 -c 0 -b/-ub 8192` (`llama_process.py`):
|
|
151
|
+
llama.cpp's default 512-token physical batch cannot hold a ~800-token chunk, and a
|
|
152
|
+
non-causal rerank/embedding input can't split across ubatches — without them every real
|
|
153
|
+
query 500s ("input is too large to process") while a 2-doc smoke test passes.
|
|
135
154
|
- Reasoning models as expansion LLM: answers land in `content` only after thinking;
|
|
136
155
|
expansion uses max_tokens=4096 to leave room for that. Don't lower it.
|
|
137
156
|
- Changing `embeddings.model` invalidates the index; run_query fails loud on manifest
|
|
@@ -158,7 +177,7 @@ mostly on Dutch/multilingual queries. Details: `research/bge-m3-dense-q8-vs-nomi
|
|
|
158
177
|
|
|
159
178
|
## Dev loop
|
|
160
179
|
|
|
161
|
-
`uv sync --group dev --extra rerank` · `uv run pytest -q` (
|
|
180
|
+
`uv sync --group dev --extra rerank` · `uv run pytest -q` (253 pass, ~5 s) ·
|
|
162
181
|
`uv run ruff check src tests` · file soft cap ~150 lines · expected failures raise `RagxError`
|
|
163
182
|
(CLI maps to exit 2). Live smoke: LM Studio must be running with the configured embedding model.
|
|
164
183
|
Changes that impact usage (CLI flags, config keys/precedence, output schemas, install steps)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: ragx-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.8.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)
|
|
@@ -128,20 +130,30 @@ since the last index) so you can see when a re-index is due without running one.
|
|
|
128
130
|
|
|
129
131
|
### Querying
|
|
130
132
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
+
A default query runs vector search + graph traversal: no LLM call, no cross-encoder.
|
|
134
|
+
Expansion (`--expand`) and rerank (`--rerank`) are opt-in; `--no-graph` skips traversal,
|
|
135
|
+
which leaves pure vector search. Callers trade latency for quality per query.
|
|
136
|
+
|
|
137
|
+
Results are **one entry per file**. The pipeline ranks chunks, walks that list until it has
|
|
138
|
+
seen `--top` distinct files (default `query.top = 20`), and folds each file's chunks inside
|
|
139
|
+
that window into one entry: up to 3 sub-entries, entry score = their sum. A file that would
|
|
140
|
+
appear several times in a flat list therefore ranks higher. Chunks below the window never
|
|
141
|
+
count, so graph traversal's weak neighbour chunks cannot pad a file (measured on the notes
|
|
142
|
+
eval corpus: graph-config MRR .43 flat-list, .38 summing all chunks, .65 windowed).
|
|
133
143
|
|
|
134
144
|
```mermaid
|
|
135
145
|
flowchart TD
|
|
136
146
|
Q[query] --> X["1 · expansion (optional, one LLM call)<br/>2–4 reformulations + HyDE passage"]
|
|
137
147
|
X --> S["2 · fan-out vector search<br/>top-20 per variant"]
|
|
138
|
-
Q -. "--
|
|
148
|
+
Q -. "default (no --expand)" .-> S
|
|
139
149
|
S --> R["3 · Reciprocal Rank Fusion<br/>merged seed set"]
|
|
140
150
|
R --> H["4 · heat propagation over the graph<br/>2 hops · decay 0.5 · max-aggregation<br/>query-similarity floor · frontier cap"]
|
|
141
151
|
R -. "--no-graph" .-> K
|
|
142
|
-
H --> K["5 · cross-encoder rerank<br/>(query, chunk) pairs, capped shortlist"]
|
|
152
|
+
H --> K["5 · cross-encoder rerank (--rerank)<br/>(query, chunk) pairs, capped shortlist"]
|
|
153
|
+
H -. "default (no --rerank)" .-> F
|
|
143
154
|
K --> F["6 · combined score<br/>α·rerank + β·heat + γ·vector"]
|
|
144
|
-
F -->
|
|
155
|
+
F --> G["7 · fold per file<br/>a file's best ≤3 chunks → one entry, score = their sum"]
|
|
156
|
+
G --> O["top 20 entries (or paths via --files-only)<br/>+ --explain traversal trace"]
|
|
145
157
|
```
|
|
146
158
|
|
|
147
159
|
**Heat propagation** is what sets ragx-cli apart from plain RAG: seed chunks (from vector search)
|
|
@@ -208,8 +220,10 @@ output for that result shows the exact seed → edge → chunk path.
|
|
|
208
220
|
**Honest caveat:** graph traversal *alone* hurts precision on this corpus (MRR 0.593 → 0.522) —
|
|
209
221
|
near-duplicate neighbors (e.g. adjacent meeting notes) displace weaker direct hits. A parameter
|
|
210
222
|
sweep over decay/floor/weights plateaued below baseline MRR, so this is a property of
|
|
211
|
-
similarity-only edges, not a tuning miss. Conclusion
|
|
212
|
-
|
|
223
|
+
similarity-only edges, not a tuning miss. Conclusion: **graph and rerank belong together**.
|
|
224
|
+
Since 0.8.0 the CLI default is the fast path (vector + graph, no rerank), which is the
|
|
225
|
+
"graph-only" row above; pass `--rerank` for the measured best quality, or `--no-graph` for
|
|
226
|
+
pure vector search.
|
|
213
227
|
|
|
214
228
|
The parameter study below explains the mechanism behind this caveat — and shows that once the
|
|
215
229
|
scoring weights stop putting heat into the final score, the graph win gets much bigger.
|
|
@@ -381,16 +395,69 @@ Why not `jina-embeddings-v5-text-nano`? Excellent model, but its EuroBERT archit
|
|
|
381
395
|
Jina's llama.cpp fork — stock LM Studio can't load it (and the license is CC-BY-NC). See
|
|
382
396
|
`research/lm-studio-model-download-api-and-curated-embedding-reranker-catalog-for-ragx-models-command.md`.
|
|
383
397
|
|
|
398
|
+
## Checking your setup: `ragx-cli doctor`
|
|
399
|
+
|
|
400
|
+
`ragx-cli doctor` runs the configured pipeline's providers for real — it embeds a probe string,
|
|
401
|
+
scores a probe pair with the reranker, and checks the index on disk against the config — then
|
|
402
|
+
reports each stage:
|
|
403
|
+
|
|
404
|
+
```
|
|
405
|
+
✓ config /corpus/ragx.toml — embeddings=openai/text-embedding-bge-m3 · expansion=openai/qwen3.5-9b · rerank=sentence-transformers/BAAI/bge-reranker-v2-m3
|
|
406
|
+
✗ embeddings request to http://localhost:1234/v1/embeddings failed after 3 attempts: [Errno 61] Connection refused
|
|
407
|
+
→ ragx does not start this server — launch it at http://localhost:1234/v1 (LM Studio: `lms server start`),
|
|
408
|
+
or hand the process to ragx with `ragx-cli models --embed-engine llama-server`
|
|
409
|
+
✗ expansion no server answered at http://localhost:1234/v1/models
|
|
410
|
+
→ turn it off with `ragx-cli config set expansion.enabled false`
|
|
411
|
+
✓ rerank sentence-transformers/BAAI/bge-reranker-v2-m3 scored a probe pair (-8.14, 4.72) 3.1s
|
|
412
|
+
✓ index 634 files, 1247 chunks, built with 'text-embedding-bge-m3', no drift
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
Exit codes: `0` healthy, `1` at least one check failed, `2` doctor itself could not run (no
|
|
416
|
+
corpus, unreadable config). Warnings (no index yet, corpus drift, a model the server does not
|
|
417
|
+
list but may JIT-load) do not fail the run. `--json` emits a `ragx.doctor.v1` document with a
|
|
418
|
+
`status`/`detail`/`hint`/`elapsed_ms` entry per check.
|
|
419
|
+
|
|
420
|
+
**Which servers does ragx start?** All of the local ones:
|
|
421
|
+
|
|
422
|
+
| engine (`ragx-cli models`) | config | lifecycle |
|
|
423
|
+
| --- | --- | --- |
|
|
424
|
+
| `llama-server` | `provider="llama-server"` (embeddings and/or rerank) | spawned on demand from the configured GGUF and **killed at exit** — ragx owns the process |
|
|
425
|
+
| `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) |
|
|
426
|
+
| `sentence-transformers` | `rerank.provider="sentence-transformers"` | in-process, no server |
|
|
427
|
+
|
|
428
|
+
`index`, `query`, and `doctor` all do this before their first request, so a cold machine needs
|
|
429
|
+
no manual setup. What was started is logged to stderr and shown in doctor's report:
|
|
430
|
+
|
|
431
|
+
```
|
|
432
|
+
✓ 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
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
Rules it follows: only a **local** `base_url` is ever touched (a cloud endpoint, including one
|
|
436
|
+
set via `OPENAI_BASE_URL`, is never ragx's to start); an already-running server is left alone,
|
|
437
|
+
and if LM Studio isn't installed but something else answers on that port, ragx just uses it; a
|
|
438
|
+
model that isn't downloaded fails loud naming the models the machine does have. Turn it off per
|
|
439
|
+
section with `ragx-cli config set embeddings.autostart false` (or machine-wide via
|
|
440
|
+
`--global`) — then ragx only ever talks to servers you started.
|
|
441
|
+
|
|
442
|
+
`ragx-cli models` still only downloads models and writes `ragx.toml`; it never launches
|
|
443
|
+
anything. If a run dies with "connection refused", `doctor` names the stage and the fix.
|
|
444
|
+
|
|
384
445
|
## Agent-first conventions
|
|
385
446
|
|
|
386
|
-
- `--json` emits exactly one JSON document on stdout (versioned schemas: `ragx.query.
|
|
387
|
-
`ragx.files.v1`, `ragx.status.v1`, `ragx.eval.v1`, `ragx.inspect.*.v1`); logs go
|
|
447
|
+
- `--json` emits exactly one JSON document on stdout (versioned schemas: `ragx.query.v2`,
|
|
448
|
+
`ragx.files.v1`, `ragx.status.v1`, `ragx.doctor.v1`, `ragx.eval.v1`, `ragx.inspect.*.v1`); logs go
|
|
449
|
+
to stderr.
|
|
388
450
|
- Exit codes: `0` results, `1` success-but-empty, `2` error.
|
|
389
|
-
-
|
|
390
|
-
|
|
391
|
-
|
|
451
|
+
- `ragx.query.v2` results are one entry per file: `{file, score, chunks: [...]}`. Each chunk
|
|
452
|
+
carries `chunk_id`, `line_start/line_end`, `byte_start/byte_end`, `score`, `breakdown`, `text`,
|
|
453
|
+
`truncated` — agents jump to the exact source location and read the full text themselves
|
|
454
|
+
(JSON chunk text is truncated). v1 (one flat entry per chunk) is gone since 0.8.0.
|
|
455
|
+
- `--files-only` returns the same entries without text (`file`, `score`, `chunk_ids`) — the
|
|
456
|
+
mode coding agents use most.
|
|
392
457
|
- `ragx-cli status` includes a `drift` object (`new`/`changed`/`deleted` file counts vs the index) —
|
|
393
458
|
agents check it to decide whether to run `ragx-cli index` before querying.
|
|
459
|
+
- `ragx-cli doctor --json` verifies the providers answer before a batch of queries (`ok: false`
|
|
460
|
+
plus a per-check `hint` when something is down); exit `1` = a check failed.
|
|
394
461
|
- `ragx-cli query -` reads the query from stdin; `ragx-cli inspect chunk|file|neighbors|communities|community`
|
|
395
462
|
debugs the graph.
|
|
396
463
|
|
|
@@ -406,10 +473,11 @@ This repo has a ragx-cli index (`.ragx/`). Prefer it over grep for "where is X d
|
|
|
406
473
|
questions; fall back to grep for exact identifiers.
|
|
407
474
|
|
|
408
475
|
- Find relevant files: `ragx-cli query "<natural-language question>" --json --files-only`
|
|
409
|
-
- Get chunks with exact locations: `ragx-cli query "..." --json
|
|
410
|
-
`
|
|
411
|
-
for full context.
|
|
412
|
-
-
|
|
476
|
+
- Get chunks with exact locations: `ragx-cli query "..." --json` — one result per `file`
|
|
477
|
+
(default 20, `--top N`), each with up to 3 `chunks` carrying `line_start/line_end`; the JSON
|
|
478
|
+
`text` is truncated, so read the file yourself for full context.
|
|
479
|
+
- The default query makes no LLM call and loads no cross-encoder. For higher precision add
|
|
480
|
+
`--rerank`; add `--expand` for an LLM query-expansion pass (one extra LLM call).
|
|
413
481
|
- After adding or editing files: `ragx-cli index` (incremental and cheap — hash-based).
|
|
414
482
|
`ragx-cli status --json` shows `drift` counts if you want to check staleness first.
|
|
415
483
|
- stdout is exactly one JSON document; logs are on stderr.
|
|
@@ -559,18 +627,23 @@ non-thinking model is the better expansion choice; detection is a best-effort na
|
|
|
559
627
|
heuristic, the APIs expose no capability flag), and corpus include/exclude
|
|
560
628
|
(gitignore-style globs, comma-separated). Every prompt is pre-filled with the defaults
|
|
561
629
|
below — Enter accepts them all. With piped stdin (agents), `--yes`, or
|
|
562
|
-
`--no-interactive`, it writes the defaults unchanged, exactly as before.
|
|
630
|
+
`--no-interactive`, it writes the defaults unchanged, exactly as before. After writing the
|
|
631
|
+
config, `init` prints a note: an AI agent can tailor `corpus.include`/`exclude` to the
|
|
632
|
+
project, and add a CLAUDE.md/AGENTS.md section on searching it with `ragx-cli query`
|
|
633
|
+
(see [Using ragx-cli from a coding agent](#using-ragx-cli-from-a-coding-agent-claudemd--agentsmd)).
|
|
634
|
+
Key defaults:
|
|
563
635
|
|
|
564
636
|
| section | defaults |
|
|
565
637
|
|---|---|
|
|
638
|
+
| `[corpus]` | `include` = source code (`*.py`, `*.ts`, `*.go`, `*.rs`, … ~70 extensions, plus `Dockerfile`/`Makefile`) and text docs (`*.md`, `*.mdx`, `*.rst`, `*.txt`, `*.adoc`, `*.org`); `exclude` = lockfiles (`*.lock`, `package-lock.json`, `pnpm-lock.yaml`, `go.sum`, …), `requirements*.txt`/`constraints*.txt`, `LICENSE*`/`COPYING*`, `*.min.js`/`*.min.css`/`*.map`, and `dist/`, `build/`, `target/`, `coverage/`, `vendor/`; `respect_gitignore=true`. Full lists: `src/ragx/core/corpus_globs.py`. Existing `ragx.toml` files keep their own lists |
|
|
566
639
|
| `[chunking]` | `size_tokens=800`, `overlap=0.15` |
|
|
567
640
|
| `[graph]` | `k=8`, `min_edge_sim=0.55`, `edge_source="chunk"`, `subchunk_size_tokens=128`, `near_dup_sim=0.9` |
|
|
568
641
|
| `[traversal]` | `hops=2`, `decay=0.5`, `query_floor=0.35`, `max_frontier=150` |
|
|
569
642
|
| `[communities]` | `resolution=1.0`, `seed=42` — recomputed every index run; changing these never invalidates the index |
|
|
570
643
|
| `[fusion]` | `rrf_k=60`, `per_query_top=20` |
|
|
571
644
|
| `[scoring]` | `alpha_rerank=0.6`, `beta_heat=0.25`, `gamma_vector=0.15` |
|
|
572
|
-
| `[embeddings]` | `provider="openai"`, `base_url="http://localhost:1234/v1"`, prefixes for nomic-style models, `api_key_env=""` |
|
|
573
|
-
| `[expansion]` | optional LLM for multi-query/HyDE; reasoning models supported (4096-token budget); `api_key_env=""` |
|
|
645
|
+
| `[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) |
|
|
646
|
+
| `[expansion]` | optional LLM for multi-query/HyDE; reasoning models supported (4096-token budget); `api_key_env=""`, `autostart=true` |
|
|
574
647
|
| `[rerank]` | `BAAI/bge-reranker-v2-m3` via sentence-transformers (`uv tool install 'ragx-cli[rerank]'`) |
|
|
575
648
|
|
|
576
649
|
## 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)
|
|
@@ -100,20 +102,30 @@ since the last index) so you can see when a re-index is due without running one.
|
|
|
100
102
|
|
|
101
103
|
### Querying
|
|
102
104
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
+
A default query runs vector search + graph traversal: no LLM call, no cross-encoder.
|
|
106
|
+
Expansion (`--expand`) and rerank (`--rerank`) are opt-in; `--no-graph` skips traversal,
|
|
107
|
+
which leaves pure vector search. Callers trade latency for quality per query.
|
|
108
|
+
|
|
109
|
+
Results are **one entry per file**. The pipeline ranks chunks, walks that list until it has
|
|
110
|
+
seen `--top` distinct files (default `query.top = 20`), and folds each file's chunks inside
|
|
111
|
+
that window into one entry: up to 3 sub-entries, entry score = their sum. A file that would
|
|
112
|
+
appear several times in a flat list therefore ranks higher. Chunks below the window never
|
|
113
|
+
count, so graph traversal's weak neighbour chunks cannot pad a file (measured on the notes
|
|
114
|
+
eval corpus: graph-config MRR .43 flat-list, .38 summing all chunks, .65 windowed).
|
|
105
115
|
|
|
106
116
|
```mermaid
|
|
107
117
|
flowchart TD
|
|
108
118
|
Q[query] --> X["1 · expansion (optional, one LLM call)<br/>2–4 reformulations + HyDE passage"]
|
|
109
119
|
X --> S["2 · fan-out vector search<br/>top-20 per variant"]
|
|
110
|
-
Q -. "--
|
|
120
|
+
Q -. "default (no --expand)" .-> S
|
|
111
121
|
S --> R["3 · Reciprocal Rank Fusion<br/>merged seed set"]
|
|
112
122
|
R --> H["4 · heat propagation over the graph<br/>2 hops · decay 0.5 · max-aggregation<br/>query-similarity floor · frontier cap"]
|
|
113
123
|
R -. "--no-graph" .-> K
|
|
114
|
-
H --> K["5 · cross-encoder rerank<br/>(query, chunk) pairs, capped shortlist"]
|
|
124
|
+
H --> K["5 · cross-encoder rerank (--rerank)<br/>(query, chunk) pairs, capped shortlist"]
|
|
125
|
+
H -. "default (no --rerank)" .-> F
|
|
115
126
|
K --> F["6 · combined score<br/>α·rerank + β·heat + γ·vector"]
|
|
116
|
-
F -->
|
|
127
|
+
F --> G["7 · fold per file<br/>a file's best ≤3 chunks → one entry, score = their sum"]
|
|
128
|
+
G --> O["top 20 entries (or paths via --files-only)<br/>+ --explain traversal trace"]
|
|
117
129
|
```
|
|
118
130
|
|
|
119
131
|
**Heat propagation** is what sets ragx-cli apart from plain RAG: seed chunks (from vector search)
|
|
@@ -180,8 +192,10 @@ output for that result shows the exact seed → edge → chunk path.
|
|
|
180
192
|
**Honest caveat:** graph traversal *alone* hurts precision on this corpus (MRR 0.593 → 0.522) —
|
|
181
193
|
near-duplicate neighbors (e.g. adjacent meeting notes) displace weaker direct hits. A parameter
|
|
182
194
|
sweep over decay/floor/weights plateaued below baseline MRR, so this is a property of
|
|
183
|
-
similarity-only edges, not a tuning miss. Conclusion
|
|
184
|
-
|
|
195
|
+
similarity-only edges, not a tuning miss. Conclusion: **graph and rerank belong together**.
|
|
196
|
+
Since 0.8.0 the CLI default is the fast path (vector + graph, no rerank), which is the
|
|
197
|
+
"graph-only" row above; pass `--rerank` for the measured best quality, or `--no-graph` for
|
|
198
|
+
pure vector search.
|
|
185
199
|
|
|
186
200
|
The parameter study below explains the mechanism behind this caveat — and shows that once the
|
|
187
201
|
scoring weights stop putting heat into the final score, the graph win gets much bigger.
|
|
@@ -353,16 +367,69 @@ Why not `jina-embeddings-v5-text-nano`? Excellent model, but its EuroBERT archit
|
|
|
353
367
|
Jina's llama.cpp fork — stock LM Studio can't load it (and the license is CC-BY-NC). See
|
|
354
368
|
`research/lm-studio-model-download-api-and-curated-embedding-reranker-catalog-for-ragx-models-command.md`.
|
|
355
369
|
|
|
370
|
+
## Checking your setup: `ragx-cli doctor`
|
|
371
|
+
|
|
372
|
+
`ragx-cli doctor` runs the configured pipeline's providers for real — it embeds a probe string,
|
|
373
|
+
scores a probe pair with the reranker, and checks the index on disk against the config — then
|
|
374
|
+
reports each stage:
|
|
375
|
+
|
|
376
|
+
```
|
|
377
|
+
✓ config /corpus/ragx.toml — embeddings=openai/text-embedding-bge-m3 · expansion=openai/qwen3.5-9b · rerank=sentence-transformers/BAAI/bge-reranker-v2-m3
|
|
378
|
+
✗ embeddings request to http://localhost:1234/v1/embeddings failed after 3 attempts: [Errno 61] Connection refused
|
|
379
|
+
→ ragx does not start this server — launch it at http://localhost:1234/v1 (LM Studio: `lms server start`),
|
|
380
|
+
or hand the process to ragx with `ragx-cli models --embed-engine llama-server`
|
|
381
|
+
✗ expansion no server answered at http://localhost:1234/v1/models
|
|
382
|
+
→ turn it off with `ragx-cli config set expansion.enabled false`
|
|
383
|
+
✓ rerank sentence-transformers/BAAI/bge-reranker-v2-m3 scored a probe pair (-8.14, 4.72) 3.1s
|
|
384
|
+
✓ index 634 files, 1247 chunks, built with 'text-embedding-bge-m3', no drift
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
Exit codes: `0` healthy, `1` at least one check failed, `2` doctor itself could not run (no
|
|
388
|
+
corpus, unreadable config). Warnings (no index yet, corpus drift, a model the server does not
|
|
389
|
+
list but may JIT-load) do not fail the run. `--json` emits a `ragx.doctor.v1` document with a
|
|
390
|
+
`status`/`detail`/`hint`/`elapsed_ms` entry per check.
|
|
391
|
+
|
|
392
|
+
**Which servers does ragx start?** All of the local ones:
|
|
393
|
+
|
|
394
|
+
| engine (`ragx-cli models`) | config | lifecycle |
|
|
395
|
+
| --- | --- | --- |
|
|
396
|
+
| `llama-server` | `provider="llama-server"` (embeddings and/or rerank) | spawned on demand from the configured GGUF and **killed at exit** — ragx owns the process |
|
|
397
|
+
| `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) |
|
|
398
|
+
| `sentence-transformers` | `rerank.provider="sentence-transformers"` | in-process, no server |
|
|
399
|
+
|
|
400
|
+
`index`, `query`, and `doctor` all do this before their first request, so a cold machine needs
|
|
401
|
+
no manual setup. What was started is logged to stderr and shown in doctor's report:
|
|
402
|
+
|
|
403
|
+
```
|
|
404
|
+
✓ 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
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
Rules it follows: only a **local** `base_url` is ever touched (a cloud endpoint, including one
|
|
408
|
+
set via `OPENAI_BASE_URL`, is never ragx's to start); an already-running server is left alone,
|
|
409
|
+
and if LM Studio isn't installed but something else answers on that port, ragx just uses it; a
|
|
410
|
+
model that isn't downloaded fails loud naming the models the machine does have. Turn it off per
|
|
411
|
+
section with `ragx-cli config set embeddings.autostart false` (or machine-wide via
|
|
412
|
+
`--global`) — then ragx only ever talks to servers you started.
|
|
413
|
+
|
|
414
|
+
`ragx-cli models` still only downloads models and writes `ragx.toml`; it never launches
|
|
415
|
+
anything. If a run dies with "connection refused", `doctor` names the stage and the fix.
|
|
416
|
+
|
|
356
417
|
## Agent-first conventions
|
|
357
418
|
|
|
358
|
-
- `--json` emits exactly one JSON document on stdout (versioned schemas: `ragx.query.
|
|
359
|
-
`ragx.files.v1`, `ragx.status.v1`, `ragx.eval.v1`, `ragx.inspect.*.v1`); logs go
|
|
419
|
+
- `--json` emits exactly one JSON document on stdout (versioned schemas: `ragx.query.v2`,
|
|
420
|
+
`ragx.files.v1`, `ragx.status.v1`, `ragx.doctor.v1`, `ragx.eval.v1`, `ragx.inspect.*.v1`); logs go
|
|
421
|
+
to stderr.
|
|
360
422
|
- Exit codes: `0` results, `1` success-but-empty, `2` error.
|
|
361
|
-
-
|
|
362
|
-
|
|
363
|
-
|
|
423
|
+
- `ragx.query.v2` results are one entry per file: `{file, score, chunks: [...]}`. Each chunk
|
|
424
|
+
carries `chunk_id`, `line_start/line_end`, `byte_start/byte_end`, `score`, `breakdown`, `text`,
|
|
425
|
+
`truncated` — agents jump to the exact source location and read the full text themselves
|
|
426
|
+
(JSON chunk text is truncated). v1 (one flat entry per chunk) is gone since 0.8.0.
|
|
427
|
+
- `--files-only` returns the same entries without text (`file`, `score`, `chunk_ids`) — the
|
|
428
|
+
mode coding agents use most.
|
|
364
429
|
- `ragx-cli status` includes a `drift` object (`new`/`changed`/`deleted` file counts vs the index) —
|
|
365
430
|
agents check it to decide whether to run `ragx-cli index` before querying.
|
|
431
|
+
- `ragx-cli doctor --json` verifies the providers answer before a batch of queries (`ok: false`
|
|
432
|
+
plus a per-check `hint` when something is down); exit `1` = a check failed.
|
|
366
433
|
- `ragx-cli query -` reads the query from stdin; `ragx-cli inspect chunk|file|neighbors|communities|community`
|
|
367
434
|
debugs the graph.
|
|
368
435
|
|
|
@@ -378,10 +445,11 @@ This repo has a ragx-cli index (`.ragx/`). Prefer it over grep for "where is X d
|
|
|
378
445
|
questions; fall back to grep for exact identifiers.
|
|
379
446
|
|
|
380
447
|
- Find relevant files: `ragx-cli query "<natural-language question>" --json --files-only`
|
|
381
|
-
- Get chunks with exact locations: `ragx-cli query "..." --json
|
|
382
|
-
`
|
|
383
|
-
for full context.
|
|
384
|
-
-
|
|
448
|
+
- Get chunks with exact locations: `ragx-cli query "..." --json` — one result per `file`
|
|
449
|
+
(default 20, `--top N`), each with up to 3 `chunks` carrying `line_start/line_end`; the JSON
|
|
450
|
+
`text` is truncated, so read the file yourself for full context.
|
|
451
|
+
- The default query makes no LLM call and loads no cross-encoder. For higher precision add
|
|
452
|
+
`--rerank`; add `--expand` for an LLM query-expansion pass (one extra LLM call).
|
|
385
453
|
- After adding or editing files: `ragx-cli index` (incremental and cheap — hash-based).
|
|
386
454
|
`ragx-cli status --json` shows `drift` counts if you want to check staleness first.
|
|
387
455
|
- stdout is exactly one JSON document; logs are on stderr.
|
|
@@ -531,18 +599,23 @@ non-thinking model is the better expansion choice; detection is a best-effort na
|
|
|
531
599
|
heuristic, the APIs expose no capability flag), and corpus include/exclude
|
|
532
600
|
(gitignore-style globs, comma-separated). Every prompt is pre-filled with the defaults
|
|
533
601
|
below — Enter accepts them all. With piped stdin (agents), `--yes`, or
|
|
534
|
-
`--no-interactive`, it writes the defaults unchanged, exactly as before.
|
|
602
|
+
`--no-interactive`, it writes the defaults unchanged, exactly as before. After writing the
|
|
603
|
+
config, `init` prints a note: an AI agent can tailor `corpus.include`/`exclude` to the
|
|
604
|
+
project, and add a CLAUDE.md/AGENTS.md section on searching it with `ragx-cli query`
|
|
605
|
+
(see [Using ragx-cli from a coding agent](#using-ragx-cli-from-a-coding-agent-claudemd--agentsmd)).
|
|
606
|
+
Key defaults:
|
|
535
607
|
|
|
536
608
|
| section | defaults |
|
|
537
609
|
|---|---|
|
|
610
|
+
| `[corpus]` | `include` = source code (`*.py`, `*.ts`, `*.go`, `*.rs`, … ~70 extensions, plus `Dockerfile`/`Makefile`) and text docs (`*.md`, `*.mdx`, `*.rst`, `*.txt`, `*.adoc`, `*.org`); `exclude` = lockfiles (`*.lock`, `package-lock.json`, `pnpm-lock.yaml`, `go.sum`, …), `requirements*.txt`/`constraints*.txt`, `LICENSE*`/`COPYING*`, `*.min.js`/`*.min.css`/`*.map`, and `dist/`, `build/`, `target/`, `coverage/`, `vendor/`; `respect_gitignore=true`. Full lists: `src/ragx/core/corpus_globs.py`. Existing `ragx.toml` files keep their own lists |
|
|
538
611
|
| `[chunking]` | `size_tokens=800`, `overlap=0.15` |
|
|
539
612
|
| `[graph]` | `k=8`, `min_edge_sim=0.55`, `edge_source="chunk"`, `subchunk_size_tokens=128`, `near_dup_sim=0.9` |
|
|
540
613
|
| `[traversal]` | `hops=2`, `decay=0.5`, `query_floor=0.35`, `max_frontier=150` |
|
|
541
614
|
| `[communities]` | `resolution=1.0`, `seed=42` — recomputed every index run; changing these never invalidates the index |
|
|
542
615
|
| `[fusion]` | `rrf_k=60`, `per_query_top=20` |
|
|
543
616
|
| `[scoring]` | `alpha_rerank=0.6`, `beta_heat=0.25`, `gamma_vector=0.15` |
|
|
544
|
-
| `[embeddings]` | `provider="openai"`, `base_url="http://localhost:1234/v1"`, prefixes for nomic-style models, `api_key_env=""` |
|
|
545
|
-
| `[expansion]` | optional LLM for multi-query/HyDE; reasoning models supported (4096-token budget); `api_key_env=""` |
|
|
617
|
+
| `[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) |
|
|
618
|
+
| `[expansion]` | optional LLM for multi-query/HyDE; reasoning models supported (4096-token budget); `api_key_env=""`, `autostart=true` |
|
|
546
619
|
| `[rerank]` | `BAAI/bge-reranker-v2-m3` via sentence-transformers (`uv tool install 'ragx-cli[rerank]'`) |
|
|
547
620
|
|
|
548
621
|
## Features & roadmap
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "ragx-cli"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.8.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.
|
|
47
|
+
dev = ["pytest>=8", "ruff>=0.15,<0.16", "respx>=0.21"]
|
|
48
48
|
|
|
49
49
|
[tool.ruff]
|
|
50
50
|
line-length = 100
|
|
@@ -11,8 +11,9 @@ description: |
|
|
|
11
11
|
# ragx-cli — semantic search over an indexed corpus
|
|
12
12
|
|
|
13
13
|
Indexes files into a chunk-level embedding similarity graph; answers queries via vector search +
|
|
14
|
-
graph traversal
|
|
15
|
-
deterministic exit codes, byte-exact source
|
|
14
|
+
graph traversal, with optional cross-encoder rerank (`--rerank`) and LLM expansion (`--expand`).
|
|
15
|
+
Agent-first: one JSON doc on stdout, logs on stderr, deterministic exit codes, byte-exact source
|
|
16
|
+
locations.
|
|
16
17
|
|
|
17
18
|
## When
|
|
18
19
|
|
|
@@ -29,24 +30,28 @@ A corpus is ragx-indexed if its root has `ragx.toml` and `.ragx/`.
|
|
|
29
30
|
```bash
|
|
30
31
|
ragx-cli status --json # root, model, counts, and drift (new/changed/deleted vs index)
|
|
31
32
|
ragx-cli query "question" --json --files-only # which files are relevant — most-used mode
|
|
32
|
-
ragx-cli query "..." --json --top
|
|
33
|
-
ragx-cli query "..." --json --
|
|
33
|
+
ragx-cli query "..." --json --top 10 # one entry per file, best chunks + exact locations
|
|
34
|
+
ragx-cli query "..." --json --rerank # higher precision: cross-encoder rerank (slower)
|
|
34
35
|
echo "..." | ragx-cli query - --json # long/multiline query from stdin
|
|
35
36
|
ragx-cli index # incremental reindex (hash-based, cheap) — the default
|
|
36
37
|
ragx-cli index --full # full rebuild: re-chunks + re-embeds everything
|
|
37
38
|
```
|
|
38
39
|
|
|
39
|
-
Other query flags: `--no-graph
|
|
40
|
+
Other query flags: `--expand` (one LLM call: multi-query + HyDE), `--no-graph` (pure vector),
|
|
41
|
+
`--hops N`, `--explain`. `--no-expand`/`--no-rerank` are deprecated no-ops (both stages are off
|
|
42
|
+
by default).
|
|
40
43
|
|
|
41
44
|
## Output
|
|
42
45
|
|
|
43
|
-
- stdout is exactly one JSON doc (`ragx.query.
|
|
46
|
+
- stdout is exactly one JSON doc (`ragx.query.v2` / `ragx.files.v1` / `ragx.status.v1`); logs go
|
|
44
47
|
to stderr. Never parse stderr.
|
|
45
48
|
- Exit codes: `0` results, `1` success-but-empty (report "nothing found", don't retry as failure),
|
|
46
49
|
`2` error.
|
|
47
|
-
-
|
|
48
|
-
`
|
|
49
|
-
|
|
50
|
+
- `ragx.query.v2` results are one entry per file (default 20): `file`, `score` (sum of its
|
|
51
|
+
chunks), `chunks` (up to 3, each with `line_start/end`, `byte_start/end`, `score`,
|
|
52
|
+
`breakdown`, `text`). JSON `text` is truncated — read the file at the line range for full
|
|
53
|
+
context.
|
|
54
|
+
- Typical flow: `--files-only` to pick files → `--json` for line ranges → read files at ranges.
|
|
50
55
|
|
|
51
56
|
## Freshness
|
|
52
57
|
|