What's New
Eight features closing out the v0.44.x live-monitoring deferrals plus a long tail of standalone CLI wins.
- #94 Live SSE training stream —
/api/train/streamasync endpoint with per-subscriber cursor (multi-tab safe);SoupTrainerCallback.on_logpushesTrainEventto a thread-safecollections.deque(maxlen=1000); JSEventSourceconsumer auto-installed in the Web UI (window.onTrainEvent(payload)hook for plugins). - #95 Phone visibility via QR —
soup ui --publicbinds0.0.0.0, auto-derives the LAN IP via aSOCK_DGRAMconnect-trick, prints a scannable ASCII QR code with the Bearer token embedded in?token=…. The SPA bootstrap hydrateswindow._authTokenfrom the query string + caches insessionStorage+history.replaceStates the URL clean so the token never sits in browser history.--auth-token <T>lets operators pin a stable token across restarts (16-128 urlsafe-base64 chars). - #98 Reasoning-trace stripping —
soup serve --reasoning-parser {deepseek-r1, qwen3, phi4, openthinker}strips<think>…</think>(and OpenThinker<|begin_of_thought|>…) from responses AFTER token metering so usage counts stay honest. Pre-compiled regex + 1 MiB cap + marker-token fast-path skip + leading-newline-only strip (preserves leading whitespace in code outputs). - #100 Tool Outputs panel — module-level
ToolOutputsBuffersingleton +/api/tool-outputsJSON poll endpoint; trainer callback observes per-batchtool_callscount as a best-effort hook (no impact on training when off). - #15
soup tokenizer train— train a BPE tokenizer from a JSONL or .txt corpus via thetokenizerslibrary. Raw-pathos.lstat + S_ISLNKrejection (TOCTOU defence), 50 MiB total + 8 KiB per-line caps, post-mkdiroutput-dir re-check,--special-tokenNUL/oversize/dedup, vocab bounds[256, 200_000]. - #26 Tail latency in bench —
soup bench --p50 --p95renders an extra per-prompt Rich table;--prompts-filegained symlink rejection on the raw path. - #28 Bench backend auto-detect —
soup bench --backend auto(new default) probes MLXweights.npz(per-entrylstatsymlink reject) →config.jsonmodel_typekeyword → falls back totransformers.SOUP_BENCH_BACKENDenv override available. - #12 Synthetic data walkthrough —
examples/synthetic_workflow.{md,yaml}chainssoup data generate → filter → score → decontaminate → trainfor an end-to-end recipe.
Install / Upgrade
pip install -U soup-cli==0.53.9
# or pin in pyproject.tomlGHCR Docker image: ghcr.io/makazhanalpamys/soup:0.53.9 (publishes ~10 min after release).
Security
3 review-agent waves (python / code / security / tdd) closed 0 CRITICAL + 11 HIGH + 14 MEDIUM + 9 LOW findings before tag. Notable:
set_auth_tokenrotation race fixed with a module-levelthreading.Lock(8-thread stress test); concurrent rotation can no longer leave a window where some requests see the old token and some the new.- QR token consumption: previously the token was rendered in the QR URL but no JS path picked it up — phones landed unauthenticated. Now the SPA bootstrap installs the token from
?token=on first load. - Tokenizer
--input+--outputsymlink TOCTOU defence on the raw path BEFOREos.path.realpathresolution (matching v0.53.7 #106 policy). - SSE generator switched from sync
time.sleeptoasync def+asyncio.sleepso uvicorn's loop stays unblocked under concurrent subscribers. - CORS regex auto-widens to loopback + RFC1918 in
--publicmode (the previous fixed allowlisthttp://0.0.0.0:portnever matched a realOriginheader). _has_mlx_weightsper-entrylstatso a symlinkedweights.npzcannot trickbench --backend autointo MLX dispatch.
Known Limitations
- SSE generator exits after
max_ticks=200(~20 s of quiet). Long-running quiet windows require client reconnect (standard SSE convention). --reasoning-parserstrip happens AFTER token metering: usage counts reflect the model's full output including the reasoning trace; only the visible response body has it stripped. Design choice for accurate billing.detect_backendreturnstransformerssilently for HF repo IDs (cwd-containment check short-circuits before MLX/config probing).--publicmode trusts the operator's LAN segment; Bearer token is the actual security gate.
Tests
8285 passed / 35 skipped on Ubuntu + macOS + Windows × Python 3.9 / 3.11 / 3.12.
Test count: 8257 → 8285 (+28 net; +57 in test_v0539.py minus relaxed v0.53.8 version-pin asserts).