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:
Alpamys 2026-07-17 20:40:54 +05:00
parent 83c4c6d60b
commit a95fedeb0e
69 changed files with 341 additions and 157 deletions

View File

@ -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

View File

@ -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
```

View File

@ -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) &middot; [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

View File

@ -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
```

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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).

View File

@ -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)
```

View File

@ -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

View File

@ -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

View File

@ -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:

View File

@ -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

View File

@ -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"

View File

@ -1,3 +1,3 @@
"""Soup CLI — Fine-tune and post-train LLMs in one command."""
__version__ = "0.71.36"
__version__ = "0.71.37"

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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:

View File

@ -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:

View File

@ -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)

View File

@ -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)

View File

@ -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]\"[/]"
)

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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:

View File

@ -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"
)

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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):

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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:

View File

@ -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:

View File

@ -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:

View File

@ -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

View File

@ -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):

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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(

View File

@ -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

View File

@ -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)

View File

@ -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.
(

View File

@ -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"

View File

@ -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":

View File

@ -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"
)

View File

@ -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)"
)

View File

@ -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)

View File

@ -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

139
tests/test_v07137.py Normal file
View File

@ -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"