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.
- laya_cli-0.2.3/PKG-INFO +299 -0
- laya_cli-0.2.3/README.md +264 -0
- {laya_cli-0.2.2 → laya_cli-0.2.3}/pyproject.toml +1 -1
- {laya_cli-0.2.2 → laya_cli-0.2.3}/src/laya_cli/__init__.py +1 -1
- {laya_cli-0.2.2 → laya_cli-0.2.3}/uv.lock +1 -1
- laya_cli-0.2.2/PKG-INFO +0 -248
- laya_cli-0.2.2/README.md +0 -213
- {laya_cli-0.2.2 → laya_cli-0.2.3}/.github/workflows/ci.yml +0 -0
- {laya_cli-0.2.2 → laya_cli-0.2.3}/.github/workflows/publish.yml +0 -0
- {laya_cli-0.2.2 → laya_cli-0.2.3}/.gitignore +0 -0
- {laya_cli-0.2.2 → laya_cli-0.2.3}/.python-version +0 -0
- {laya_cli-0.2.2 → laya_cli-0.2.3}/LICENSE +0 -0
- {laya_cli-0.2.2 → laya_cli-0.2.3}/TASK.md +0 -0
- {laya_cli-0.2.2 → laya_cli-0.2.3}/src/laya_cli/cli.py +0 -0
- {laya_cli-0.2.2 → laya_cli-0.2.3}/src/laya_cli/daemon.py +0 -0
- {laya_cli-0.2.2 → laya_cli-0.2.3}/tests/__init__.py +0 -0
- {laya_cli-0.2.2 → laya_cli-0.2.3}/tests/conftest.py +0 -0
- {laya_cli-0.2.2 → laya_cli-0.2.3}/tests/test_classify.py +0 -0
- {laya_cli-0.2.2 → laya_cli-0.2.3}/tests/test_cli.py +0 -0
- {laya_cli-0.2.2 → laya_cli-0.2.3}/tests/test_daemon.py +0 -0
- {laya_cli-0.2.2 → laya_cli-0.2.3}/tests/test_evaluate.py +0 -0
- {laya_cli-0.2.2 → laya_cli-0.2.3}/tests/test_filter.py +0 -0
- {laya_cli-0.2.2 → laya_cli-0.2.3}/tests/test_predict.py +0 -0
- {laya_cli-0.2.2 → laya_cli-0.2.3}/tests/test_questions.py +0 -0
laya_cli-0.2.3/PKG-INFO
ADDED
|
@@ -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
|
+
[](https://opensource.org/licenses/Apache-2.0)
|
|
39
|
+
[](https://www.python.org/downloads/)
|
|
40
|
+
[](https://github.com/astral-sh/uv)
|
|
41
|
+
[](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`).
|
laya_cli-0.2.3/README.md
ADDED
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
# ✨ Laya CLI (`laya-cli`)
|
|
2
|
+
|
|
3
|
+
[](https://opensource.org/licenses/Apache-2.0)
|
|
4
|
+
[](https://www.python.org/downloads/)
|
|
5
|
+
[](https://github.com/astral-sh/uv)
|
|
6
|
+
[](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`).
|
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
|
-
[](https://opensource.org/licenses/Apache-2.0)
|
|
39
|
-
[](https://www.python.org/downloads/)
|
|
40
|
-
[](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
|
-
[](https://opensource.org/licenses/Apache-2.0)
|
|
4
|
-
[](https://www.python.org/downloads/)
|
|
5
|
-
[](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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|