laya-cli 0.2.2__tar.gz → 0.2.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,299 @@
1
+ Metadata-Version: 2.5
2
+ Name: laya-cli
3
+ Version: 0.2.3
4
+ Summary: Ergonomic CLI for Laya — typed decisions for humans and AI agents (predict, batch, presets, shortlist, router)
5
+ Project-URL: Homepage, https://github.com/MIt9/laya-cli
6
+ Project-URL: Repository, https://github.com/MIt9/laya-cli.git
7
+ Project-URL: Issues, https://github.com/MIt9/laya-cli/issues
8
+ Author: MIt9
9
+ License: Apache-2.0
10
+ License-File: LICENSE
11
+ Keywords: ai,cli,decision-model,guardrails,laya,router,triage
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: Apache Software License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Programming Language :: Python :: 3.14
21
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
22
+ Requires-Python: >=3.10
23
+ Requires-Dist: huggingface-hub>=0.20.0
24
+ Requires-Dist: laya>=0.3.4
25
+ Requires-Dist: numpy>=1.20.0
26
+ Requires-Dist: safetensors>=0.4.0
27
+ Requires-Dist: torch>=2.0.0
28
+ Requires-Dist: transformers>=4.48.0
29
+ Provides-Extra: dev
30
+ Requires-Dist: mypy>=1.10; extra == 'dev'
31
+ Requires-Dist: pytest-cov>=4.0; extra == 'dev'
32
+ Requires-Dist: pytest>=8.0; extra == 'dev'
33
+ Requires-Dist: ruff>=0.9; extra == 'dev'
34
+ Description-Content-Type: text/markdown
35
+
36
+ # ✨ Laya CLI (`laya-cli`)
37
+
38
+ [![License: Apache-2.0](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
39
+ [![Python Version](https://img.shields.io/badge/python-3.10%2B-blue.svg)](https://www.python.org/downloads/)
40
+ [![Built with uv](https://img.shields.io/badge/built%20with-uv-purple.svg)](https://github.com/astral-sh/uv)
41
+ [![PyPI version](https://img.shields.io/pypi/v/laya-cli.svg)](https://pypi.org/project/laya-cli/)
42
+
43
+ A standalone, high-performance CLI for [Laya](https://github.com/NandhaKishorM/laya) — **typed decisions** (`choice` / `score` / `noul`) in a single forward pass. Built for both **humans** (pretty tables) and **AI agents** (machine-readable `json`/`jsonl`).
44
+
45
+ - No text generation, no hallucination — ~33 ms for one question, ~7 ms/q batched (T4)
46
+ - One model load, many questions at once; presets, shortlisting, multilingual routing, and an optional resident daemon
47
+
48
+ ---
49
+
50
+ ## ⚡ Key Features
51
+
52
+ * **Typed Decisions in One Pass** — `choice` (label + calibrated probs), `score` (ordinal expected value), `noul` (P(true)). All questions in one `agent.predict(state, questions)` call.
53
+ * **Router-Aware Multilingual** — `laya.Router(preload=True)` auto-detects script/language in <0.5 ms and routes to `laya` (English, 512 ctx, 421M) vs `laya-multilingual` (100+ langs, 1024 ctx, 322M). Warns if `--router` without `--lang`.
54
+ * **Preset Library** — `triage` / `email` / `guard` / `moderation` / `router` are direct `laya.*_questions()` passthroughs, mergeable with `--questions file.json` and `--questions-inline '{"id": {...}}'`.
55
+ * **Embedding Shortlist for High-Cardinality** — `--shortlist-k 20` via `laya.predict_shortlist` + `embed_fn_from_agent` (Banking77 77 options → ~3 tokens/opt without shortlist → shortlist restores accuracy).
56
+ * **Streaming Batch Mode** — `classify` / `predict --input` loads the model **once** + warmup throwaway `predict`, streams JSONL. `--state-field` is taken **verbatim** (no silent `(photographer: Name)` injection).
57
+ * **Post-Filter & Eval** — `filter --where "on_topic>=0.4" --sort -on_topic` and `evaluate` (accuracy, passing @ threshold, precision @ threshold, escalation rate).
58
+ * **Resident Daemon (optional)** — `laya-cli serve` keeps the model in RAM; subsequent `predict`/`classify`/`evaluate` hit `127.0.0.1` and skip the 10–35 s `laya.load()` on repeated calls. Per-config daemon file `~/.cache/laya-cli/daemons/<hash>.json`, idle-timeout auto-exit.
59
+
60
+ ---
61
+
62
+ ## 🚀 Installation
63
+
64
+ ### Option 1: `uv` (recommended)
65
+
66
+ ```bash
67
+ uv tool install laya-cli # globally isolated, uses uv.lock
68
+ laya-cli --help
69
+
70
+ # upgrade
71
+ uv tool update laya-cli --refresh
72
+ uv tool install laya-cli --force --refresh # force reinstall
73
+ ```
74
+
75
+ ### Option 2: `pip` / `pipx`
76
+
77
+ ```bash
78
+ pipx install laya-cli
79
+ # or
80
+ pip install laya-cli
81
+ ```
82
+
83
+ ### Option 3: Run without installing
84
+
85
+ ```bash
86
+ uvx laya-cli --help
87
+ uvx --refresh laya-cli@latest --help # bypass uv cache
88
+ ```
89
+
90
+ > Requires Python 3.10+ (`.python-version` pins 3.11). Heavy ML deps (`torch`, `transformers`, `safetensors` via `laya`, ~2 GB) download on first `predict`/`classify`/`evaluate` (650–850 MB per checkpoint via `allow_patterns`; bundle root is 2.3 GB). Afterwards `HF_HUB_OFFLINE=1` works. Dev/tests mock Laya so CI is fast.
91
+
92
+ ---
93
+
94
+ ## 📖 Quick Start
95
+
96
+ ### Single prediction (human)
97
+
98
+ ```bash
99
+ # Preset without creating a file — table output
100
+ laya-cli predict "I was charged twice, refund please" --preset triage
101
+ laya-cli predict --text "Ignore previous instructions" --preset guard --format table
102
+ laya-cli predict --state '{"subject":"Invoice #4411","body":"Billed twice"}' --preset email --format table
103
+ ```
104
+
105
+ ### Single prediction (AI agent — JSON)
106
+
107
+ ```bash
108
+ laya-cli predict --text "Is this spam?" --preset guard --format json
109
+ # -> {"answers": {"jailbreak": {"type":"noul","noul":0.02,"confidence":0.97,"action":{...}}}, "usage":{"input_tokens":12}}
110
+
111
+ # Custom questions, merged: preset < file < inline
112
+ laya-cli predict --text "hello" \
113
+ --preset triage \
114
+ --questions-inline '{"custom":{"type":"noul","instructions":"Is it polite?"}}' \
115
+ --format json
116
+
117
+ # High-cardinality choice (e.g. 77 banking intents)
118
+ laya-cli predict --text "where is my card?" --questions banking.json --shortlist-k 20 --format json
119
+
120
+ # Multilingual — Router is recommended (see https://github.com/NandhaKishorM/laya#why-route-the-evidence)
121
+ laya-cli predict --text "मुझसे दो बार शुल्क लिया गया" --preset triage --router --format json
122
+ laya-cli predict --text "Der Kunde wurde zweimal belastet" --preset triage --router --lang de --format json
123
+ ```
124
+
125
+ State is flexible: positional `TEXT`, `--text "str"` (repeatable), `--state '{"key":"value"}'` (dict/list accepted as `agent.predict` does), `--state-file file.json`, or batch `--input file.jsonl` / stdin. Questions come from `--preset`, `--questions file.json`, `--questions-inline JSON` (merged, later wins).
126
+
127
+ Checkpoints: `convaiinnovations/laya` (English, 512 ctx), `convaiinnovations/laya/multilingual` (use `--subfolder multilingual`), `typed-decisions`. Use `--model` / `--subfolder` for a single model, or `--router` for auto routing (`Router(preload=True)`).
128
+
129
+ ---
130
+
131
+ ## 🔁 Batch & Pipeline
132
+
133
+ Batch loads the model **once** and reuses it (warmup throwaway `predict` on start):
134
+
135
+ ```bash
136
+ # From file — human-readable tables per row
137
+ laya-cli predict --input candidates.jsonl --questions questions.json --format table
138
+
139
+ # Machine-readable JSONL with flattened fields (like classify) for jq/filter
140
+ laya-cli predict --input candidates.jsonl --questions questions.json --flatten --format jsonl > scored.jsonl
141
+ cat scored.jsonl | laya-cli filter --where "on_topic>=0.4,is_relevant>=0.7" --sort -on_topic,+id
142
+
143
+ # Legacy streaming (kept for compatibility with TASK.md / pexels-cli)
144
+ cat candidates.jsonl | laya-cli classify --questions questions.json | laya-cli filter --where "on_topic>=0.4" --sort -on_topic > shortlist.jsonl
145
+ ```
146
+
147
+ **Pexels integration** — the original use case (82 candidates, `--state` deduped):
148
+
149
+ ```bash
150
+ # 1. Pexels → candidates.jsonl with `state` strings (via https://github.com/MIt9/pexels-cli)
151
+ px videos --queries "black friday shopping,christmas shopping,checkout cart" \
152
+ --per-page 8 --state --dedupe keep-first > candidates.jsonl
153
+
154
+ # 2. Laya → score + filter (one model load, streaming)
155
+ cat candidates.jsonl \
156
+ | laya-cli classify --questions questions.json \
157
+ | laya-cli filter --where "on_topic>=0.4" --sort -on_topic \
158
+ > shortlist.jsonl
159
+
160
+ # Or with the primary command (supports presets without a file):
161
+ cat candidates.jsonl | laya-cli predict --questions questions.json --format jsonl > scored.jsonl
162
+ px videos --queries "..." --state --dedupe | laya-cli predict --preset triage --format jsonl | laya-cli filter --where "intent==refund"
163
+ ```
164
+
165
+ Candidate JSONL format (`px --state`):
166
+
167
+ ```json
168
+ {"id": 5890229, "type": "video", "query": "black friday shopping", "photographer": "Pavel Danilyuk", "state": "a man shopping on black friday", "url": "https://www.pexels.com/video/a-man-shopping-on-black-friday-5890229/", "duration": 10, "width": 2160, "height": 3840}
169
+ ```
170
+
171
+ `--state-field` is taken **verbatim**. Never silently prepend metadata — use `--prepend-field NAME` only explicitly (mixing `(photographer: Name)` degraded `on_topic` by 0.1–0.3 in the real Pexels case).
172
+
173
+ ---
174
+
175
+ ## 📚 Commands
176
+
177
+ ```
178
+ laya-cli predict [TEXT ...] [--text TEXT] [--state JSON] [--input file.jsonl] [--preset NAME] [--questions file.json] [--router] [--shortlist-k K] [--format json|jsonl|table] [--no-daemon]
179
+ laya-cli classify --questions q.json [--state-field state] [--router] [--device cpu|mps|cuda] [--no-daemon] # batch JSONL stdin→stdout, legacy
180
+ laya-cli filter --where "on_topic>=0.4" --sort -on_topic # JSONL sort/filter
181
+ laya-cli questions list # list presets
182
+ laya-cli questions triage > questions.json # also: email, guard, moderation, router (alias: presets)
183
+ laya-cli evaluate --questions q.json --labeled labeled.jsonl --label-field label --threshold 0.5 [--no-daemon]
184
+ laya-cli serve [--model ID] [--device mps] [--router] [--port 0] [--idle-timeout 1800] [--foreground] # daemon
185
+ laya-cli serve status [--model ID] [--subfolder NAME] [--device NAME] [--router]
186
+ laya-cli serve stop [--model ID] [--all]
187
+ laya-cli info [--model ID] [--subfolder NAME]
188
+ laya-cli --help; laya-cli <command> --help # every command shows examples
189
+ ```
190
+
191
+ ### Presets & Info
192
+
193
+ ```bash
194
+ laya-cli questions list --format table
195
+ laya-cli questions triage > questions.json
196
+ laya-cli presets guard | laya-cli predict --text "test" --questions /dev/stdin --format json
197
+
198
+ laya-cli info # python/torch/cuda/mps + HF cache
199
+ laya-cli evaluate --questions questions.json --labeled labeled.jsonl --label-field label --threshold 0.5
200
+ # -> accuracy per question, passing @ threshold, precision @ threshold, escalation rate
201
+ # Measure on 50-200 real examples before trusting probabilities — base checkpoints are near chance on some tasks.
202
+ ```
203
+
204
+ ---
205
+
206
+ ## ⚡ Resident Daemon Mode (optional)
207
+
208
+ Repeated `laya.load()` costs 10–35 s (MPS instantiation) + ~120 ms × N candidates. One pipeline run is fine (35–50 s for 82 videos), but iterative tuning of `questions.json` (`predict` → inspect → edit → `predict` again) pays 10–35 s every time for the same model.
209
+
210
+ `laya-cli serve` keeps the model resident in RAM; subsequent `predict`/`classify`/`evaluate` with the **same config** skip the load and hit `127.0.0.1` in <1 ms.
211
+
212
+ ### Measured baseline (2026-09-22, 82 candidates, M-series MPS)
213
+
214
+ | stage | time |
215
+ |-------|------|
216
+ | `px` (network, 12 queries) | 9.3 s |
217
+ | `laya.load()` (model into RAM) | 10–35 s |
218
+ | inference, 82 candidates | ~10 s (~120 ms each) |
219
+ | `filter` (sort/filter) | 0.08 s |
220
+
221
+ ### Transport (as in `laya-integration` SKILL.md — “run Laya as a small local HTTP sidecar”)
222
+
223
+ HTTP on loopback `127.0.0.1` only — never `0.0.0.0`. Single-threaded with `threading.Lock` (one GPU = one forward pass):
224
+
225
+ - `POST /predict` — `{"state": ..., "questions": {...}, "lang": "...", "shortlist_k": 20}` → same JSON as `agent.predict()` (`laya-cli predict --format json`)
226
+ - `GET /status` — `{"model": "...", "subfolder": "...", "device": "...", "loaded_at": 123..., "idle_seconds": 42, "requests_served": 17, "pid": 12345, "port": 8765, "idle_timeout": 1800}`
227
+ - `POST /shutdown` — graceful stop from localhost only (also `serve stop` sends `SIGTERM` via pid file)
228
+
229
+ ### Lifecycle
230
+
231
+ ```bash
232
+ # Start in background (writes pid+port to ~/.cache/laya-cli/daemons/<hash>.json)
233
+ laya-cli serve --model convaiinnovations/laya --device mps
234
+ laya-cli serve --model convaiinnovations/laya --subfolder multilingual --device cpu --idle-timeout 60
235
+ laya-cli serve --router --foreground --idle-timeout 0 --port 8765 # foreground for logs, 0 disables idle
236
+
237
+ # One daemon = one checkpoint config hash(model|subfolder|device|router|lang) → separate file/port
238
+ # device is resolved before hashing (auto: cuda>mps>cpu), so `serve` and `serve --device mps` share the same file on an MPS host
239
+
240
+ laya-cli serve status # default config (same defaults as predict)
241
+ laya-cli serve status --model convaiinnovations/laya --subfolder multilingual --device cpu
242
+ laya-cli serve stop # graceful via POST /shutdown, removes pid file
243
+ laya-cli serve stop --all # stop all daemons
244
+ curl http://127.0.0.1:<port>/status
245
+ ```
246
+
247
+ Warmup throwaway `predict` runs before serving. `idle-timeout` defaults to 1800 s; `--idle-timeout 5` (for tests) makes the daemon exit after ~5 s of no requests and `serve status` then reports `not running`. `--foreground` blocks and logs to stderr.
248
+
249
+ ### Client side (`predict` / `classify` / `evaluate`)
250
+
251
+ Before `laya.load()`, each command checks `~/.cache/laya-cli/daemons/<hash>.json` for the current config. If the file exists and the daemon answers `GET /status`, requests go to `POST /predict` instead of a local load. If the file is missing or the daemon is dead (stale pid), it silently falls back to in-process behaviour — no pipeline change required. `--no-daemon` forces in-process even if a daemon is live (for reproducibility/debugging).
252
+
253
+ For batch (`classify`, `predict --input`), each JSONL line is a separate `POST /predict` in a loop (loopback overhead is milliseconds, no batch endpoint needed yet).
254
+
255
+ ```bash
256
+ # AI workflow — fully automatic, no extra flags after serve:
257
+ laya-cli serve --device mps & # once per session
258
+ laya-cli predict "hello" --preset guard --format json # via daemon, no 10s load
259
+ laya-cli predict "hello2" --preset guard --format json # still via daemon (<1 ms overhead)
260
+ laya-cli serve status # {"loaded_at":..., "requests_served": 2}
261
+ laya-cli predict "hello" --preset guard --no-daemon --format json # force in-process (10-35s again)
262
+ laya-cli serve stop
263
+
264
+ # Human tuning loop:
265
+ laya-cli serve &
266
+ cat candidates.jsonl | laya-cli classify --questions q.json | laya-cli filter --where "on_topic>=0.4" --sort -on_topic # via daemon
267
+ # ...edit q.json...
268
+ cat candidates.jsonl | laya-cli classify --questions q.json | laya-cli filter --where "on_topic>=0.4" --sort -on_topic # still via daemon
269
+ laya-cli serve stop
270
+
271
+ # 5 parallel predicts — serialized by daemon Lock, all succeed with correct, non-interleaved results:
272
+ seq 1 5 | xargs -P5 -I{} laya-cli predict "text {}" --preset guard --format json
273
+ ```
274
+
275
+ **Security / limits:** binds only `127.0.0.1`, no auth (single-user local machine, as in SKILL.md), one request at a time, stateless apart from the model in RAM, no multi-model hot-swap (new config = new daemon on another port).
276
+
277
+ ---
278
+
279
+ ## 🛠 Development
280
+
281
+ ```bash
282
+ uv sync --group dev # hatchling + ruff/mypy/pytest
283
+ uv run ruff check . # lint (E/F/W/I, line-length 120)
284
+ uv run ruff format . # format
285
+ uv run pytest -q # 30 tests, mocked laya (no model download)
286
+ uv run pytest --cov=src/laya_cli --cov-report=term-missing
287
+ uv run laya-cli --help # every command shows examples
288
+ uv build # hatchling -> dist/*.whl + sdist (src/ layout)
289
+ ```
290
+
291
+ Repo layout: `src/laya_cli/`, `tests/`, `pyproject.toml` (hatchling + `dependency-groups`), `uv.lock`, `.python-version` 3.11, `.github/workflows/ci.yml` + `publish.yml`.
292
+
293
+ ---
294
+
295
+ ## 📄 License
296
+
297
+ Apache-2.0 — same as [Laya](https://github.com/NandhaKishorM/laya) upstream. See [LICENSE](LICENSE).
298
+
299
+ This project is not affiliated with Convai Innovations. Laya weights are Apache-2.0 and hosted on Hugging Face (`convaiinnovations/laya`).
@@ -0,0 +1,264 @@
1
+ # ✨ Laya CLI (`laya-cli`)
2
+
3
+ [![License: Apache-2.0](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
4
+ [![Python Version](https://img.shields.io/badge/python-3.10%2B-blue.svg)](https://www.python.org/downloads/)
5
+ [![Built with uv](https://img.shields.io/badge/built%20with-uv-purple.svg)](https://github.com/astral-sh/uv)
6
+ [![PyPI version](https://img.shields.io/pypi/v/laya-cli.svg)](https://pypi.org/project/laya-cli/)
7
+
8
+ A standalone, high-performance CLI for [Laya](https://github.com/NandhaKishorM/laya) — **typed decisions** (`choice` / `score` / `noul`) in a single forward pass. Built for both **humans** (pretty tables) and **AI agents** (machine-readable `json`/`jsonl`).
9
+
10
+ - No text generation, no hallucination — ~33 ms for one question, ~7 ms/q batched (T4)
11
+ - One model load, many questions at once; presets, shortlisting, multilingual routing, and an optional resident daemon
12
+
13
+ ---
14
+
15
+ ## ⚡ Key Features
16
+
17
+ * **Typed Decisions in One Pass** — `choice` (label + calibrated probs), `score` (ordinal expected value), `noul` (P(true)). All questions in one `agent.predict(state, questions)` call.
18
+ * **Router-Aware Multilingual** — `laya.Router(preload=True)` auto-detects script/language in <0.5 ms and routes to `laya` (English, 512 ctx, 421M) vs `laya-multilingual` (100+ langs, 1024 ctx, 322M). Warns if `--router` without `--lang`.
19
+ * **Preset Library** — `triage` / `email` / `guard` / `moderation` / `router` are direct `laya.*_questions()` passthroughs, mergeable with `--questions file.json` and `--questions-inline '{"id": {...}}'`.
20
+ * **Embedding Shortlist for High-Cardinality** — `--shortlist-k 20` via `laya.predict_shortlist` + `embed_fn_from_agent` (Banking77 77 options → ~3 tokens/opt without shortlist → shortlist restores accuracy).
21
+ * **Streaming Batch Mode** — `classify` / `predict --input` loads the model **once** + warmup throwaway `predict`, streams JSONL. `--state-field` is taken **verbatim** (no silent `(photographer: Name)` injection).
22
+ * **Post-Filter & Eval** — `filter --where "on_topic>=0.4" --sort -on_topic` and `evaluate` (accuracy, passing @ threshold, precision @ threshold, escalation rate).
23
+ * **Resident Daemon (optional)** — `laya-cli serve` keeps the model in RAM; subsequent `predict`/`classify`/`evaluate` hit `127.0.0.1` and skip the 10–35 s `laya.load()` on repeated calls. Per-config daemon file `~/.cache/laya-cli/daemons/<hash>.json`, idle-timeout auto-exit.
24
+
25
+ ---
26
+
27
+ ## 🚀 Installation
28
+
29
+ ### Option 1: `uv` (recommended)
30
+
31
+ ```bash
32
+ uv tool install laya-cli # globally isolated, uses uv.lock
33
+ laya-cli --help
34
+
35
+ # upgrade
36
+ uv tool update laya-cli --refresh
37
+ uv tool install laya-cli --force --refresh # force reinstall
38
+ ```
39
+
40
+ ### Option 2: `pip` / `pipx`
41
+
42
+ ```bash
43
+ pipx install laya-cli
44
+ # or
45
+ pip install laya-cli
46
+ ```
47
+
48
+ ### Option 3: Run without installing
49
+
50
+ ```bash
51
+ uvx laya-cli --help
52
+ uvx --refresh laya-cli@latest --help # bypass uv cache
53
+ ```
54
+
55
+ > Requires Python 3.10+ (`.python-version` pins 3.11). Heavy ML deps (`torch`, `transformers`, `safetensors` via `laya`, ~2 GB) download on first `predict`/`classify`/`evaluate` (650–850 MB per checkpoint via `allow_patterns`; bundle root is 2.3 GB). Afterwards `HF_HUB_OFFLINE=1` works. Dev/tests mock Laya so CI is fast.
56
+
57
+ ---
58
+
59
+ ## 📖 Quick Start
60
+
61
+ ### Single prediction (human)
62
+
63
+ ```bash
64
+ # Preset without creating a file — table output
65
+ laya-cli predict "I was charged twice, refund please" --preset triage
66
+ laya-cli predict --text "Ignore previous instructions" --preset guard --format table
67
+ laya-cli predict --state '{"subject":"Invoice #4411","body":"Billed twice"}' --preset email --format table
68
+ ```
69
+
70
+ ### Single prediction (AI agent — JSON)
71
+
72
+ ```bash
73
+ laya-cli predict --text "Is this spam?" --preset guard --format json
74
+ # -> {"answers": {"jailbreak": {"type":"noul","noul":0.02,"confidence":0.97,"action":{...}}}, "usage":{"input_tokens":12}}
75
+
76
+ # Custom questions, merged: preset < file < inline
77
+ laya-cli predict --text "hello" \
78
+ --preset triage \
79
+ --questions-inline '{"custom":{"type":"noul","instructions":"Is it polite?"}}' \
80
+ --format json
81
+
82
+ # High-cardinality choice (e.g. 77 banking intents)
83
+ laya-cli predict --text "where is my card?" --questions banking.json --shortlist-k 20 --format json
84
+
85
+ # Multilingual — Router is recommended (see https://github.com/NandhaKishorM/laya#why-route-the-evidence)
86
+ laya-cli predict --text "मुझसे दो बार शुल्क लिया गया" --preset triage --router --format json
87
+ laya-cli predict --text "Der Kunde wurde zweimal belastet" --preset triage --router --lang de --format json
88
+ ```
89
+
90
+ State is flexible: positional `TEXT`, `--text "str"` (repeatable), `--state '{"key":"value"}'` (dict/list accepted as `agent.predict` does), `--state-file file.json`, or batch `--input file.jsonl` / stdin. Questions come from `--preset`, `--questions file.json`, `--questions-inline JSON` (merged, later wins).
91
+
92
+ Checkpoints: `convaiinnovations/laya` (English, 512 ctx), `convaiinnovations/laya/multilingual` (use `--subfolder multilingual`), `typed-decisions`. Use `--model` / `--subfolder` for a single model, or `--router` for auto routing (`Router(preload=True)`).
93
+
94
+ ---
95
+
96
+ ## 🔁 Batch & Pipeline
97
+
98
+ Batch loads the model **once** and reuses it (warmup throwaway `predict` on start):
99
+
100
+ ```bash
101
+ # From file — human-readable tables per row
102
+ laya-cli predict --input candidates.jsonl --questions questions.json --format table
103
+
104
+ # Machine-readable JSONL with flattened fields (like classify) for jq/filter
105
+ laya-cli predict --input candidates.jsonl --questions questions.json --flatten --format jsonl > scored.jsonl
106
+ cat scored.jsonl | laya-cli filter --where "on_topic>=0.4,is_relevant>=0.7" --sort -on_topic,+id
107
+
108
+ # Legacy streaming (kept for compatibility with TASK.md / pexels-cli)
109
+ cat candidates.jsonl | laya-cli classify --questions questions.json | laya-cli filter --where "on_topic>=0.4" --sort -on_topic > shortlist.jsonl
110
+ ```
111
+
112
+ **Pexels integration** — the original use case (82 candidates, `--state` deduped):
113
+
114
+ ```bash
115
+ # 1. Pexels → candidates.jsonl with `state` strings (via https://github.com/MIt9/pexels-cli)
116
+ px videos --queries "black friday shopping,christmas shopping,checkout cart" \
117
+ --per-page 8 --state --dedupe keep-first > candidates.jsonl
118
+
119
+ # 2. Laya → score + filter (one model load, streaming)
120
+ cat candidates.jsonl \
121
+ | laya-cli classify --questions questions.json \
122
+ | laya-cli filter --where "on_topic>=0.4" --sort -on_topic \
123
+ > shortlist.jsonl
124
+
125
+ # Or with the primary command (supports presets without a file):
126
+ cat candidates.jsonl | laya-cli predict --questions questions.json --format jsonl > scored.jsonl
127
+ px videos --queries "..." --state --dedupe | laya-cli predict --preset triage --format jsonl | laya-cli filter --where "intent==refund"
128
+ ```
129
+
130
+ Candidate JSONL format (`px --state`):
131
+
132
+ ```json
133
+ {"id": 5890229, "type": "video", "query": "black friday shopping", "photographer": "Pavel Danilyuk", "state": "a man shopping on black friday", "url": "https://www.pexels.com/video/a-man-shopping-on-black-friday-5890229/", "duration": 10, "width": 2160, "height": 3840}
134
+ ```
135
+
136
+ `--state-field` is taken **verbatim**. Never silently prepend metadata — use `--prepend-field NAME` only explicitly (mixing `(photographer: Name)` degraded `on_topic` by 0.1–0.3 in the real Pexels case).
137
+
138
+ ---
139
+
140
+ ## 📚 Commands
141
+
142
+ ```
143
+ laya-cli predict [TEXT ...] [--text TEXT] [--state JSON] [--input file.jsonl] [--preset NAME] [--questions file.json] [--router] [--shortlist-k K] [--format json|jsonl|table] [--no-daemon]
144
+ laya-cli classify --questions q.json [--state-field state] [--router] [--device cpu|mps|cuda] [--no-daemon] # batch JSONL stdin→stdout, legacy
145
+ laya-cli filter --where "on_topic>=0.4" --sort -on_topic # JSONL sort/filter
146
+ laya-cli questions list # list presets
147
+ laya-cli questions triage > questions.json # also: email, guard, moderation, router (alias: presets)
148
+ laya-cli evaluate --questions q.json --labeled labeled.jsonl --label-field label --threshold 0.5 [--no-daemon]
149
+ laya-cli serve [--model ID] [--device mps] [--router] [--port 0] [--idle-timeout 1800] [--foreground] # daemon
150
+ laya-cli serve status [--model ID] [--subfolder NAME] [--device NAME] [--router]
151
+ laya-cli serve stop [--model ID] [--all]
152
+ laya-cli info [--model ID] [--subfolder NAME]
153
+ laya-cli --help; laya-cli <command> --help # every command shows examples
154
+ ```
155
+
156
+ ### Presets & Info
157
+
158
+ ```bash
159
+ laya-cli questions list --format table
160
+ laya-cli questions triage > questions.json
161
+ laya-cli presets guard | laya-cli predict --text "test" --questions /dev/stdin --format json
162
+
163
+ laya-cli info # python/torch/cuda/mps + HF cache
164
+ laya-cli evaluate --questions questions.json --labeled labeled.jsonl --label-field label --threshold 0.5
165
+ # -> accuracy per question, passing @ threshold, precision @ threshold, escalation rate
166
+ # Measure on 50-200 real examples before trusting probabilities — base checkpoints are near chance on some tasks.
167
+ ```
168
+
169
+ ---
170
+
171
+ ## ⚡ Resident Daemon Mode (optional)
172
+
173
+ Repeated `laya.load()` costs 10–35 s (MPS instantiation) + ~120 ms × N candidates. One pipeline run is fine (35–50 s for 82 videos), but iterative tuning of `questions.json` (`predict` → inspect → edit → `predict` again) pays 10–35 s every time for the same model.
174
+
175
+ `laya-cli serve` keeps the model resident in RAM; subsequent `predict`/`classify`/`evaluate` with the **same config** skip the load and hit `127.0.0.1` in <1 ms.
176
+
177
+ ### Measured baseline (2026-09-22, 82 candidates, M-series MPS)
178
+
179
+ | stage | time |
180
+ |-------|------|
181
+ | `px` (network, 12 queries) | 9.3 s |
182
+ | `laya.load()` (model into RAM) | 10–35 s |
183
+ | inference, 82 candidates | ~10 s (~120 ms each) |
184
+ | `filter` (sort/filter) | 0.08 s |
185
+
186
+ ### Transport (as in `laya-integration` SKILL.md — “run Laya as a small local HTTP sidecar”)
187
+
188
+ HTTP on loopback `127.0.0.1` only — never `0.0.0.0`. Single-threaded with `threading.Lock` (one GPU = one forward pass):
189
+
190
+ - `POST /predict` — `{"state": ..., "questions": {...}, "lang": "...", "shortlist_k": 20}` → same JSON as `agent.predict()` (`laya-cli predict --format json`)
191
+ - `GET /status` — `{"model": "...", "subfolder": "...", "device": "...", "loaded_at": 123..., "idle_seconds": 42, "requests_served": 17, "pid": 12345, "port": 8765, "idle_timeout": 1800}`
192
+ - `POST /shutdown` — graceful stop from localhost only (also `serve stop` sends `SIGTERM` via pid file)
193
+
194
+ ### Lifecycle
195
+
196
+ ```bash
197
+ # Start in background (writes pid+port to ~/.cache/laya-cli/daemons/<hash>.json)
198
+ laya-cli serve --model convaiinnovations/laya --device mps
199
+ laya-cli serve --model convaiinnovations/laya --subfolder multilingual --device cpu --idle-timeout 60
200
+ laya-cli serve --router --foreground --idle-timeout 0 --port 8765 # foreground for logs, 0 disables idle
201
+
202
+ # One daemon = one checkpoint config hash(model|subfolder|device|router|lang) → separate file/port
203
+ # device is resolved before hashing (auto: cuda>mps>cpu), so `serve` and `serve --device mps` share the same file on an MPS host
204
+
205
+ laya-cli serve status # default config (same defaults as predict)
206
+ laya-cli serve status --model convaiinnovations/laya --subfolder multilingual --device cpu
207
+ laya-cli serve stop # graceful via POST /shutdown, removes pid file
208
+ laya-cli serve stop --all # stop all daemons
209
+ curl http://127.0.0.1:<port>/status
210
+ ```
211
+
212
+ Warmup throwaway `predict` runs before serving. `idle-timeout` defaults to 1800 s; `--idle-timeout 5` (for tests) makes the daemon exit after ~5 s of no requests and `serve status` then reports `not running`. `--foreground` blocks and logs to stderr.
213
+
214
+ ### Client side (`predict` / `classify` / `evaluate`)
215
+
216
+ Before `laya.load()`, each command checks `~/.cache/laya-cli/daemons/<hash>.json` for the current config. If the file exists and the daemon answers `GET /status`, requests go to `POST /predict` instead of a local load. If the file is missing or the daemon is dead (stale pid), it silently falls back to in-process behaviour — no pipeline change required. `--no-daemon` forces in-process even if a daemon is live (for reproducibility/debugging).
217
+
218
+ For batch (`classify`, `predict --input`), each JSONL line is a separate `POST /predict` in a loop (loopback overhead is milliseconds, no batch endpoint needed yet).
219
+
220
+ ```bash
221
+ # AI workflow — fully automatic, no extra flags after serve:
222
+ laya-cli serve --device mps & # once per session
223
+ laya-cli predict "hello" --preset guard --format json # via daemon, no 10s load
224
+ laya-cli predict "hello2" --preset guard --format json # still via daemon (<1 ms overhead)
225
+ laya-cli serve status # {"loaded_at":..., "requests_served": 2}
226
+ laya-cli predict "hello" --preset guard --no-daemon --format json # force in-process (10-35s again)
227
+ laya-cli serve stop
228
+
229
+ # Human tuning loop:
230
+ laya-cli serve &
231
+ cat candidates.jsonl | laya-cli classify --questions q.json | laya-cli filter --where "on_topic>=0.4" --sort -on_topic # via daemon
232
+ # ...edit q.json...
233
+ cat candidates.jsonl | laya-cli classify --questions q.json | laya-cli filter --where "on_topic>=0.4" --sort -on_topic # still via daemon
234
+ laya-cli serve stop
235
+
236
+ # 5 parallel predicts — serialized by daemon Lock, all succeed with correct, non-interleaved results:
237
+ seq 1 5 | xargs -P5 -I{} laya-cli predict "text {}" --preset guard --format json
238
+ ```
239
+
240
+ **Security / limits:** binds only `127.0.0.1`, no auth (single-user local machine, as in SKILL.md), one request at a time, stateless apart from the model in RAM, no multi-model hot-swap (new config = new daemon on another port).
241
+
242
+ ---
243
+
244
+ ## 🛠 Development
245
+
246
+ ```bash
247
+ uv sync --group dev # hatchling + ruff/mypy/pytest
248
+ uv run ruff check . # lint (E/F/W/I, line-length 120)
249
+ uv run ruff format . # format
250
+ uv run pytest -q # 30 tests, mocked laya (no model download)
251
+ uv run pytest --cov=src/laya_cli --cov-report=term-missing
252
+ uv run laya-cli --help # every command shows examples
253
+ uv build # hatchling -> dist/*.whl + sdist (src/ layout)
254
+ ```
255
+
256
+ Repo layout: `src/laya_cli/`, `tests/`, `pyproject.toml` (hatchling + `dependency-groups`), `uv.lock`, `.python-version` 3.11, `.github/workflows/ci.yml` + `publish.yml`.
257
+
258
+ ---
259
+
260
+ ## 📄 License
261
+
262
+ Apache-2.0 — same as [Laya](https://github.com/NandhaKishorM/laya) upstream. See [LICENSE](LICENSE).
263
+
264
+ This project is not affiliated with Convai Innovations. Laya weights are Apache-2.0 and hosted on Hugging Face (`convaiinnovations/laya`).
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "laya-cli"
3
- version = "0.2.2"
3
+ version = "0.2.3"
4
4
  description = "Ergonomic CLI for Laya — typed decisions for humans and AI agents (predict, batch, presets, shortlist, router)"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10"
@@ -1,3 +1,3 @@
1
1
  """laya-cli — streaming JSONL classifier over Laya."""
2
2
 
3
- __version__ = "0.2.2"
3
+ __version__ = "0.2.3"
@@ -499,7 +499,7 @@ wheels = [
499
499
 
500
500
  [[package]]
501
501
  name = "laya-cli"
502
- version = "0.2.2"
502
+ version = "0.2.3"
503
503
  source = { editable = "." }
504
504
  dependencies = [
505
505
  { name = "huggingface-hub" },
laya_cli-0.2.2/PKG-INFO DELETED
@@ -1,248 +0,0 @@
1
- Metadata-Version: 2.5
2
- Name: laya-cli
3
- Version: 0.2.2
4
- Summary: Ergonomic CLI for Laya — typed decisions for humans and AI agents (predict, batch, presets, shortlist, router)
5
- Project-URL: Homepage, https://github.com/MIt9/laya-cli
6
- Project-URL: Repository, https://github.com/MIt9/laya-cli.git
7
- Project-URL: Issues, https://github.com/MIt9/laya-cli/issues
8
- Author: MIt9
9
- License: Apache-2.0
10
- License-File: LICENSE
11
- Keywords: ai,cli,decision-model,guardrails,laya,router,triage
12
- Classifier: Development Status :: 4 - Beta
13
- Classifier: Intended Audience :: Developers
14
- Classifier: License :: OSI Approved :: Apache Software License
15
- Classifier: Programming Language :: Python :: 3
16
- Classifier: Programming Language :: Python :: 3.10
17
- Classifier: Programming Language :: Python :: 3.11
18
- Classifier: Programming Language :: Python :: 3.12
19
- Classifier: Programming Language :: Python :: 3.13
20
- Classifier: Programming Language :: Python :: 3.14
21
- Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
22
- Requires-Python: >=3.10
23
- Requires-Dist: huggingface-hub>=0.20.0
24
- Requires-Dist: laya>=0.3.4
25
- Requires-Dist: numpy>=1.20.0
26
- Requires-Dist: safetensors>=0.4.0
27
- Requires-Dist: torch>=2.0.0
28
- Requires-Dist: transformers>=4.48.0
29
- Provides-Extra: dev
30
- Requires-Dist: mypy>=1.10; extra == 'dev'
31
- Requires-Dist: pytest-cov>=4.0; extra == 'dev'
32
- Requires-Dist: pytest>=8.0; extra == 'dev'
33
- Requires-Dist: ruff>=0.9; extra == 'dev'
34
- Description-Content-Type: text/markdown
35
-
36
- # ✨ Laya CLI (`laya-cli`)
37
-
38
- [![License: Apache-2.0](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
39
- [![Python Version](https://img.shields.io/badge/python-3.10%2B-blue.svg)](https://www.python.org/downloads/)
40
- [![Built with uv](https://img.shields.io/badge/built%20with-uv-purple.svg)](https://github.com/astral-sh/uv)
41
-
42
- A modern, high-performance CLI for [Laya](https://github.com/NandhaKishorM/laya) — **typed decisions** (`choice`/`score`/`noul`) in one forward pass, designed for **Humans** (Rich table output) and **AI Agents / Classifiers** (Machine-readable `--json` & JSONL).
43
-
44
- ---
45
-
46
- ## ⚡ Key Features
47
-
48
- * 🤖 **Typed Decisions in One Pass**: `choice` (top label + probs), `score` (ordinal), `noul` (P(true)) — no generation, no hallucination, ~33ms on T4, 7ms/q batched
49
- * 🧭 **Router-Aware Multilingual**: `laya.Router(preload=True)` auto-detects script/language in <0.5ms and dispatches to `laya` (English, 512 ctx) vs `laya-multilingual` (100+ langs, 1024 ctx); warns if `--router` without `--lang`
50
- * 🧹 **Embedding Shortlist for High-Cardinality**: `--shortlist-k 20` via `laya.predict_shortlist` + `embed_fn_from_agent` (Banking77 77 opts → 3 tokens/opt without shortlist → shortlist fixes)
51
- * 🔍 **Preset Library**: `triage` / `email` / `guard` / `moderation` / `router` — direct `laya.*_questions()` passthrough, mergeable with `--questions file.json` and `--questions-inline`
52
- * 📦 **Streaming Batch Mode**: `classify`/`predict --input` loads model **once** + warmup, streams JSONL (`--state-field` verbatim, no silent `(photographer: Name)` injection)
53
- * 🔧 **Post-Filter & Eval**: `filter --where "on_topic>=0.4" --sort -on_topic` and `evaluate` (accuracy, passing @ threshold, precision, escalation rate)
54
- * ⚡ **Resident Daemon (optional)**: `laya-cli serve` keeps model in RAM — `predict`/`classify`/`evaluate` auto-hit `127.0.0.1` and skip 10-35s `laya.load()` on repeated calls (hash → `~/.cache/laya-cli/daemons/<hash>.json`, idle-timeout 1800s)
55
-
56
- ---
57
-
58
- ## 🚀 Global Installation
59
-
60
- ### Option 1: Install globally via `uv` (Recommended)
61
-
62
- ```bash
63
- uv tool install laya-cli
64
- ```
65
-
66
- ### Option 2: Install via `pip` / `pipx`
67
-
68
- ```bash
69
- pipx install laya-cli
70
- # or
71
- pip install laya-cli
72
- ```
73
-
74
- ### Option 3: Run without installing
75
-
76
- ```bash
77
- uvx laya-cli --help
78
- ```
79
-
80
- > Requires Python 3.10+ (`.python-version` pins 3.11). Heavy ML deps (`torch`, `transformers`, `safetensors` via `laya`, ~2 GB) download on first `predict`/`classify`. Afterwards `HF_HUB_OFFLINE=1` works. For `uv` dev, tests mock Laya so CI is fast.
81
-
82
- ---
83
-
84
- ## 🤖 Classifier Pipeline Integration (Pexels → Laya)
85
-
86
- Pipe candidate streams directly from [`pexels-cli`](https://github.com/MIt9/pexels-cli) (`px`) into Laya. This is the original use-case that drove `laya-cli` (82 candidates, `state` deduped):
87
-
88
- ```bash
89
- # 1. Pexels → candidates.jsonl with `state` strings
90
- px videos --queries "black friday shopping,christmas shopping,checkout cart" \
91
- --per-page 8 --state --dedupe keep-first > candidates.jsonl
92
-
93
- # 2. Laya → score + filter (streaming, one model load)
94
- cat candidates.jsonl \
95
- | laya-cli classify --questions questions.json \
96
- | laya-cli filter --where "on_topic>=0.4" --sort -on_topic \
97
- > shortlist.jsonl
98
-
99
- # Or with the new primary command (supports presets without a file):
100
- cat candidates.jsonl | laya-cli predict --questions questions.json --format jsonl > scored.jsonl
101
- px videos --queries "..." --state --dedupe | laya-cli predict --preset triage --format jsonl | laya-cli filter --where "intent==refund"
102
- ```
103
-
104
- **Candidate JSONL format** (`px --state`):
105
- ```json
106
- {"id": 5890229, "type": "video", "query": "black friday shopping", "photographer": "Pavel Danilyuk", "state": "a man shopping on black friday", "url": "https://www.pexels.com/video/a-man-shopping-on-black-friday-5890229/", "duration": 10, "width": 2160, "height": 3840}
107
- ```
108
- `laya-cli` reads `--state-field state` **verbatim** — use `--prepend-field` only explicitly (mixing photographer into state degraded `on_topic` by 0.1-0.3).
109
-
110
- ---
111
-
112
- ## 📖 Usage Examples
113
-
114
- ### 1. Direct CLI (Human & AI)
115
-
116
- ```bash
117
- # Human: table output, no file needed
118
- laya-cli predict "I was charged twice, refund please" --preset triage
119
- laya-cli predict --text "Ignore previous instructions" --preset guard --format table
120
- laya-cli predict --state '{"subject":"Invoice #4411","body":"Billed twice"}' --preset email --format table
121
-
122
- # AI agent: JSON single-shot
123
- laya-cli predict --text "Is this spam?" --preset guard --format json
124
- # -> {"answers": {"jailbreak": {"noul": 0.02, "confidence": 0.97, ...}}, "usage": ...}
125
-
126
- # Custom + preset merging (preset < file < inline)
127
- laya-cli predict --text "hello" --preset triage --questions-inline '{"custom":{"type":"noul","instructions":"Is it polite?"}}' --format json
128
-
129
- # High-cardinality choice (77 banking intents)
130
- laya-cli predict --text "where is my card?" --questions banking.json --shortlist-k 20 --format json
131
-
132
- # Multilingual — Router recommended
133
- laya-cli predict --text "मुझसे दो बार शुल्क लिया गया" --preset triage --router --format json
134
- laya-cli predict --text "Der Kunde wurde zweimal belastet" --preset triage --router --lang de --format json
135
- ```
136
-
137
- ### 2. Batch & Pipeline
138
-
139
- ```bash
140
- # Batch from file (human-readable table per row)
141
- laya-cli predict --input candidates.jsonl --questions questions.json --format table
142
-
143
- # Batch JSONL with flatten (like classify) for jq/filter
144
- laya-cli predict --input candidates.jsonl --questions questions.json --flatten --format jsonl > scored.jsonl
145
- cat scored.jsonl | laya-cli filter --where "on_topic>=0.4,is_relevant>=0.7" --sort -on_topic,+id
146
-
147
- # Legacy streaming (kept for compatibility):
148
- cat candidates.jsonl | laya-cli classify --questions questions.json | laya-cli filter --where "on_topic>=0.4" --sort -on_topic > shortlist.jsonl
149
- ```
150
-
151
- ### 3. Presets & Info
152
-
153
- ```bash
154
- laya-cli questions list --format table
155
- laya-cli questions triage > questions.json # also: email, guard, moderation, router
156
- laya-cli presets guard | laya-cli predict --text "test" --questions /dev/stdin
157
-
158
- laya-cli info # python/torch/cuda/mps + cache
159
- laya-cli evaluate --questions questions.json --labeled labeled.jsonl --label-field label --threshold 0.5
160
- ```
161
-
162
- ### 4. Resident Daemon Mode (optional, speeds up repeated calls)
163
-
164
- **Виміряний факт (сесія 2026-09-22, 82 кандидати, MPS):**
165
-
166
- | етап | час |
167
- |------|-----|
168
- | `px` (мережа, 12 запитів) | 9.3s |
169
- | `laya.load()` (модель у пам'ять) | 10–35s |
170
- | inference, 82 кандидати | ~10s (~120ms/шт) |
171
- | `filter` | 0.08s |
172
-
173
- Для одного пайплайну `px → classify → filter` 35–50s норм. Проблема — коли за сесію кілька разів викликаєш `predict`/`classify`/`evaluate` (підбір `questions.json`: прогнав → подивився → поправив → знову), кожен раз платиш 10–35s за ту саму модель. `laya-cli serve` тримає модель в RAM, наступні виклики летять на `127.0.0.1` (<1ms overhead).
174
-
175
- #### Транспорт (як у `~/.claude/skills/laya-integration/SKILL.md` "Anything else... HTTP sidecar")
176
-
177
- HTTP на loopback `127.0.0.1` (не `0.0.0.0`), один потік з `threading.Lock` (SKILL.md: "one GPU serves one forward pass at a time"):
178
-
179
- - `POST /predict` → `{"state": ..., "questions": {...}, "lang": "...", "shortlist_k": 20}` → те саме що `agent.predict()` (той самий JSON що `laya-cli predict --format json`)
180
- - `GET /status` → `{"model": "...", "subfolder": "...", "device": "...", "loaded_at": 123..., "idle_seconds": 42, "requests_served": 17, "pid": 12345, "port": 8765}`
181
- - `POST /shutdown` → graceful stop (лише з localhost, також `serve stop` шле сигнал за pid-файлом)
182
-
183
- #### Lifecycle
184
-
185
- ```bash
186
- # Старт (без --foreground — форк у фон, пише pid+port в ~/.cache/laya-cli/daemons/<hash>.json)
187
- laya-cli serve --model convaiinnovations/laya --device mps
188
- laya-cli serve --model convaiinnovations/laya --subfolder multilingual --device cpu --idle-timeout 60
189
- laya-cli serve --router --foreground --idle-timeout 0 --port 8765 # форграунд для логів, 0=disabled idle
190
-
191
- # Один daemon = один конфіг (hash(model|subfolder|device|router|lang)), інший конфіг — окремий файл/порт
192
- laya-cli serve status # дефолтний конфіг (як у predict без прапорців)
193
- laya-cli serve status --model convaiinnovations/laya --subfolder multilingual --device cpu
194
- laya-cli serve stop # graceful (POST /shutdown → pid файл видаляється)
195
- laya-cli serve stop --all # всі daemon-и
196
- # Також: curl http://127.0.0.1:<port>/status
197
- ```
198
-
199
- Перед прийомом запитів — прогрів throwaway `predict` (як у `TASK.md`). `idle-timeout 1800s` дефолт, `--idle-timeout 5` для тесту → daemon сам виходить через 5s без запитів і `serve status` каже `not running`.
200
-
201
- #### Клієнт (`predict`/`classify`/`evaluate`)
202
-
203
- Перед `laya.load()` перевіряє `~/.cache/laya-cli/daemons/<hash>.json` для поточного конфігу; якщо файл є і daemon відповідає на `/status` — шле туди, інакше мовчки падає назад на in-process (поведінка `v1` без змін). `--no-daemon` форсує in-process (для відтворюваності/дебагу). Для батчу (`classify`/`predict --input`) — кожен рядок окремий `POST /predict` в циклі (loopback мілісекунди, batch-ендпоінт не потрібен).
204
-
205
- ```bash
206
- # AI workflow (повністю автоматичний):
207
- laya-cli serve --device mps & # один раз на сесію
208
- laya-cli predict "hello" --preset guard --format json # -> via daemon, без 10s
209
- laya-cli predict "hello2" --preset guard --format json # -> знову via daemon
210
- laya-cli serve status # {"loaded_at":..., "requests_served": 2}
211
- laya-cli predict "hello" --preset guard --no-daemon # форс in-process (знову 10s, для ізоляції)
212
- laya-cli serve stop
213
-
214
- # Human tuning loop (типовий):
215
- laya-cli serve & # фон
216
- cat candidates.jsonl | laya-cli classify --questions q.json | laya-cli filter --where "on_topic>=0.4" --sort -on_topic # via daemon
217
- # ...поправив q.json...
218
- cat candidates.jsonl | laya-cli classify --questions q.json | laya-cli filter ... # знову via daemon, без перезавантаження
219
- laya-cli serve stop
220
-
221
- # 5 паралельних predict — не падають, результати не плутаються (серіалізація Lock)
222
- seq 1 5 | xargs -P5 -I{} laya-cli predict "text {}" --preset guard --format json
223
- ```
224
-
225
- **Безпека/межі:** лише `127.0.0.1`, без auth (однокористувацька машина, як у SKILL.md), один потік, stateless крім моделі.
226
-
227
- ---
228
-
229
- ## 🛠 Development
230
-
231
- ```bash
232
- uv sync --group dev # hatchling + ruff/mypy/pytest
233
- uv run ruff check . # lint (E/F/W/I, line-length 120)
234
- uv run ruff format . # format
235
- uv run pytest -q # 26 tests, mocked laya (no model download)
236
- uv run pytest --cov=src/laya_cli --cov-report=term-missing
237
- uv build # hatchling -> dist/*.whl + sdist (src/ layout)
238
- ```
239
-
240
- Repo layout: `src/laya_cli/`, `tests/`, `pyproject.toml` (hatchling + `dependency-groups`), `uv.lock`, `.python-version` 3.11, `.github/workflows/ci.yml` + `publish.yml`.
241
-
242
- ---
243
-
244
- ## 📄 License
245
-
246
- Apache-2.0 — same as [Laya](https://github.com/NandhaKishorM/laya) upstream. See [LICENSE](LICENSE).
247
-
248
- This project is not affiliated with Convai Innovations. Laya weights are Apache-2.0 and hosted on Hugging Face (`convaiinnovations/laya`).
laya_cli-0.2.2/README.md DELETED
@@ -1,213 +0,0 @@
1
- # ✨ Laya CLI (`laya-cli`)
2
-
3
- [![License: Apache-2.0](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
4
- [![Python Version](https://img.shields.io/badge/python-3.10%2B-blue.svg)](https://www.python.org/downloads/)
5
- [![Built with uv](https://img.shields.io/badge/built%20with-uv-purple.svg)](https://github.com/astral-sh/uv)
6
-
7
- A modern, high-performance CLI for [Laya](https://github.com/NandhaKishorM/laya) — **typed decisions** (`choice`/`score`/`noul`) in one forward pass, designed for **Humans** (Rich table output) and **AI Agents / Classifiers** (Machine-readable `--json` & JSONL).
8
-
9
- ---
10
-
11
- ## ⚡ Key Features
12
-
13
- * 🤖 **Typed Decisions in One Pass**: `choice` (top label + probs), `score` (ordinal), `noul` (P(true)) — no generation, no hallucination, ~33ms on T4, 7ms/q batched
14
- * 🧭 **Router-Aware Multilingual**: `laya.Router(preload=True)` auto-detects script/language in <0.5ms and dispatches to `laya` (English, 512 ctx) vs `laya-multilingual` (100+ langs, 1024 ctx); warns if `--router` without `--lang`
15
- * 🧹 **Embedding Shortlist for High-Cardinality**: `--shortlist-k 20` via `laya.predict_shortlist` + `embed_fn_from_agent` (Banking77 77 opts → 3 tokens/opt without shortlist → shortlist fixes)
16
- * 🔍 **Preset Library**: `triage` / `email` / `guard` / `moderation` / `router` — direct `laya.*_questions()` passthrough, mergeable with `--questions file.json` and `--questions-inline`
17
- * 📦 **Streaming Batch Mode**: `classify`/`predict --input` loads model **once** + warmup, streams JSONL (`--state-field` verbatim, no silent `(photographer: Name)` injection)
18
- * 🔧 **Post-Filter & Eval**: `filter --where "on_topic>=0.4" --sort -on_topic` and `evaluate` (accuracy, passing @ threshold, precision, escalation rate)
19
- * ⚡ **Resident Daemon (optional)**: `laya-cli serve` keeps model in RAM — `predict`/`classify`/`evaluate` auto-hit `127.0.0.1` and skip 10-35s `laya.load()` on repeated calls (hash → `~/.cache/laya-cli/daemons/<hash>.json`, idle-timeout 1800s)
20
-
21
- ---
22
-
23
- ## 🚀 Global Installation
24
-
25
- ### Option 1: Install globally via `uv` (Recommended)
26
-
27
- ```bash
28
- uv tool install laya-cli
29
- ```
30
-
31
- ### Option 2: Install via `pip` / `pipx`
32
-
33
- ```bash
34
- pipx install laya-cli
35
- # or
36
- pip install laya-cli
37
- ```
38
-
39
- ### Option 3: Run without installing
40
-
41
- ```bash
42
- uvx laya-cli --help
43
- ```
44
-
45
- > Requires Python 3.10+ (`.python-version` pins 3.11). Heavy ML deps (`torch`, `transformers`, `safetensors` via `laya`, ~2 GB) download on first `predict`/`classify`. Afterwards `HF_HUB_OFFLINE=1` works. For `uv` dev, tests mock Laya so CI is fast.
46
-
47
- ---
48
-
49
- ## 🤖 Classifier Pipeline Integration (Pexels → Laya)
50
-
51
- Pipe candidate streams directly from [`pexels-cli`](https://github.com/MIt9/pexels-cli) (`px`) into Laya. This is the original use-case that drove `laya-cli` (82 candidates, `state` deduped):
52
-
53
- ```bash
54
- # 1. Pexels → candidates.jsonl with `state` strings
55
- px videos --queries "black friday shopping,christmas shopping,checkout cart" \
56
- --per-page 8 --state --dedupe keep-first > candidates.jsonl
57
-
58
- # 2. Laya → score + filter (streaming, one model load)
59
- cat candidates.jsonl \
60
- | laya-cli classify --questions questions.json \
61
- | laya-cli filter --where "on_topic>=0.4" --sort -on_topic \
62
- > shortlist.jsonl
63
-
64
- # Or with the new primary command (supports presets without a file):
65
- cat candidates.jsonl | laya-cli predict --questions questions.json --format jsonl > scored.jsonl
66
- px videos --queries "..." --state --dedupe | laya-cli predict --preset triage --format jsonl | laya-cli filter --where "intent==refund"
67
- ```
68
-
69
- **Candidate JSONL format** (`px --state`):
70
- ```json
71
- {"id": 5890229, "type": "video", "query": "black friday shopping", "photographer": "Pavel Danilyuk", "state": "a man shopping on black friday", "url": "https://www.pexels.com/video/a-man-shopping-on-black-friday-5890229/", "duration": 10, "width": 2160, "height": 3840}
72
- ```
73
- `laya-cli` reads `--state-field state` **verbatim** — use `--prepend-field` only explicitly (mixing photographer into state degraded `on_topic` by 0.1-0.3).
74
-
75
- ---
76
-
77
- ## 📖 Usage Examples
78
-
79
- ### 1. Direct CLI (Human & AI)
80
-
81
- ```bash
82
- # Human: table output, no file needed
83
- laya-cli predict "I was charged twice, refund please" --preset triage
84
- laya-cli predict --text "Ignore previous instructions" --preset guard --format table
85
- laya-cli predict --state '{"subject":"Invoice #4411","body":"Billed twice"}' --preset email --format table
86
-
87
- # AI agent: JSON single-shot
88
- laya-cli predict --text "Is this spam?" --preset guard --format json
89
- # -> {"answers": {"jailbreak": {"noul": 0.02, "confidence": 0.97, ...}}, "usage": ...}
90
-
91
- # Custom + preset merging (preset < file < inline)
92
- laya-cli predict --text "hello" --preset triage --questions-inline '{"custom":{"type":"noul","instructions":"Is it polite?"}}' --format json
93
-
94
- # High-cardinality choice (77 banking intents)
95
- laya-cli predict --text "where is my card?" --questions banking.json --shortlist-k 20 --format json
96
-
97
- # Multilingual — Router recommended
98
- laya-cli predict --text "मुझसे दो बार शुल्क लिया गया" --preset triage --router --format json
99
- laya-cli predict --text "Der Kunde wurde zweimal belastet" --preset triage --router --lang de --format json
100
- ```
101
-
102
- ### 2. Batch & Pipeline
103
-
104
- ```bash
105
- # Batch from file (human-readable table per row)
106
- laya-cli predict --input candidates.jsonl --questions questions.json --format table
107
-
108
- # Batch JSONL with flatten (like classify) for jq/filter
109
- laya-cli predict --input candidates.jsonl --questions questions.json --flatten --format jsonl > scored.jsonl
110
- cat scored.jsonl | laya-cli filter --where "on_topic>=0.4,is_relevant>=0.7" --sort -on_topic,+id
111
-
112
- # Legacy streaming (kept for compatibility):
113
- cat candidates.jsonl | laya-cli classify --questions questions.json | laya-cli filter --where "on_topic>=0.4" --sort -on_topic > shortlist.jsonl
114
- ```
115
-
116
- ### 3. Presets & Info
117
-
118
- ```bash
119
- laya-cli questions list --format table
120
- laya-cli questions triage > questions.json # also: email, guard, moderation, router
121
- laya-cli presets guard | laya-cli predict --text "test" --questions /dev/stdin
122
-
123
- laya-cli info # python/torch/cuda/mps + cache
124
- laya-cli evaluate --questions questions.json --labeled labeled.jsonl --label-field label --threshold 0.5
125
- ```
126
-
127
- ### 4. Resident Daemon Mode (optional, speeds up repeated calls)
128
-
129
- **Виміряний факт (сесія 2026-09-22, 82 кандидати, MPS):**
130
-
131
- | етап | час |
132
- |------|-----|
133
- | `px` (мережа, 12 запитів) | 9.3s |
134
- | `laya.load()` (модель у пам'ять) | 10–35s |
135
- | inference, 82 кандидати | ~10s (~120ms/шт) |
136
- | `filter` | 0.08s |
137
-
138
- Для одного пайплайну `px → classify → filter` 35–50s норм. Проблема — коли за сесію кілька разів викликаєш `predict`/`classify`/`evaluate` (підбір `questions.json`: прогнав → подивився → поправив → знову), кожен раз платиш 10–35s за ту саму модель. `laya-cli serve` тримає модель в RAM, наступні виклики летять на `127.0.0.1` (<1ms overhead).
139
-
140
- #### Транспорт (як у `~/.claude/skills/laya-integration/SKILL.md` "Anything else... HTTP sidecar")
141
-
142
- HTTP на loopback `127.0.0.1` (не `0.0.0.0`), один потік з `threading.Lock` (SKILL.md: "one GPU serves one forward pass at a time"):
143
-
144
- - `POST /predict` → `{"state": ..., "questions": {...}, "lang": "...", "shortlist_k": 20}` → те саме що `agent.predict()` (той самий JSON що `laya-cli predict --format json`)
145
- - `GET /status` → `{"model": "...", "subfolder": "...", "device": "...", "loaded_at": 123..., "idle_seconds": 42, "requests_served": 17, "pid": 12345, "port": 8765}`
146
- - `POST /shutdown` → graceful stop (лише з localhost, також `serve stop` шле сигнал за pid-файлом)
147
-
148
- #### Lifecycle
149
-
150
- ```bash
151
- # Старт (без --foreground — форк у фон, пише pid+port в ~/.cache/laya-cli/daemons/<hash>.json)
152
- laya-cli serve --model convaiinnovations/laya --device mps
153
- laya-cli serve --model convaiinnovations/laya --subfolder multilingual --device cpu --idle-timeout 60
154
- laya-cli serve --router --foreground --idle-timeout 0 --port 8765 # форграунд для логів, 0=disabled idle
155
-
156
- # Один daemon = один конфіг (hash(model|subfolder|device|router|lang)), інший конфіг — окремий файл/порт
157
- laya-cli serve status # дефолтний конфіг (як у predict без прапорців)
158
- laya-cli serve status --model convaiinnovations/laya --subfolder multilingual --device cpu
159
- laya-cli serve stop # graceful (POST /shutdown → pid файл видаляється)
160
- laya-cli serve stop --all # всі daemon-и
161
- # Також: curl http://127.0.0.1:<port>/status
162
- ```
163
-
164
- Перед прийомом запитів — прогрів throwaway `predict` (як у `TASK.md`). `idle-timeout 1800s` дефолт, `--idle-timeout 5` для тесту → daemon сам виходить через 5s без запитів і `serve status` каже `not running`.
165
-
166
- #### Клієнт (`predict`/`classify`/`evaluate`)
167
-
168
- Перед `laya.load()` перевіряє `~/.cache/laya-cli/daemons/<hash>.json` для поточного конфігу; якщо файл є і daemon відповідає на `/status` — шле туди, інакше мовчки падає назад на in-process (поведінка `v1` без змін). `--no-daemon` форсує in-process (для відтворюваності/дебагу). Для батчу (`classify`/`predict --input`) — кожен рядок окремий `POST /predict` в циклі (loopback мілісекунди, batch-ендпоінт не потрібен).
169
-
170
- ```bash
171
- # AI workflow (повністю автоматичний):
172
- laya-cli serve --device mps & # один раз на сесію
173
- laya-cli predict "hello" --preset guard --format json # -> via daemon, без 10s
174
- laya-cli predict "hello2" --preset guard --format json # -> знову via daemon
175
- laya-cli serve status # {"loaded_at":..., "requests_served": 2}
176
- laya-cli predict "hello" --preset guard --no-daemon # форс in-process (знову 10s, для ізоляції)
177
- laya-cli serve stop
178
-
179
- # Human tuning loop (типовий):
180
- laya-cli serve & # фон
181
- cat candidates.jsonl | laya-cli classify --questions q.json | laya-cli filter --where "on_topic>=0.4" --sort -on_topic # via daemon
182
- # ...поправив q.json...
183
- cat candidates.jsonl | laya-cli classify --questions q.json | laya-cli filter ... # знову via daemon, без перезавантаження
184
- laya-cli serve stop
185
-
186
- # 5 паралельних predict — не падають, результати не плутаються (серіалізація Lock)
187
- seq 1 5 | xargs -P5 -I{} laya-cli predict "text {}" --preset guard --format json
188
- ```
189
-
190
- **Безпека/межі:** лише `127.0.0.1`, без auth (однокористувацька машина, як у SKILL.md), один потік, stateless крім моделі.
191
-
192
- ---
193
-
194
- ## 🛠 Development
195
-
196
- ```bash
197
- uv sync --group dev # hatchling + ruff/mypy/pytest
198
- uv run ruff check . # lint (E/F/W/I, line-length 120)
199
- uv run ruff format . # format
200
- uv run pytest -q # 26 tests, mocked laya (no model download)
201
- uv run pytest --cov=src/laya_cli --cov-report=term-missing
202
- uv build # hatchling -> dist/*.whl + sdist (src/ layout)
203
- ```
204
-
205
- Repo layout: `src/laya_cli/`, `tests/`, `pyproject.toml` (hatchling + `dependency-groups`), `uv.lock`, `.python-version` 3.11, `.github/workflows/ci.yml` + `publish.yml`.
206
-
207
- ---
208
-
209
- ## 📄 License
210
-
211
- Apache-2.0 — same as [Laya](https://github.com/NandhaKishorM/laya) upstream. See [LICENSE](LICENSE).
212
-
213
- This project is not affiliated with Convai Innovations. Laya weights are Apache-2.0 and hosted on Hugging Face (`convaiinnovations/laya`).
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes