mirror of https://github.com/razor-ai/soup.git
fix(cli): quote install hints so `pip install soup-cli[extra]` works on cmd.exe (v0.71.37)
Every printed and documented `pip install 'soup-cli[extra]'` was bash / zsh /
PowerShell syntax and failed on Windows cmd.exe:
ERROR: Invalid requirement: "'soup-cli[train]'": Expected package name at
the start of dependency specifier
cmd.exe has no single-quote quoting, so it passes the quotes to pip verbatim
and pip rejects the requirement. Nothing in Soup can fix that once the command
is typed -- pip and the shell own it, and Soup is not installed yet when the
README line runs -- so the fix is the spelling we print.
Migrated 147 sites across 67 files to `pip install "soup-cli[extra]"`:
- 64 in src/ (Rich console hints + plain ImportError text)
- 57 in README.md + docs/
- 22 in src/soup_cli/templates/*.yaml + examples/configs/*.yaml
- 3 in examples/README.md
Double quotes are the only spelling valid in every shell (cmd, PowerShell,
bash, zsh), which is why the repo already used `pip install -e ".[dev]"`.
Measured on Windows: single quotes fail ONLY on cmd; double quotes pass
everywhere; bare passes on Windows but zsh globs `[extra]` and fails.
Method note (the PR #247 class): the hints sit INSIDE double-quoted Python
string literals, so a blind ' -> " sed produces SyntaxError. A tokenize-based
rewriter escaped `\"` in DQUOTE tokens and left bare `"` in TRIPLE / COMMENT
tokens; every touched .py was compile-checked. The full suite (not ruff, not
compile-check) caught two rewriter blind spots: the real YAML templates under
src/soup_cli/templates/ (byte-identical drift test) and examples/README.md.
A regression test (tests/test_v07137.py) scans the package and every docs code
block for the single-quoted form; prose may still name it so a reader from an
older tutorial recognises the error.
Also bundles #315 (@Sanjays2402): eval-gate benchmark tasks now run via
ForgettingDetector instead of a helper that never existed. Closes #310.
Test count: 16283 -> 16288 (+4 in tests/test_v07137.py).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
83c4c6d60b
commit
a95fedeb0e
27
CHANGELOG.md
27
CHANGELOG.md
|
|
@ -12,8 +12,35 @@ reproducing 70+ versions of notes.
|
|||
|
||||
## [Unreleased]
|
||||
|
||||
## [0.71.37] - 2026-07-17
|
||||
|
||||
**Every `pip install soup-cli[extra]` command now works on Windows `cmd.exe`**, and
|
||||
eval-gate benchmark tasks run instead of always failing.
|
||||
|
||||
### Fixed
|
||||
|
||||
- **Install hints are now quoted so they work in every shell.** Soup printed
|
||||
`pip install 'soup-cli[ui]'` — bash / zsh / PowerShell syntax. `cmd.exe` has no
|
||||
single-quote quoting, so it hands the quotes to pip verbatim and pip refuses:
|
||||
|
||||
```
|
||||
ERROR: Invalid requirement: "'soup-cli[train]'": Expected package name at the start of dependency specifier
|
||||
```
|
||||
|
||||
Every hint, README command, and docs example now uses `pip install
|
||||
"soup-cli[extra]"`, which works in cmd, PowerShell, bash, and zsh alike — the
|
||||
same spelling the repo already used for `pip install -e ".[dev]"`. Measured on
|
||||
Windows: single quotes fail only on `cmd.exe`; double quotes pass everywhere;
|
||||
dropping the quotes passes on Windows but breaks zsh, which globs the bracket.
|
||||
|
||||
Nothing in Soup can rescue the command after it is typed — pip and the shell
|
||||
own it, and Soup is not installed yet when the README command runs — so the
|
||||
fix is the spelling we print. A regression test now scans the package and every
|
||||
docs code block for the single-quoted form.
|
||||
|
||||
If you followed an older tutorial and hit `Invalid requirement`, swap the `'`
|
||||
for `"`; nothing is wrong with the package.
|
||||
|
||||
- **Eval-gate `type: benchmark` tasks now actually run.** `eval/gate.py` probed
|
||||
for a `forgetting.run_mini_benchmark` helper that never existed, so every
|
||||
`type: benchmark` task in a gate suite failed 100% of the time — while
|
||||
|
|
|
|||
|
|
@ -120,7 +120,7 @@ src/soup_cli/
|
|||
templates/ - 21 built-in soup.yaml templates (YAML + manifest.json) with load_template loader (v0.39.0, +bco v0.40.0, +4 compliance v0.71.35)
|
||||
ui/ - Web UI (FastAPI + HTML/JS SPA)
|
||||
|
||||
tests/ - Test suite (314 files, 16283 tests)
|
||||
tests/ - Test suite (315 files, 16288 tests)
|
||||
examples/ - Real-world config examples and datasets
|
||||
```
|
||||
|
||||
|
|
|
|||
41
README.md
41
README.md
|
|
@ -32,7 +32,7 @@
|
|||
Soup turns the pain of LLM fine-tuning into a simple workflow. One config, one command, done.
|
||||
|
||||
```bash
|
||||
pip install 'soup-cli[train]' # add [train] to fine-tune; bare `soup-cli` is the light CLI
|
||||
pip install "soup-cli[train]" # add [train] to fine-tune; bare `soup-cli` is the light CLI
|
||||
soup init --template chat
|
||||
soup train
|
||||
```
|
||||
|
|
@ -68,7 +68,7 @@ infrastructure instead of improving models. Soup fixes that.
|
|||
separates them. The default is deliberately conservative; see the CHANGELOG.
|
||||
- **Two blocking bugs fixed:** the hardware-fit gate **refused to train any model you merged
|
||||
yourself** (a local checkpoint's name has no size marker, so it guessed 7B and predicted
|
||||
16 GB), and every `pip install 'soup-cli[extra]'` hint printed **without the extra** —
|
||||
16 GB), and every `pip install "soup-cli[extra]"` hint printed **without the extra** —
|
||||
17 sites where the suggested command succeeds and leaves the feature still broken.
|
||||
|
||||
```bash
|
||||
|
|
@ -104,26 +104,37 @@ Full history: [CHANGELOG.md](CHANGELOG.md) · [GitHub Releases](https://gi
|
|||
### 1. Install
|
||||
|
||||
```bash
|
||||
pip install soup-cli # light: CLI + config + data tools (no PyTorch)
|
||||
pip install 'soup-cli[train]' # add the training stack (torch, transformers, peft, trl, …)
|
||||
pip install git+https://github.com/MakazhanAlpamys/Soup.git # latest dev
|
||||
# Light core: CLI + config + data tools, no PyTorch
|
||||
pip install soup-cli
|
||||
|
||||
# Add the training stack (torch, transformers, peft, trl, datasets, …)
|
||||
pip install "soup-cli[train]"
|
||||
|
||||
# Everything (train + serve + ui + data) in one shot
|
||||
pip install "soup-cli[all]"
|
||||
|
||||
# Or from GitHub (latest dev)
|
||||
pip install git+https://github.com/MakazhanAlpamys/Soup.git
|
||||
```
|
||||
|
||||
> **On Windows `cmd.exe`, swap the single quotes for double quotes:**
|
||||
> `pip install "soup-cli[train]"`
|
||||
The full extras table (`fast`, `mlx`, `serve`, `eval`, `ui`, `vision`, `audio`, …) lives in
|
||||
[`docs/models.md`](docs/models.md#optional-extras).
|
||||
|
||||
> **Use double quotes around the extra.** They are the only spelling that works in
|
||||
> every shell — `cmd.exe`, PowerShell, bash, and zsh.
|
||||
>
|
||||
> The commands above are written for bash / zsh / PowerShell, where `'…'` quotes a
|
||||
> string. `cmd.exe` has no single-quote quoting — it passes the characters straight
|
||||
> to pip, which then rejects them:
|
||||
> Older tutorials and videos (including some of ours) show the single-quoted
|
||||
> `pip install 'soup-cli[train]'`. That is bash / zsh / PowerShell syntax, and it
|
||||
> fails on Windows `cmd.exe`, which has no single-quote quoting and hands the
|
||||
> quotes straight to pip:
|
||||
>
|
||||
> ```
|
||||
> ERROR: Invalid requirement: "'soup-cli[train]'": Expected package name at the start of dependency specifier
|
||||
> ```
|
||||
>
|
||||
> That is pip parsing a literal `'`, not a problem with the package. Double quotes
|
||||
> work in **every** shell — cmd, PowerShell, bash, and zsh — so `pip install
|
||||
> "soup-cli[train]"` is always safe. (Unquoted `pip install soup-cli[train]` also
|
||||
> works in cmd and PowerShell, but zsh reads `[train]` as a glob and fails.)
|
||||
> If you hit that, swap the `'` for `"` — pip is rejecting a literal quote
|
||||
> character, nothing is wrong with the package. (Dropping the quotes entirely
|
||||
> works on Windows too, but zsh then reads `[train]` as a glob and fails.)
|
||||
|
||||
`soup init`, `soup data …`, and the other data/inspection commands work on the light install.
|
||||
Fine-tuning (`soup train`) needs the `[train]` extra.
|
||||
|
|
@ -159,7 +170,7 @@ A complete `soup.yaml`:
|
|||
```yaml
|
||||
base: meta-llama/Llama-3.1-8B-Instruct
|
||||
task: sft
|
||||
# backend: unsloth # 2-5x faster, pip install 'soup-cli[fast]'
|
||||
# backend: unsloth # 2-5x faster, pip install "soup-cli[fast]"
|
||||
|
||||
data:
|
||||
train: ./data/train.jsonl
|
||||
|
|
|
|||
|
|
@ -79,7 +79,7 @@ Fine-tune on M1-M4 Macs via Apple's [MLX](https://github.com/ml-explore/mlx) fra
|
|||
|
||||
```bash
|
||||
# Install MLX support
|
||||
pip install 'soup-cli[mlx]'
|
||||
pip install "soup-cli[mlx]"
|
||||
```
|
||||
|
||||
```yaml
|
||||
|
|
@ -108,7 +108,7 @@ Use the [Unsloth](https://github.com/unslothai/unsloth) backend for significantl
|
|||
|
||||
```bash
|
||||
# Install unsloth support
|
||||
pip install 'soup-cli[fast]'
|
||||
pip install "soup-cli[fast]"
|
||||
```
|
||||
|
||||
Then add one line to your config:
|
||||
|
|
@ -143,7 +143,7 @@ app from your `soup.yaml` for serverless, per-second-billed GPU training. The co
|
|||
base64-embedded as **data** — no code interpolation, no secrets in the generated stub.
|
||||
|
||||
```bash
|
||||
pip install 'soup-cli[modal]' # only needed for live submit
|
||||
pip install "soup-cli[modal]" # only needed for live submit
|
||||
|
||||
# Plan-only (default): write the stub + print the `modal run` command.
|
||||
soup train --config soup.yaml --cloud modal --gpu a100
|
||||
|
|
@ -585,7 +585,7 @@ Full-screen Textual dashboard. Two-pane: run list (left) + selected-run detail
|
|||
(right). `r` refreshes, `q` quits.
|
||||
|
||||
```bash
|
||||
pip install 'soup-cli[tui]'
|
||||
pip install "soup-cli[tui]"
|
||||
soup tui --refresh 1.0 --limit 50
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -255,9 +255,9 @@ soup adapters bisect <ckpt>... --eval-command "..." Binary search over training
|
|||
soup lock write --base-sha <h> --dataset-sha <h> --env-hash <h> Write soup.lock (v0.67.0)
|
||||
soup lock write --base-sha <h> --dataset-sha <h> --env-lock soup-env.lock Auto-derive --env-hash from soup-env.lock (v0.71.1)
|
||||
soup lock show / soup lock check Show + drift-check (exit 3 on drift)
|
||||
soup compile <program.py> --eval <suite> [--optimizer mipro|gepa|textgrad|copro|bootstrap_fewshot] [--plan-only] DSPy / GEPA / TextGrad prompt-program compiler — live (v0.71.13; pip install 'soup-cli[compile]')
|
||||
soup compile <program.py> --eval <suite> [--optimizer mipro|gepa|textgrad|copro|bootstrap_fewshot] [--plan-only] DSPy / GEPA / TextGrad prompt-program compiler — live (v0.71.13; pip install "soup-cli[compile]")
|
||||
soup distill-prompt --traces <jsonl> --teacher <m> --student <m> --strategy sft|preference|kl [--provider ollama|anthropic|vllm] [--base-url <url>] [--temperature F] [--max-rows N] Distill prompt-heavy traces via a live teacher (v0.71.13)
|
||||
soup compile-tools <spec.json|yaml> --eval <jsonl> [--optimizer textgrad|gepa] [--plan-only] TextGrad / GEPA tool-schema optimiser — live (v0.71.13; pip install 'soup-cli[compile]')
|
||||
soup compile-tools <spec.json|yaml> --eval <jsonl> [--optimizer textgrad|gepa] [--plan-only] TextGrad / GEPA tool-schema optimiser — live (v0.71.13; pip install "soup-cli[compile]")
|
||||
soup apple-adapter <source-dir> --direction hf-to-mlx|mlx-to-hf|hf-to-apple|mlx-to-apple --output <dir> [--sign] [--plan-only] PEFT LoRA <-> mlx-lm adapter conversion — live (v0.71.21; *-to-apple upstream-gated exit 3)
|
||||
soup local-rl init --db <path> Create personal-LLM flywheel SQLite schema (v0.68.0)
|
||||
soup local-rl status --db <path> Print interactions / thumbs-up / thumbs-down counters
|
||||
|
|
@ -299,7 +299,7 @@ server over **stdio**, so any MCP client — Claude Code, Cursor, Cline, Continu
|
|||
can drive Soup conversationally. Install the extra first:
|
||||
|
||||
```bash
|
||||
pip install 'soup-cli[mcp]'
|
||||
pip install "soup-cli[mcp]"
|
||||
```
|
||||
|
||||
Register it with your client. For **Claude Code** (`.mcp.json` in the repo) or
|
||||
|
|
|
|||
|
|
@ -337,7 +337,7 @@ soup data generate --prompt "..." --validate
|
|||
# Auto-filter by quality (coherence scoring)
|
||||
soup data generate --prompt "..." --filter
|
||||
|
||||
# Auto-dedup (MinHash, requires: pip install 'soup-cli[data]')
|
||||
# Auto-dedup (MinHash, requires: pip install "soup-cli[data]")
|
||||
soup data generate --prompt "..." --dedup
|
||||
|
||||
# Full quality pipeline: validate + filter + dedup
|
||||
|
|
@ -591,7 +591,7 @@ soup data convert ./data/train.jsonl --to sharegpt --output converted.jsonl
|
|||
# Merge multiple datasets
|
||||
soup data merge data1.jsonl data2.jsonl --output merged.jsonl --shuffle
|
||||
|
||||
# Remove near-duplicates (requires: pip install 'soup-cli[data]')
|
||||
# Remove near-duplicates (requires: pip install "soup-cli[data]")
|
||||
soup data dedup ./data/train.jsonl --threshold 0.8
|
||||
|
||||
# Extended statistics (length distribution, token counts, languages)
|
||||
|
|
@ -814,7 +814,7 @@ Five checks: `length_bias` — the **#1 silent DPO degradation**: `chosen`
|
|||
systematically longer than `rejected`, reported as a Cohen's d effect size —
|
||||
`label_imbalance` (KTO desirable:undesirable ratio), `near_duplicates`
|
||||
(MinHash/LSH, reuses the `soup data dedup` kernel; requires
|
||||
`pip install 'soup-cli[data]'`, degrades to an advisory skip otherwise),
|
||||
`pip install "soup-cli[data]"`, degrades to an advisory skip otherwise),
|
||||
`identical_pairs` (`chosen == rejected` — zero preference signal), and
|
||||
`prompt_leak` (the prompt echoed verbatim inside the completion, a common
|
||||
synthetic-data pipeline bug). Same OK/MINOR/MAJOR taxonomy and exit codes as
|
||||
|
|
|
|||
|
|
@ -432,7 +432,7 @@ Full-featured evaluation platform with standard benchmarks, custom evals, LLM-as
|
|||
|
||||
```bash
|
||||
# Install eval dependencies
|
||||
pip install 'soup-cli[eval]'
|
||||
pip install "soup-cli[eval]"
|
||||
|
||||
# Standard benchmarks (wraps lm-evaluation-harness)
|
||||
soup eval benchmark --model ./output --benchmarks mmlu,gsm8k,hellaswag
|
||||
|
|
|
|||
|
|
@ -70,29 +70,29 @@ no PyTorch. Add `[train]` to fine-tune, or install other extras only when you ne
|
|||
|
||||
| Extra | Install | What it adds |
|
||||
|---|---|---|
|
||||
| `train` | `pip install 'soup-cli[train]'` | Training stack: torch, transformers, peft, trl, datasets, bitsandbytes, accelerate |
|
||||
| `all` | `pip install 'soup-cli[all]'` | `train` + `serve` + `ui` + `data` in one shot |
|
||||
| `fast` | `pip install 'soup-cli[fast]'` | Unsloth backend (2-5x faster, lower VRAM) |
|
||||
| `vision` | `pip install 'soup-cli[vision]'` | Vision / multimodal fine-tuning (Pillow) |
|
||||
| `audio` | `pip install 'soup-cli[audio]'` | Audio / speech fine-tuning (librosa, soundfile) |
|
||||
| `mlx` | `pip install 'soup-cli[mlx]'` | Apple Silicon backend (mlx, mlx-lm) |
|
||||
| `qat` | `pip install 'soup-cli[qat]'` | Quantization-Aware Training (torchao) |
|
||||
| `serve` | `pip install 'soup-cli[serve]'` | Inference server (FastAPI + uvicorn) |
|
||||
| `serve-fast` | `pip install 'soup-cli[serve-fast]'` | vLLM inference backend (2-4x throughput) |
|
||||
| `sglang` | `pip install 'soup-cli[sglang]'` | SGLang inference backend |
|
||||
| `ui` | `pip install 'soup-cli[ui]'` | Web UI + inference server |
|
||||
| `tui` | `pip install 'soup-cli[tui]'` | Full-screen Textual dashboard (`soup tui`) |
|
||||
| `eval` | `pip install 'soup-cli[eval]'` | Benchmark evaluation (lm-evaluation-harness) |
|
||||
| `data` | `pip install 'soup-cli[data]'` | Deduplication (MinHash via datasketch) |
|
||||
| `data-pro` | `pip install 'soup-cli[data-pro]'` | Language detection + PII (langdetect, presidio) |
|
||||
| `deepspeed` | `pip install 'soup-cli[deepspeed]'` | Multi-GPU training (DeepSpeed ZeRO) |
|
||||
| `liger` | `pip install 'soup-cli[liger]'` | Liger Kernel fused ops |
|
||||
| `ring-attn` | `pip install 'soup-cli[ring-attn]'` | Ring FlashAttention (sequence parallelism) |
|
||||
| `onnx` / `tensorrt` | `pip install 'soup-cli[onnx]'` | ONNX / TensorRT-LLM export |
|
||||
| `awq` / `gptq` | `pip install 'soup-cli[awq]'` | AWQ / GPTQ quantized export |
|
||||
| `trackers` | `pip install 'soup-cli[trackers]'` | MLflow / SwanLab / Trackio logging |
|
||||
| `remote` | `pip install 'soup-cli[remote]'` | Remote datasets (s3 / gs / az / oci) |
|
||||
| `dev` | `pip install 'soup-cli[dev]'` | Tests + lint + types (pytest, ruff, mypy, pre-commit) |
|
||||
| `train` | `pip install "soup-cli[train]"` | Training stack: torch, transformers, peft, trl, datasets, bitsandbytes, accelerate |
|
||||
| `all` | `pip install "soup-cli[all]"` | `train` + `serve` + `ui` + `data` in one shot |
|
||||
| `fast` | `pip install "soup-cli[fast]"` | Unsloth backend (2-5x faster, lower VRAM) |
|
||||
| `vision` | `pip install "soup-cli[vision]"` | Vision / multimodal fine-tuning (Pillow) |
|
||||
| `audio` | `pip install "soup-cli[audio]"` | Audio / speech fine-tuning (librosa, soundfile) |
|
||||
| `mlx` | `pip install "soup-cli[mlx]"` | Apple Silicon backend (mlx, mlx-lm) |
|
||||
| `qat` | `pip install "soup-cli[qat]"` | Quantization-Aware Training (torchao) |
|
||||
| `serve` | `pip install "soup-cli[serve]"` | Inference server (FastAPI + uvicorn) |
|
||||
| `serve-fast` | `pip install "soup-cli[serve-fast]"` | vLLM inference backend (2-4x throughput) |
|
||||
| `sglang` | `pip install "soup-cli[sglang]"` | SGLang inference backend |
|
||||
| `ui` | `pip install "soup-cli[ui]"` | Web UI + inference server |
|
||||
| `tui` | `pip install "soup-cli[tui]"` | Full-screen Textual dashboard (`soup tui`) |
|
||||
| `eval` | `pip install "soup-cli[eval]"` | Benchmark evaluation (lm-evaluation-harness) |
|
||||
| `data` | `pip install "soup-cli[data]"` | Deduplication (MinHash via datasketch) |
|
||||
| `data-pro` | `pip install "soup-cli[data-pro]"` | Language detection + PII (langdetect, presidio) |
|
||||
| `deepspeed` | `pip install "soup-cli[deepspeed]"` | Multi-GPU training (DeepSpeed ZeRO) |
|
||||
| `liger` | `pip install "soup-cli[liger]"` | Liger Kernel fused ops |
|
||||
| `ring-attn` | `pip install "soup-cli[ring-attn]"` | Ring FlashAttention (sequence parallelism) |
|
||||
| `onnx` / `tensorrt` | `pip install "soup-cli[onnx]"` | ONNX / TensorRT-LLM export |
|
||||
| `awq` / `gptq` | `pip install "soup-cli[awq]"` | AWQ / GPTQ quantized export |
|
||||
| `trackers` | `pip install "soup-cli[trackers]"` | MLflow / SwanLab / Trackio logging |
|
||||
| `remote` | `pip install "soup-cli[remote]"` | Remote datasets (s3 / gs / az / oci) |
|
||||
| `dev` | `pip install "soup-cli[dev]"` | Tests + lint + types (pytest, ruff, mypy, pre-commit) |
|
||||
|
||||
The complete, authoritative extras list is in [`pyproject.toml`](../pyproject.toml).
|
||||
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ Train with simulated quantization for significantly better post-quantization qua
|
|||
|
||||
```bash
|
||||
# Install QAT support
|
||||
pip install 'soup-cli[qat]'
|
||||
pip install "soup-cli[qat]"
|
||||
```
|
||||
|
||||
```yaml
|
||||
|
|
@ -71,7 +71,7 @@ QAT works with all training tasks (SFT, DPO, GRPO, PPO, KTO, ORPO, SimPO, IPO, P
|
|||
For H100 / H200 / B100 / B200 GPUs, train with float8 matmuls for ~2x speedup vs bf16 at comparable quality. This extends QAT infrastructure via `torchao.float8`:
|
||||
|
||||
```bash
|
||||
pip install 'soup-cli[qat]' # torchao >= 0.5.0 includes torchao.float8
|
||||
pip install "soup-cli[qat]" # torchao >= 0.5.0 includes torchao.float8
|
||||
```
|
||||
|
||||
```yaml
|
||||
|
|
@ -106,7 +106,7 @@ Bool `true` stays on the int8 QAT path for backward compatibility. FP8 requires
|
|||
Models with 128k+ vocabularies (Llama 3.1, Qwen2) materialise a huge `(batch, seq, vocab)` logits tensor that dominates VRAM. Cut Cross-Entropy computes the loss in chunks instead:
|
||||
|
||||
```bash
|
||||
pip install 'soup-cli[cce]' # or: pip install cut-cross-entropy
|
||||
pip install "soup-cli[cce]" # or: pip install cut-cross-entropy
|
||||
```
|
||||
|
||||
```yaml
|
||||
|
|
@ -368,9 +368,9 @@ data:
|
|||
Install optional performance packages:
|
||||
|
||||
```bash
|
||||
pip install 'soup-cli[liger]' # Liger Kernel fused operations
|
||||
pip install "soup-cli[liger]" # Liger Kernel fused operations
|
||||
pip install flash-attn --no-build-isolation # FlashAttention
|
||||
pip install 'soup-cli[ring-attn]' # Ring FlashAttention (sequence parallelism)
|
||||
pip install "soup-cli[ring-attn]" # Ring FlashAttention (sequence parallelism)
|
||||
```
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -97,7 +97,7 @@ Linux/macOS need only a C++ toolchain + CMake. CUDA llama.cpp builds are unteste
|
|||
Export models to ONNX format for use with [ONNX Runtime](https://onnxruntime.ai/):
|
||||
|
||||
```bash
|
||||
pip install 'soup-cli[onnx]'
|
||||
pip install "soup-cli[onnx]"
|
||||
soup export --model ./output --format onnx
|
||||
soup export --model ./output --format onnx --output ./model_onnx
|
||||
```
|
||||
|
|
@ -107,7 +107,7 @@ soup export --model ./output --format onnx --output ./model_onnx
|
|||
Export models to TensorRT-LLM format for high-throughput GPU inference:
|
||||
|
||||
```bash
|
||||
pip install 'soup-cli[tensorrt]'
|
||||
pip install "soup-cli[tensorrt]"
|
||||
soup export --model ./output --format tensorrt
|
||||
soup export --model ./output --format tensorrt --output ./model_trt
|
||||
```
|
||||
|
|
@ -212,7 +212,7 @@ Start a local OpenAI-compatible inference server:
|
|||
|
||||
```bash
|
||||
# Install server dependencies
|
||||
pip install 'soup-cli[serve]'
|
||||
pip install "soup-cli[serve]"
|
||||
|
||||
# Start server
|
||||
soup serve --model ./output --port 8000
|
||||
|
|
@ -253,7 +253,7 @@ Use [vLLM](https://github.com/vllm-project/vllm) for significantly better throug
|
|||
|
||||
```bash
|
||||
# Install vLLM support
|
||||
pip install 'soup-cli[serve-fast]'
|
||||
pip install "soup-cli[serve-fast]"
|
||||
|
||||
# Start with vLLM backend
|
||||
soup serve --model ./output --backend vllm
|
||||
|
|
@ -273,7 +273,7 @@ Use [SGLang](https://github.com/sgl-project/sglang) as an alternative high-throu
|
|||
|
||||
```bash
|
||||
# Install SGLang support
|
||||
pip install 'soup-cli[sglang]'
|
||||
pip install "soup-cli[sglang]"
|
||||
|
||||
# Start with SGLang backend
|
||||
soup serve --model ./output --backend sglang
|
||||
|
|
@ -482,7 +482,7 @@ The OTLP endpoint is SSRF-hardened: only http/https schemes, plain HTTP only for
|
|||
Launch a local web interface to manage experiments, start training, explore data, and chat with models — all from your browser.
|
||||
|
||||
```bash
|
||||
pip install 'soup-cli[ui]'
|
||||
pip install "soup-cli[ui]"
|
||||
soup ui
|
||||
# -> opens http://127.0.0.1:7860 in your browser
|
||||
# -> prints auth token to console
|
||||
|
|
|
|||
|
|
@ -531,7 +531,7 @@ Fine-tune vision-language models (LLaMA-3.2-Vision, Qwen2-VL, Pixtral) on image+
|
|||
|
||||
```bash
|
||||
# Install vision support
|
||||
pip install 'soup-cli[vision]'
|
||||
pip install "soup-cli[vision]"
|
||||
|
||||
# Create a vision config
|
||||
soup init --template vision
|
||||
|
|
@ -581,7 +581,7 @@ Fine-tune audio-language models (Qwen2-Audio, Whisper) on audio+text data:
|
|||
|
||||
```bash
|
||||
# Install audio support
|
||||
pip install 'soup-cli[audio]'
|
||||
pip install "soup-cli[audio]"
|
||||
|
||||
# Create an audio config
|
||||
soup init --template audio
|
||||
|
|
|
|||
|
|
@ -137,7 +137,7 @@ soup train
|
|||
- Uses RoPE scaling (dynamic) to extend context to 128k tokens
|
||||
- Enables gradient checkpointing and FlashAttention for memory efficiency
|
||||
- Supports `linear`, `dynamic`, `yarn`, `longrope` scaling types
|
||||
- Optional Liger Kernel for fused ops: `pip install 'soup-cli[liger]'`
|
||||
- Optional Liger Kernel for fused ops: `pip install "soup-cli[liger]"`
|
||||
|
||||
### 9. Embedding Model Fine-Tuning
|
||||
|
||||
|
|
@ -159,7 +159,7 @@ soup train
|
|||
Fine-tune audio-language models (Qwen2-Audio, Whisper):
|
||||
|
||||
```bash
|
||||
pip install 'soup-cli[audio]'
|
||||
pip install "soup-cli[audio]"
|
||||
soup init --template audio
|
||||
soup train
|
||||
```
|
||||
|
|
@ -301,7 +301,7 @@ quantization: int8 # Reduces memory by 4x
|
|||
Unsloth is 2-5x faster training:
|
||||
|
||||
```bash
|
||||
pip install 'soup-cli[fast]'
|
||||
pip install "soup-cli[fast]"
|
||||
```
|
||||
|
||||
Then in your config:
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
base: meta-llama/Llama-3.1-8B-Instruct
|
||||
task: dpo
|
||||
# backend: unsloth # 2-5x faster, pip install 'soup-cli[fast]'
|
||||
# backend: unsloth # 2-5x faster, pip install "soup-cli[fast]"
|
||||
|
||||
data:
|
||||
train: examples/data/dpo_sample.jsonl
|
||||
|
|
@ -32,7 +32,7 @@ training:
|
|||
# gradient_checkpointing: true # Save memory on long sequences
|
||||
# packing: true # Pack short samples for faster training
|
||||
# use_flash_attn: true # FlashAttention for faster attention
|
||||
# use_liger: true # Liger Kernel fused ops, pip install 'soup-cli[liger]'
|
||||
# use_liger: true # Liger Kernel fused ops, pip install "soup-cli[liger]"
|
||||
lora:
|
||||
r: 16
|
||||
alpha: 32
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|||
|
||||
[project]
|
||||
name = "soup-cli"
|
||||
version = "0.71.36"
|
||||
version = "0.71.37"
|
||||
description = "Fine-tune and post-train LLMs in one command. No SSH, no config hell."
|
||||
readme = "README.md"
|
||||
license = "Apache-2.0"
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
"""Soup CLI — Fine-tune and post-train LLMs in one command."""
|
||||
|
||||
__version__ = "0.71.36"
|
||||
__version__ = "0.71.37"
|
||||
|
|
|
|||
|
|
@ -280,7 +280,7 @@ def submit_modal_run(plan: CloudPlan, *, env: Optional[Mapping] = None) -> int:
|
|||
import modal # noqa: F401 — presence check only
|
||||
except ImportError as exc:
|
||||
raise RuntimeError(
|
||||
"Modal SDK not installed. Run `pip install 'soup-cli[modal]'`."
|
||||
"Modal SDK not installed. Run `pip install \"soup-cli[modal]\"`."
|
||||
) from exc
|
||||
import subprocess
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
Renders a ``CompilePlan`` panel and, when ``--plan-only`` is omitted, runs
|
||||
the live optimizer (v0.71.13 #225). A missing optimizer library surfaces a
|
||||
friendly exit-2 advisory naming ``pip install 'soup-cli[compile]'``.
|
||||
friendly exit-2 advisory naming ``pip install "soup-cli[compile]"``.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
|
|
|||
|
|
@ -264,9 +264,11 @@ def _semantic_dedup(
|
|||
console.print(
|
||||
"[red]Semantic dedup needs PyTorch + transformers.[/]\n"
|
||||
# \[train] is escaped: Rich would otherwise eat the bracket as a
|
||||
# markup tag and print `pip install 'soup-cli'` -- a command that
|
||||
# markup tag and print `pip install "soup-cli"` -- a command that
|
||||
# installs the package WITHOUT the extra the user is missing.
|
||||
"Install with: [bold]pip install 'soup-cli\\[train]'[/]"
|
||||
# Double quotes, not single: cmd.exe cannot strip `'` and pip then
|
||||
# rejects the requirement outright.
|
||||
"Install with: [bold]pip install \"soup-cli\\[train]\"[/]"
|
||||
)
|
||||
raise typer.Exit(1)
|
||||
except (ValueError, TypeError) as exc:
|
||||
|
|
@ -358,7 +360,7 @@ def dedup(
|
|||
except ImportError:
|
||||
console.print(
|
||||
"[red]datasketch not installed.[/]\n"
|
||||
"Install with: [bold]pip install 'soup-cli\\[data]'[/]"
|
||||
"Install with: [bold]pip install \"soup-cli\\[data]\"[/]"
|
||||
)
|
||||
raise typer.Exit(1)
|
||||
|
||||
|
|
|
|||
|
|
@ -191,7 +191,7 @@ def check(
|
|||
"[red]soup data canary check needs PyTorch + transformers.[/]\n"
|
||||
# \[train] escaped: Rich would eat the bracket and print a
|
||||
# command that installs WITHOUT the extra.
|
||||
"Install with: [bold]pip install 'soup-cli\\[train]'[/]"
|
||||
"Install with: [bold]pip install \"soup-cli\\[train]\"[/]"
|
||||
)
|
||||
raise typer.Exit(1)
|
||||
except (ValueError, OSError, RuntimeError) as exc:
|
||||
|
|
|
|||
|
|
@ -117,7 +117,7 @@ def topics(
|
|||
"[red]soup data topics needs PyTorch + transformers.[/]\n"
|
||||
# \[train] escaped: Rich would eat the bracket and print a
|
||||
# command that installs WITHOUT the extra.
|
||||
"Install with: [bold]pip install 'soup-cli\\[train]'[/]"
|
||||
"Install with: [bold]pip install \"soup-cli\\[train]\"[/]"
|
||||
)
|
||||
raise typer.Exit(1)
|
||||
except (ValueError, TypeError) as exc:
|
||||
|
|
|
|||
|
|
@ -108,7 +108,7 @@ def benchmark(
|
|||
except ImportError:
|
||||
console.print(
|
||||
"[red]lm-eval not installed.[/]\n"
|
||||
"Install with: [bold]pip install 'soup-cli\\[eval]'[/]"
|
||||
"Install with: [bold]pip install \"soup-cli\\[eval]\"[/]"
|
||||
)
|
||||
raise typer.Exit(1)
|
||||
|
||||
|
|
|
|||
|
|
@ -579,7 +579,7 @@ def _export_onnx(
|
|||
except ImportError:
|
||||
console.print(
|
||||
"[red]optimum not installed.[/]\n"
|
||||
"Install with: [bold]pip install 'soup-cli\\[onnx]'[/]\n"
|
||||
"Install with: [bold]pip install \"soup-cli\\[onnx]\"[/]\n"
|
||||
"Or directly: [bold]pip install optimum[onnx][/]"
|
||||
)
|
||||
raise typer.Exit(1)
|
||||
|
|
@ -664,7 +664,7 @@ def _export_tensorrt(
|
|||
if not trtllm_available:
|
||||
console.print(
|
||||
"[red]tensorrt_llm not installed.[/]\n"
|
||||
"Install with: [bold]pip install 'soup-cli\\[tensorrt]'[/]\n"
|
||||
"Install with: [bold]pip install \"soup-cli\\[tensorrt]\"[/]\n"
|
||||
"Or follow: https://github.com/NVIDIA/TensorRT-LLM#installation"
|
||||
)
|
||||
raise typer.Exit(1)
|
||||
|
|
@ -857,7 +857,7 @@ def _export_awq(
|
|||
except ImportError:
|
||||
console.print(
|
||||
"[red]autoawq not installed.[/]\n"
|
||||
"Install with: [bold]pip install 'soup-cli\\[awq]'[/]\n"
|
||||
"Install with: [bold]pip install \"soup-cli\\[awq]\"[/]\n"
|
||||
"Or directly: [bold]pip install autoawq[/]"
|
||||
)
|
||||
raise typer.Exit(1)
|
||||
|
|
@ -989,7 +989,7 @@ def _export_gptq(
|
|||
except ImportError:
|
||||
console.print(
|
||||
"[red]auto-gptq not installed.[/]\n"
|
||||
"Install with: [bold]pip install 'soup-cli\\[gptq]'[/]\n"
|
||||
"Install with: [bold]pip install \"soup-cli\\[gptq]\"[/]\n"
|
||||
"Or directly: [bold]pip install auto-gptq[/]"
|
||||
)
|
||||
raise typer.Exit(1)
|
||||
|
|
|
|||
|
|
@ -487,7 +487,7 @@ def _run_dedup_pipeline(path: Path) -> None:
|
|||
except ImportError:
|
||||
console.print(
|
||||
"[yellow]Dedup: datasketch not installed, skipping. "
|
||||
"Install: pip install 'soup-cli\\[data]'[/]"
|
||||
"Install: pip install \"soup-cli\\[data]\"[/]"
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ def serve(
|
|||
# instead of parsing '[mcp]' as a (dropped) markup tag.
|
||||
console.print(
|
||||
"[red]The MCP server needs the 'mcp' SDK.[/] "
|
||||
"Install it with: [bold]pip install 'soup-cli\\[mcp]'[/]"
|
||||
"Install it with: [bold]pip install \"soup-cli\\[mcp]\"[/]"
|
||||
)
|
||||
raise typer.Exit(1) from None
|
||||
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@ def merge_sharded_fsdp_weights(
|
|||
except ImportError as exc:
|
||||
console.print(
|
||||
"[red]torch + safetensors are required for consolidation. "
|
||||
"Install with: [bold]pip install 'soup-cli\\[train]'[/][/]"
|
||||
"Install with: [bold]pip install \"soup-cli\\[train]\"[/][/]"
|
||||
)
|
||||
raise typer.Exit(code=1) from exc
|
||||
|
||||
|
|
|
|||
|
|
@ -460,7 +460,7 @@ def serve(
|
|||
except ImportError:
|
||||
console.print(
|
||||
"[red]FastAPI/uvicorn not installed.[/]\n"
|
||||
"Install with: [bold]pip install 'soup-cli\\[serve]'[/]"
|
||||
"Install with: [bold]pip install \"soup-cli\\[serve]\"[/]"
|
||||
)
|
||||
raise typer.Exit(1)
|
||||
|
||||
|
|
@ -537,7 +537,7 @@ def serve(
|
|||
if not is_vllm_available():
|
||||
console.print(
|
||||
"[red]vLLM not installed.[/]\n"
|
||||
"Install with: [bold]pip install 'soup-cli\\[serve-fast]'[/]"
|
||||
"Install with: [bold]pip install \"soup-cli\\[serve-fast]\"[/]"
|
||||
)
|
||||
raise typer.Exit(1)
|
||||
|
||||
|
|
@ -548,7 +548,7 @@ def serve(
|
|||
if not check_sglang_available():
|
||||
console.print(
|
||||
"[red]SGLang not installed.[/]\n"
|
||||
"Install with: [bold]pip install 'soup-cli\\[sglang]'[/]"
|
||||
"Install with: [bold]pip install \"soup-cli\\[sglang]\"[/]"
|
||||
)
|
||||
raise typer.Exit(1)
|
||||
|
||||
|
|
|
|||
|
|
@ -767,7 +767,7 @@ def train(
|
|||
except ImportError:
|
||||
console.print(
|
||||
"[red]wandb not installed.[/]\n"
|
||||
"Run: [bold]pip install 'soup-cli\\[wandb]'[/]"
|
||||
"Run: [bold]pip install \"soup-cli\\[wandb]\"[/]"
|
||||
)
|
||||
raise typer.Exit(1)
|
||||
except Exception as wandb_err:
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ console = Console()
|
|||
def _missing_dep_panel() -> str:
|
||||
return (
|
||||
"[red]Textual is not installed.[/]\n\n"
|
||||
"[bold]Install:[/] pip install 'soup-cli\\[tui]'\n"
|
||||
"[bold]Install:[/] pip install \"soup-cli\\[tui]\"\n"
|
||||
"[dim]Or directly:[/] pip install textual"
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ def ui(
|
|||
except ImportError:
|
||||
console.print(
|
||||
"[red]FastAPI/uvicorn not installed.[/]\n"
|
||||
"Install with: [bold]pip install 'soup-cli\\[ui]'[/]"
|
||||
"Install with: [bold]pip install \"soup-cli\\[ui]\"[/]"
|
||||
)
|
||||
raise typer.Exit(1)
|
||||
|
||||
|
|
|
|||
|
|
@ -5554,7 +5554,7 @@ TEMPLATES: dict[str, str] = {
|
|||
|
||||
base: meta-llama/Llama-3.1-8B-Instruct
|
||||
task: sft
|
||||
# backend: unsloth # 2-5x faster, pip install 'soup-cli[fast]'
|
||||
# backend: unsloth # 2-5x faster, pip install "soup-cli[fast]"
|
||||
|
||||
data:
|
||||
train: ./data/train.jsonl
|
||||
|
|
@ -5579,7 +5579,7 @@ output: ./output
|
|||
|
||||
base: codellama/CodeLlama-7b-Instruct-hf
|
||||
task: sft
|
||||
# backend: unsloth # 2-5x faster, pip install 'soup-cli[fast]'
|
||||
# backend: unsloth # 2-5x faster, pip install "soup-cli[fast]"
|
||||
|
||||
data:
|
||||
train: ./data/code_train.jsonl
|
||||
|
|
@ -5604,7 +5604,7 @@ output: ./output
|
|||
|
||||
base: meta-llama/Llama-3.1-8B-Instruct
|
||||
task: grpo
|
||||
# backend: unsloth # 2-5x faster, pip install 'soup-cli[fast]'
|
||||
# backend: unsloth # 2-5x faster, pip install "soup-cli[fast]"
|
||||
|
||||
data:
|
||||
train: ./data/reasoning_train.jsonl
|
||||
|
|
@ -5634,7 +5634,7 @@ output: ./output
|
|||
base: meta-llama/Llama-3.2-11B-Vision-Instruct
|
||||
task: sft
|
||||
modality: vision
|
||||
# backend: unsloth # 2-5x faster, pip install 'soup-cli[fast]'
|
||||
# backend: unsloth # 2-5x faster, pip install "soup-cli[fast]"
|
||||
|
||||
data:
|
||||
train: ./data/vision_train.jsonl
|
||||
|
|
@ -5660,7 +5660,7 @@ output: ./output
|
|||
|
||||
base: meta-llama/Llama-3.1-8B-Instruct
|
||||
task: sft
|
||||
# backend: unsloth # 2-5x faster, pip install 'soup-cli[fast]'
|
||||
# backend: unsloth # 2-5x faster, pip install "soup-cli[fast]"
|
||||
|
||||
data:
|
||||
train: ./data/medical_train.jsonl
|
||||
|
|
@ -5690,7 +5690,7 @@ output: ./output
|
|||
|
||||
base: meta-llama/Llama-3.1-8B-Instruct
|
||||
task: kto
|
||||
# backend: unsloth # 2-5x faster, pip install 'soup-cli[fast]'
|
||||
# backend: unsloth # 2-5x faster, pip install "soup-cli[fast]"
|
||||
|
||||
data:
|
||||
train: ./data/kto_train.jsonl
|
||||
|
|
@ -5720,7 +5720,7 @@ output: ./output
|
|||
|
||||
base: meta-llama/Llama-3.1-8B-Instruct
|
||||
task: orpo
|
||||
# backend: unsloth # 2-5x faster, pip install 'soup-cli[fast]'
|
||||
# backend: unsloth # 2-5x faster, pip install "soup-cli[fast]"
|
||||
|
||||
data:
|
||||
train: ./data/preference_train.jsonl
|
||||
|
|
@ -5750,7 +5750,7 @@ output: ./output
|
|||
|
||||
base: meta-llama/Llama-3.1-8B-Instruct
|
||||
task: bco
|
||||
# backend: unsloth # 2-5x faster, pip install 'soup-cli[fast]'
|
||||
# backend: unsloth # 2-5x faster, pip install "soup-cli[fast]"
|
||||
|
||||
data:
|
||||
train: ./data/preference_train.jsonl
|
||||
|
|
@ -5780,7 +5780,7 @@ output: ./output
|
|||
|
||||
base: meta-llama/Llama-3.1-8B-Instruct
|
||||
task: simpo
|
||||
# backend: unsloth # 2-5x faster, pip install 'soup-cli[fast]'
|
||||
# backend: unsloth # 2-5x faster, pip install "soup-cli[fast]"
|
||||
|
||||
data:
|
||||
train: ./data/preference_train.jsonl
|
||||
|
|
@ -5811,7 +5811,7 @@ output: ./output
|
|||
|
||||
base: meta-llama/Llama-3.1-8B-Instruct
|
||||
task: ipo
|
||||
# backend: unsloth # 2-5x faster, pip install 'soup-cli[fast]'
|
||||
# backend: unsloth # 2-5x faster, pip install "soup-cli[fast]"
|
||||
|
||||
data:
|
||||
train: ./data/preference_train.jsonl
|
||||
|
|
@ -5843,7 +5843,7 @@ output: ./output
|
|||
|
||||
base: meta-llama/Llama-3.1-8B
|
||||
task: pretrain
|
||||
# backend: unsloth # 2-5x faster, pip install 'soup-cli[fast]'
|
||||
# backend: unsloth # 2-5x faster, pip install "soup-cli[fast]"
|
||||
|
||||
data:
|
||||
train: ./data/corpus.jsonl
|
||||
|
|
@ -5871,7 +5871,7 @@ output: ./output_pretrain
|
|||
|
||||
base: Qwen/Qwen3-30B-A3B
|
||||
task: sft
|
||||
# backend: unsloth # 2-5x faster, pip install 'soup-cli[fast]'
|
||||
# backend: unsloth # 2-5x faster, pip install "soup-cli[fast]"
|
||||
|
||||
data:
|
||||
train: ./data/train.jsonl
|
||||
|
|
@ -5902,7 +5902,7 @@ output: ./output
|
|||
|
||||
base: meta-llama/Llama-3.1-8B-Instruct
|
||||
task: sft
|
||||
# backend: unsloth # 2-5x faster, pip install 'soup-cli[fast]'
|
||||
# backend: unsloth # 2-5x faster, pip install "soup-cli[fast]"
|
||||
|
||||
data:
|
||||
train: ./data/long_context_train.jsonl
|
||||
|
|
@ -5923,7 +5923,7 @@ training:
|
|||
gradient_checkpointing: true
|
||||
rope_scaling_type: dynamic
|
||||
use_flash_attn: true
|
||||
# use_liger: true # pip install 'soup-cli[liger]' for fused ops
|
||||
# use_liger: true # pip install "soup-cli[liger]" for fused ops
|
||||
# use_ring_attention: true # Multi-GPU sequence parallelism
|
||||
|
||||
output: ./output_longctx
|
||||
|
|
@ -5939,7 +5939,7 @@ output: ./output_longctx
|
|||
|
||||
base: BAAI/bge-base-en-v1.5
|
||||
task: embedding
|
||||
# backend: unsloth # 2-5x faster, pip install 'soup-cli[fast]'
|
||||
# backend: unsloth # 2-5x faster, pip install "soup-cli[fast]"
|
||||
|
||||
data:
|
||||
train: ./data/embedding_train.jsonl
|
||||
|
|
@ -5976,7 +5976,7 @@ output: ./output_embedding
|
|||
base: Qwen/Qwen2-Audio-7B-Instruct
|
||||
task: sft
|
||||
modality: audio
|
||||
# backend: unsloth # 2-5x faster, pip install 'soup-cli[fast]'
|
||||
# backend: unsloth # 2-5x faster, pip install "soup-cli[fast]"
|
||||
|
||||
data:
|
||||
train: ./data/audio_train.jsonl
|
||||
|
|
@ -6017,7 +6017,7 @@ output: ./output_audio
|
|||
|
||||
base: meta-llama/Llama-3.1-8B-Instruct
|
||||
task: sft
|
||||
# backend: unsloth # 2-5x faster, pip install 'soup-cli[fast]'
|
||||
# backend: unsloth # 2-5x faster, pip install "soup-cli[fast]"
|
||||
|
||||
data:
|
||||
train: ./data/tool_calling_train.jsonl
|
||||
|
|
@ -6051,7 +6051,7 @@ output: ./output
|
|||
|
||||
base: meta-llama/Llama-3.1-8B-Instruct
|
||||
task: ppo
|
||||
# backend: unsloth # 2-5x faster, pip install 'soup-cli[fast]'
|
||||
# backend: unsloth # 2-5x faster, pip install "soup-cli[fast]"
|
||||
|
||||
data:
|
||||
train: ./data/prompts.jsonl
|
||||
|
|
|
|||
|
|
@ -518,7 +518,7 @@ def _base_pairwise_judge_cls() -> type:
|
|||
from trl import BasePairwiseJudge
|
||||
except ImportError as exc: # pragma: no cover — trl ships in [train]/[dev]
|
||||
raise ImportError(
|
||||
"SoupPairwiseJudge needs trl>=0.19 (pip install 'soup-cli[train]')"
|
||||
"SoupPairwiseJudge needs trl>=0.19 (pip install \"soup-cli[train]\")"
|
||||
) from exc
|
||||
return BasePairwiseJudge
|
||||
|
||||
|
|
|
|||
|
|
@ -269,7 +269,7 @@ def tool_data_doctor(args: dict) -> dict:
|
|||
import transformers # noqa: F401
|
||||
except ImportError as exc:
|
||||
raise McpToolError(
|
||||
"data_doctor needs the tokenizer stack: pip install 'soup-cli[train]'"
|
||||
"data_doctor needs the tokenizer stack: pip install \"soup-cli[train]\""
|
||||
) from exc
|
||||
try:
|
||||
tok = _dd.resolve_tokenizer(model, trust_remote_code=False)
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
base: Qwen/Qwen2-Audio-7B-Instruct
|
||||
task: sft
|
||||
modality: audio
|
||||
# backend: unsloth # 2-5x faster, pip install 'soup-cli[fast]'
|
||||
# backend: unsloth # 2-5x faster, pip install "soup-cli[fast]"
|
||||
|
||||
data:
|
||||
train: ./data/audio_train.jsonl
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
base: meta-llama/Llama-3.1-8B-Instruct
|
||||
task: bco
|
||||
# backend: unsloth # 2-5x faster, pip install 'soup-cli[fast]'
|
||||
# backend: unsloth # 2-5x faster, pip install "soup-cli[fast]"
|
||||
|
||||
data:
|
||||
train: ./data/preference_train.jsonl
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
base: meta-llama/Llama-3.1-8B-Instruct
|
||||
task: sft
|
||||
# backend: unsloth # 2-5x faster, pip install 'soup-cli[fast]'
|
||||
# backend: unsloth # 2-5x faster, pip install "soup-cli[fast]"
|
||||
|
||||
data:
|
||||
train: ./data/train.jsonl
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
base: codellama/CodeLlama-7b-Instruct-hf
|
||||
task: sft
|
||||
# backend: unsloth # 2-5x faster, pip install 'soup-cli[fast]'
|
||||
# backend: unsloth # 2-5x faster, pip install "soup-cli[fast]"
|
||||
|
||||
data:
|
||||
train: ./data/code_train.jsonl
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
|
||||
base: BAAI/bge-base-en-v1.5
|
||||
task: embedding
|
||||
# backend: unsloth # 2-5x faster, pip install 'soup-cli[fast]'
|
||||
# backend: unsloth # 2-5x faster, pip install "soup-cli[fast]"
|
||||
|
||||
data:
|
||||
train: ./data/embedding_train.jsonl
|
||||
|
|
|
|||
|
|
@ -10,8 +10,8 @@
|
|||
#
|
||||
# 2. Auto-generate the Annex XI/XII technical documentation + measure energy:
|
||||
# soup train --config soup.yaml \
|
||||
# --annex-xi annex_xi.md \ # or annex_xi.pdf (pip install 'soup-cli[pdf]')
|
||||
# --track-energy --energy-country DEU \ # codecarbon (pip install 'soup-cli[carbon]')
|
||||
# --annex-xi annex_xi.md \ # or annex_xi.pdf (pip install "soup-cli[pdf]")
|
||||
# --track-energy --energy-country DEU \ # codecarbon (pip install "soup-cli[carbon]")
|
||||
# --energy-out energy.json
|
||||
#
|
||||
# 3. Fold the measured energy into the ML-BOM (transparency):
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
base: meta-llama/Llama-3.1-8B-Instruct
|
||||
task: ipo
|
||||
# backend: unsloth # 2-5x faster, pip install 'soup-cli[fast]'
|
||||
# backend: unsloth # 2-5x faster, pip install "soup-cli[fast]"
|
||||
|
||||
data:
|
||||
train: ./data/preference_train.jsonl
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
base: meta-llama/Llama-3.1-8B-Instruct
|
||||
task: kto
|
||||
# backend: unsloth # 2-5x faster, pip install 'soup-cli[fast]'
|
||||
# backend: unsloth # 2-5x faster, pip install "soup-cli[fast]"
|
||||
|
||||
data:
|
||||
train: ./data/kto_train.jsonl
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
base: meta-llama/Llama-3.1-8B-Instruct
|
||||
task: sft
|
||||
# backend: unsloth # 2-5x faster, pip install 'soup-cli[fast]'
|
||||
# backend: unsloth # 2-5x faster, pip install "soup-cli[fast]"
|
||||
|
||||
data:
|
||||
train: ./data/long_context_train.jsonl
|
||||
|
|
@ -27,7 +27,7 @@ training:
|
|||
gradient_checkpointing: true
|
||||
rope_scaling_type: dynamic
|
||||
use_flash_attn: true
|
||||
# use_liger: true # pip install 'soup-cli[liger]' for fused ops
|
||||
# use_liger: true # pip install "soup-cli[liger]" for fused ops
|
||||
# use_ring_attention: true # Multi-GPU sequence parallelism
|
||||
|
||||
output: ./output_longctx
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
base: meta-llama/Llama-3.1-8B-Instruct
|
||||
task: sft
|
||||
# backend: unsloth # 2-5x faster, pip install 'soup-cli[fast]'
|
||||
# backend: unsloth # 2-5x faster, pip install "soup-cli[fast]"
|
||||
|
||||
data:
|
||||
train: ./data/medical_train.jsonl
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
base: Qwen/Qwen3-30B-A3B
|
||||
task: sft
|
||||
# backend: unsloth # 2-5x faster, pip install 'soup-cli[fast]'
|
||||
# backend: unsloth # 2-5x faster, pip install "soup-cli[fast]"
|
||||
|
||||
data:
|
||||
train: ./data/train.jsonl
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
base: meta-llama/Llama-3.1-8B-Instruct
|
||||
task: orpo
|
||||
# backend: unsloth # 2-5x faster, pip install 'soup-cli[fast]'
|
||||
# backend: unsloth # 2-5x faster, pip install "soup-cli[fast]"
|
||||
|
||||
data:
|
||||
train: ./data/preference_train.jsonl
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
base: meta-llama/Llama-3.1-8B
|
||||
task: pretrain
|
||||
# backend: unsloth # 2-5x faster, pip install 'soup-cli[fast]'
|
||||
# backend: unsloth # 2-5x faster, pip install "soup-cli[fast]"
|
||||
|
||||
data:
|
||||
train: ./data/corpus.jsonl
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
base: meta-llama/Llama-3.1-8B-Instruct
|
||||
task: grpo
|
||||
# backend: unsloth # 2-5x faster, pip install 'soup-cli[fast]'
|
||||
# backend: unsloth # 2-5x faster, pip install "soup-cli[fast]"
|
||||
|
||||
data:
|
||||
train: ./data/reasoning_train.jsonl
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
|
||||
base: meta-llama/Llama-3.1-8B-Instruct
|
||||
task: ppo
|
||||
# backend: unsloth # 2-5x faster, pip install 'soup-cli[fast]'
|
||||
# backend: unsloth # 2-5x faster, pip install "soup-cli[fast]"
|
||||
|
||||
data:
|
||||
train: ./data/prompts.jsonl
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
base: meta-llama/Llama-3.1-8B-Instruct
|
||||
task: simpo
|
||||
# backend: unsloth # 2-5x faster, pip install 'soup-cli[fast]'
|
||||
# backend: unsloth # 2-5x faster, pip install "soup-cli[fast]"
|
||||
|
||||
data:
|
||||
train: ./data/preference_train.jsonl
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@
|
|||
|
||||
base: meta-llama/Llama-3.1-8B-Instruct
|
||||
task: sft
|
||||
# backend: unsloth # 2-5x faster, pip install 'soup-cli[fast]'
|
||||
# backend: unsloth # 2-5x faster, pip install "soup-cli[fast]"
|
||||
|
||||
data:
|
||||
train: ./data/tool_calling_train.jsonl
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
base: meta-llama/Llama-3.2-11B-Vision-Instruct
|
||||
task: sft
|
||||
modality: vision
|
||||
# backend: unsloth # 2-5x faster, pip install 'soup-cli[fast]'
|
||||
# backend: unsloth # 2-5x faster, pip install "soup-cli[fast]"
|
||||
|
||||
data:
|
||||
train: ./data/vision_train.jsonl
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ class MLXDPOTrainerWrapper:
|
|||
except ImportError as exc:
|
||||
raise ImportError(
|
||||
"MLX backend requires the 'mlx' and 'mlx-lm' packages. "
|
||||
"Install with: pip install 'soup-cli[mlx]'"
|
||||
"Install with: pip install \"soup-cli[mlx]\""
|
||||
) from exc
|
||||
|
||||
def setup(self, dataset: dict) -> None:
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ class MLXGRPOTrainerWrapper:
|
|||
except ImportError as exc:
|
||||
raise ImportError(
|
||||
"MLX backend requires the 'mlx' and 'mlx-lm' packages. "
|
||||
"Install with: pip install 'soup-cli[mlx]'"
|
||||
"Install with: pip install \"soup-cli[mlx]\""
|
||||
) from exc
|
||||
|
||||
def setup(self, dataset: dict) -> None:
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ class MLXSFTTrainerWrapper:
|
|||
except ImportError as exc:
|
||||
raise ImportError(
|
||||
"MLX backend requires the 'mlx' and 'mlx-lm' packages. "
|
||||
"Install with: pip install 'soup-cli[mlx]'"
|
||||
"Install with: pip install \"soup-cli[mlx]\""
|
||||
) from exc
|
||||
|
||||
def _check_unsupported(self) -> None:
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ def _import_online_dpo():
|
|||
except ImportError as exc: # pragma: no cover — trl ships in [train]
|
||||
raise ImportError(
|
||||
"task='online_dpo' requires trl with OnlineDPO support "
|
||||
"(pip install 'soup-cli[train]')"
|
||||
"(pip install \"soup-cli[train]\")"
|
||||
) from exc
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1098,7 +1098,7 @@ class SFTTrainerWrapper:
|
|||
except ImportError:
|
||||
raise ImportError(
|
||||
"librosa is required for audio training. "
|
||||
"Install with: pip install 'soup-cli[audio]'"
|
||||
"Install with: pip install \"soup-cli[audio]\""
|
||||
)
|
||||
|
||||
def load_and_format_audio(example):
|
||||
|
|
|
|||
|
|
@ -318,7 +318,7 @@ def _load_safetensors_arrays(path: str, field: str) -> dict[str, Any]:
|
|||
raise ImportError(
|
||||
"this adapter holds non-numpy dtypes (likely bf16); "
|
||||
"converting it requires torch "
|
||||
"(pip install 'soup-cli[train]')"
|
||||
"(pip install \"soup-cli[train]\")"
|
||||
) from exc
|
||||
try:
|
||||
tensors = torch_load_file(path)
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
Generate tool schemas + descriptions optimized via textual gradients.
|
||||
Schema + validators from v0.68.0 Part C; the live optimizer pass lands in
|
||||
v0.71.13 (#227), lazy-importing TextGrad / GEPA with a friendly
|
||||
``ImportError`` (``pip install 'soup-cli[compile]'``).
|
||||
``ImportError`` (``pip install "soup-cli[compile]"``).
|
||||
|
||||
Composes with v0.46 Agent Forge (OpenAPI / MCP / GraphQL parser) — Agent
|
||||
Forge produces the spec, ``compile-tools`` optimises the descriptions.
|
||||
|
|
@ -26,7 +26,7 @@ _SUPPORTED_SPEC_EXTENSIONS: frozenset[str] = frozenset({".json", ".yaml", ".yml"
|
|||
|
||||
_MAX_OPTIMIZER_NAME_LEN = 32
|
||||
_INSTALL_HINT = (
|
||||
"Run: pip install 'soup-cli[compile]' (installs textgrad / gepa)"
|
||||
"Run: pip install \"soup-cli[compile]\" (installs textgrad / gepa)"
|
||||
)
|
||||
|
||||
# Injectable seam: tests set this to a ``(description, examples, optimizer)
|
||||
|
|
|
|||
|
|
@ -192,7 +192,7 @@ def resolve_tokenizer(tokenizer: Any, *, trust_remote_code: bool = False) -> Any
|
|||
except ImportError as exc:
|
||||
raise ValueError(
|
||||
"soup data doctor needs transformers to load a tokenizer — "
|
||||
"install with: pip install 'soup-cli[train]'"
|
||||
"install with: pip install \"soup-cli[train]\""
|
||||
) from exc
|
||||
try:
|
||||
return AutoTokenizer.from_pretrained(tokenizer, trust_remote_code=trust_remote_code)
|
||||
|
|
|
|||
|
|
@ -290,7 +290,7 @@ def check_near_duplicates(
|
|||
name="near_duplicates",
|
||||
verdict="OK",
|
||||
message="near-dup check skipped (datasketch not installed)",
|
||||
evidence="pip install 'soup-cli[data]' to enable",
|
||||
evidence="pip install \"soup-cli[data]\" to enable",
|
||||
)
|
||||
if len(rows) < 2:
|
||||
return LintCheck(
|
||||
|
|
|
|||
|
|
@ -305,7 +305,7 @@ def run_delinearize(
|
|||
except ImportError as exc:
|
||||
raise ImportError(
|
||||
"delinearize-llama4 requires torch + safetensors "
|
||||
"(pip install 'soup-cli[train]')"
|
||||
"(pip install \"soup-cli[train]\")"
|
||||
) from exc
|
||||
|
||||
# Containment BEFORE makedirs — a directly constructed plan must not
|
||||
|
|
|
|||
|
|
@ -134,7 +134,7 @@ def resolve_tokenizer(tokenizer: object) -> object:
|
|||
except ImportError as exc:
|
||||
raise ValueError(
|
||||
"tokenizer-aware diagnose needs transformers — "
|
||||
"install with: pip install 'soup-cli[train]'"
|
||||
"install with: pip install \"soup-cli[train]\""
|
||||
) from exc
|
||||
try:
|
||||
return AutoTokenizer.from_pretrained(tokenizer)
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ console = Console(stderr=True)
|
|||
# datasets / bitsandbytes / accelerate) moved out of the core install into the
|
||||
# `[train]` extra. A missing one of these surfaces this single, actionable fix.
|
||||
# The `\\[` escapes the literal `[` for Rich markup (it renders as `[train]`).
|
||||
_TRAIN_FIX = "Training needs the \\[train] extra. Run: pip install 'soup-cli\\[train]'"
|
||||
_TRAIN_FIX = "Training needs the \\[train] extra. Run: pip install \"soup-cli\\[train]\""
|
||||
|
||||
# Map known error patterns to (short message, fix suggestion)
|
||||
ERROR_MAP = [
|
||||
|
|
@ -38,22 +38,22 @@ ERROR_MAP = [
|
|||
(
|
||||
"No module named 'fastapi'",
|
||||
"FastAPI is not installed (needed for soup serve).",
|
||||
"Run: pip install 'soup-cli\\[serve]'",
|
||||
"Run: pip install \"soup-cli\\[serve]\"",
|
||||
),
|
||||
(
|
||||
"No module named 'uvicorn'",
|
||||
"Uvicorn is not installed (needed for soup serve).",
|
||||
"Run: pip install 'soup-cli\\[serve]'",
|
||||
"Run: pip install \"soup-cli\\[serve]\"",
|
||||
),
|
||||
(
|
||||
"No module named 'datasketch'",
|
||||
"Datasketch is not installed (needed for dedup).",
|
||||
"Run: pip install 'soup-cli\\[data]'",
|
||||
"Run: pip install \"soup-cli\\[data]\"",
|
||||
),
|
||||
(
|
||||
"No module named 'lm_eval'",
|
||||
"lm-evaluation-harness is not installed (needed for eval).",
|
||||
"Run: pip install 'soup-cli\\[eval]'",
|
||||
"Run: pip install \"soup-cli\\[eval]\"",
|
||||
),
|
||||
(
|
||||
"No module named 'wandb'",
|
||||
|
|
@ -63,12 +63,12 @@ ERROR_MAP = [
|
|||
(
|
||||
"No module named 'deepspeed'",
|
||||
"DeepSpeed is not installed.",
|
||||
"Run: pip install 'soup-cli\\[deepspeed]'",
|
||||
"Run: pip install \"soup-cli\\[deepspeed]\"",
|
||||
),
|
||||
(
|
||||
"No module named 'httpx'",
|
||||
"httpx is not installed (needed for data generate).",
|
||||
"Run: pip install 'soup-cli\\[generate]'",
|
||||
"Run: pip install \"soup-cli\\[generate]\"",
|
||||
),
|
||||
# Heavy training stack — all moved to the [train] extra in v0.71.0.
|
||||
(
|
||||
|
|
|
|||
|
|
@ -150,7 +150,7 @@ def apply_vllm_sleep_mode(engine_args: object) -> object:
|
|||
if version is None:
|
||||
raise RuntimeError(
|
||||
"vllm_sleep_mode requires vLLM >= 0.7 but vLLM is not "
|
||||
"installed (pip install 'soup-cli[serve-fast]')."
|
||||
"installed (pip install \"soup-cli[serve-fast]\")."
|
||||
)
|
||||
if version < _MIN_VLLM_SLEEP_VERSION:
|
||||
found = ".".join(str(part) for part in version) or "unknown"
|
||||
|
|
|
|||
|
|
@ -115,7 +115,7 @@ def validate_liger_config(use_liger: bool, backend: str, device: str) -> list[st
|
|||
if not check_liger_available():
|
||||
errors.append(
|
||||
"liger-kernel is not installed. "
|
||||
"Install it with: pip install 'soup-cli[liger]'"
|
||||
"Install it with: pip install \"soup-cli[liger]\""
|
||||
)
|
||||
|
||||
if backend == "unsloth":
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ def create_mii_pipeline(
|
|||
if not is_mii_available():
|
||||
raise ImportError(
|
||||
"deepspeed-mii is not installed. "
|
||||
"Install with: pip install 'soup-cli[mii]' "
|
||||
"Install with: pip install \"soup-cli[mii]\" "
|
||||
"or pip install deepspeed-mii"
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -241,7 +241,7 @@ def build_compile_plan(
|
|||
|
||||
|
||||
_INSTALL_HINT = (
|
||||
"Run: pip install 'soup-cli[compile]' (installs dspy-ai / textgrad / gepa)"
|
||||
"Run: pip install \"soup-cli[compile]\" (installs dspy-ai / textgrad / gepa)"
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -244,7 +244,7 @@ def _resolve_tokenizer(tokenizer: Union[str, Any]) -> Any:
|
|||
except ImportError as exc:
|
||||
raise ValueError(
|
||||
"tokenizer-aware prune-prompt needs transformers — "
|
||||
"install with: pip install 'soup-cli[train]'"
|
||||
"install with: pip install \"soup-cli[train]\""
|
||||
) from exc
|
||||
try:
|
||||
return AutoTokenizer.from_pretrained(tokenizer)
|
||||
|
|
|
|||
|
|
@ -510,8 +510,11 @@ class TestExtrasHintsAreEscaped:
|
|||
def test_plain_exception_hints_are_not_escaped(self):
|
||||
"""The counter-rule: non-Rich text must NOT gain a backslash.
|
||||
|
||||
`raise ImportError("... pip install 'soup-cli[mlx]'")` never reaches
|
||||
`raise ImportError('... pip install "soup-cli[mlx]"')` never reaches
|
||||
Rich, so escaping it would surface a literal backslash to the user.
|
||||
|
||||
The quoting itself is v0.71.37's business (cmd.exe cannot strip `'`);
|
||||
this test only cares that the bracket stays bare here.
|
||||
"""
|
||||
import pathlib
|
||||
|
||||
|
|
@ -519,7 +522,9 @@ class TestExtrasHintsAreEscaped:
|
|||
|
||||
root = pathlib.Path(soup_cli.__file__).parent
|
||||
text = (root / "trainer" / "mlx_sft.py").read_text(encoding="utf-8")
|
||||
assert "'soup-cli[mlx]'" in text
|
||||
# Quote-agnostic on purpose: the shell quoting around the name is
|
||||
# v0.71.37's concern, this rule is only about the bracket staying bare.
|
||||
assert "soup-cli[mlx]" in text
|
||||
assert "soup-cli\\[mlx]" not in text
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,139 @@
|
|||
"""v0.71.37 — every printed `pip install soup-cli[extra]` hint must be cmd.exe-safe.
|
||||
|
||||
The single-quoted spelling is bash / zsh / PowerShell syntax. Windows
|
||||
`cmd.exe` has no single-quote quoting: it hands the quotes to pip verbatim and
|
||||
pip rejects the requirement outright:
|
||||
|
||||
ERROR: Invalid requirement: "'soup-cli[train]'": Expected package name at
|
||||
the start of dependency specifier
|
||||
|
||||
Measured on Windows before this fix:
|
||||
|
||||
cmd.exe 'soup-cli[train]' -> ERROR "soup-cli[train]" -> ok bare -> ok
|
||||
PowerShell 'soup-cli[train]' -> ok "soup-cli[train]" -> ok
|
||||
|
||||
Double quotes are the only spelling that works in *every* shell, which is why
|
||||
the repo already says `pip install -e ".[dev]"`. Bare `soup-cli[train]` is not
|
||||
an option: zsh globs the bracket and fails with `no matches found`.
|
||||
|
||||
Nothing in Soup can rescue the command once it is typed — pip and the shell own
|
||||
it, and Soup is not installed yet when the README command runs. The only lever
|
||||
is what we print, so these tests guard that.
|
||||
"""
|
||||
|
||||
import pathlib
|
||||
import re
|
||||
|
||||
import pytest
|
||||
|
||||
# `pip install "soup-cli[x]"` and the Rich-escaped `pip install "soup-cli\[x]"`.
|
||||
# `\\*` (any run of backslashes), NOT `\\?`: the escaped hint carries TWO
|
||||
# backslash characters in the source, so `\\?` silently misses every Rich hint —
|
||||
# including the `soup ui` one that started this — and the guard passes vacuously.
|
||||
SINGLE_QUOTED = re.compile(r"pip install 'soup-cli\\*\[[a-z][a-z0-9-]*\]'")
|
||||
|
||||
|
||||
def _package_root() -> pathlib.Path:
|
||||
import soup_cli
|
||||
|
||||
return pathlib.Path(soup_cli.__file__).parent
|
||||
|
||||
|
||||
def _repo_root() -> pathlib.Path | None:
|
||||
"""src-layout: <repo>/src/soup_cli. None when installed as a plain wheel."""
|
||||
root = _package_root().parent.parent
|
||||
return root if (root / "pyproject.toml").is_file() else None
|
||||
|
||||
|
||||
class TestHintsAreCmdSafe:
|
||||
def test_no_single_quoted_hint_in_package(self):
|
||||
"""Nothing under src/soup_cli may print the cmd-hostile spelling."""
|
||||
root = _package_root()
|
||||
offenders = []
|
||||
for path in sorted(root.rglob("*.py")):
|
||||
rel = path.relative_to(root).as_posix()
|
||||
for lineno, line in enumerate(
|
||||
path.read_text(encoding="utf-8").splitlines(), start=1
|
||||
):
|
||||
if SINGLE_QUOTED.search(line):
|
||||
offenders.append(f"{rel}:{lineno}: {line.strip()}")
|
||||
assert not offenders, (
|
||||
"single-quoted soup-cli[extra] hint — cmd.exe passes the quotes to "
|
||||
'pip and it errors out. Use \\"soup-cli[extra]\\" (works in every '
|
||||
"shell):\n" + "\n".join(offenders)
|
||||
)
|
||||
|
||||
def test_no_single_quoted_hint_in_docs_code_blocks(self):
|
||||
"""No fenced block may hand a reader a command that dies on cmd.exe.
|
||||
|
||||
Scoped to fenced code blocks on purpose: those are what people copy.
|
||||
Prose is allowed to *name* the broken spelling — the README note does
|
||||
exactly that, so someone who followed an older video recognises their
|
||||
error and can search for it.
|
||||
"""
|
||||
repo = _repo_root()
|
||||
if repo is None:
|
||||
pytest.skip("not an editable/source checkout — docs/ unavailable")
|
||||
targets = [repo / "README.md", *sorted((repo / "docs").rglob("*.md"))]
|
||||
offenders = []
|
||||
for path in targets:
|
||||
if not path.is_file():
|
||||
continue
|
||||
# Plans are a historical record of what was decided, not advice.
|
||||
if "superpowers/plans" in path.as_posix():
|
||||
continue
|
||||
rel = path.relative_to(repo).as_posix()
|
||||
in_fence = False
|
||||
for lineno, line in enumerate(
|
||||
path.read_text(encoding="utf-8").splitlines(), start=1
|
||||
):
|
||||
if line.lstrip().startswith("```"):
|
||||
in_fence = not in_fence
|
||||
continue
|
||||
if in_fence and SINGLE_QUOTED.search(line):
|
||||
offenders.append(f"{rel}:{lineno}: {line.strip()}")
|
||||
assert not offenders, (
|
||||
"single-quoted soup-cli[extra] in a docs code block — a Windows "
|
||||
"cmd.exe reader copies this and gets `Invalid requirement`:\n"
|
||||
+ "\n".join(offenders)
|
||||
)
|
||||
|
||||
def test_regex_actually_matches_the_broken_spelling(self):
|
||||
"""Guard the guard: a regex that matches nothing would pass vacuously.
|
||||
|
||||
The first cut used `\\\\?` and matched at most ONE backslash, so it saw
|
||||
the plain `'soup-cli[fast]'` sites but not a single Rich-escaped one --
|
||||
i.e. it would have gone green while `soup ui` still printed a command
|
||||
that dies on cmd.exe. Both spellings are pinned here on purpose.
|
||||
"""
|
||||
# Plain (YAML template comments, plain exception text).
|
||||
assert SINGLE_QUOTED.search("pip install 'soup-cli[train]'")
|
||||
# Rich-escaped, exactly as it sits in ui.py: TWO backslash chars.
|
||||
assert SINGLE_QUOTED.search(
|
||||
'"Install with: [bold]pip install \'soup-cli\\\\[ui]\'[/]"'
|
||||
)
|
||||
# The spellings we are migrating *to* must not be flagged.
|
||||
assert not SINGLE_QUOTED.search('pip install "soup-cli[train]"')
|
||||
assert not SINGLE_QUOTED.search('pip install "soup-cli\\\\[ui]"')
|
||||
assert not SINGLE_QUOTED.search("pip install soup-cli")
|
||||
|
||||
|
||||
class TestDoubleQuotesSurviveRich:
|
||||
"""The bracket still needs escaping — double quotes do not change that."""
|
||||
|
||||
def test_rich_keeps_escaped_bracket_inside_double_quotes(self):
|
||||
from io import StringIO
|
||||
|
||||
from rich.console import Console
|
||||
|
||||
def render(markup: str) -> str:
|
||||
buf = StringIO()
|
||||
Console(file=buf, force_terminal=False, width=100).print(markup)
|
||||
return buf.getvalue().strip()
|
||||
|
||||
assert render('[bold]pip install "soup-cli\\[ui]"[/]') == (
|
||||
'pip install "soup-cli[ui]"'
|
||||
), "escaped bracket must survive inside double quotes"
|
||||
assert render('[bold]pip install "soup-cli[ui]"[/]') == (
|
||||
'pip install "soup-cli"'
|
||||
), "unescaped bracket is still eaten — quoting does not fix escaping"
|
||||
Loading…
Reference in New Issue