laya-cli 0.1.0__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,41 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ jobs:
9
+ test:
10
+ runs-on: ubuntu-latest
11
+ strategy:
12
+ matrix:
13
+ python-version: ["3.11", "3.12"]
14
+ steps:
15
+ - uses: actions/checkout@v4
16
+ - uses: astral-sh/setup-uv@v5
17
+ with:
18
+ enable-cache: true
19
+ python-version: ${{ matrix.python-version }}
20
+ - name: Install (no heavy ML deps for mocked tests)
21
+ run: |
22
+ uv sync --group test
23
+ # For mocked tests we don't need the real torch/laya weights;
24
+ # uv sync already installed deps, but tests mock laya.
25
+ - name: Lint
26
+ run: uv run ruff check .
27
+ - name: Format
28
+ run: uv run ruff format --check .
29
+ - name: Tests (mocked, no model download)
30
+ run: uv run pytest --cov --cov-report=term-missing
31
+
32
+ test-real-model:
33
+ runs-on: ubuntu-latest
34
+ if: contains(github.event.head_commit.message, '[real-model]')
35
+ steps:
36
+ - uses: actions/checkout@v4
37
+ - uses: astral-sh/setup-uv@v5
38
+ with:
39
+ python-version: "3.11"
40
+ - run: uv sync
41
+ - run: uv run pytest -m slow --cov --cov-report=term-missing
@@ -0,0 +1,30 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+ workflow_dispatch:
7
+
8
+ jobs:
9
+ pypi-publish:
10
+ name: Build and publish to PyPI
11
+ runs-on: ubuntu-latest
12
+ permissions:
13
+ id-token: write
14
+ steps:
15
+ - name: Checkout code
16
+ uses: actions/checkout@v4
17
+
18
+ - name: Install uv
19
+ uses: astral-sh/setup-uv@v5
20
+ with:
21
+ enable-cache: true
22
+
23
+ - name: Build package
24
+ run: uv build
25
+
26
+ - name: Publish package to PyPI
27
+ run: uv publish
28
+ env:
29
+ UV_PUBLISH_USERNAME: __token__
30
+ UV_PUBLISH_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
@@ -0,0 +1,47 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ *.so
6
+ *.egg-info/
7
+ dist/
8
+ build/
9
+ *.egg
10
+ .eggs/
11
+ *.whl
12
+
13
+ # Virtual envs
14
+ .venv/
15
+ venv/
16
+ env/
17
+
18
+ # Testing & coverage
19
+ .pytest_cache/
20
+ .coverage
21
+ .coverage.*
22
+ htmlcov/
23
+ coverage.xml
24
+ *.cover
25
+ .hypothesis/
26
+
27
+ # Mypy, Ruff
28
+ .mypy_cache/
29
+ .ruff_cache/
30
+
31
+ # IDE
32
+ .idea/
33
+ .vscode/
34
+ *.swp
35
+ *.swo
36
+ .DS_Store
37
+ Thumbs.db
38
+
39
+ # HF cache / models (do not commit weights)
40
+ models/
41
+ *.safetensors
42
+ *.bin
43
+
44
+ # Local
45
+ .env
46
+ *.log
47
+ tmp/
@@ -0,0 +1 @@
1
+ 3.11
laya_cli-0.1.0/LICENSE ADDED
@@ -0,0 +1,17 @@
1
+ Apache License 2.0
2
+
3
+ Copyright (c) 2026 laya-cli contributors
4
+
5
+ Licensed under the Apache License, Version 2.0 (the "License");
6
+ you may not use this file except in compliance with the License.
7
+ You may obtain a copy of the License at
8
+
9
+ http://www.apache.org/licenses/LICENSE-2.0
10
+
11
+ Unless required by applicable law or agreed to in writing, software
12
+ distributed under the License is distributed on an "AS IS" BASIS,
13
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ See the License for the specific language governing permissions and
15
+ limitations under the License.
16
+
17
+ Upstream Laya is Apache-2.0 by Convai Innovations (https://github.com/NandhaKishorM/laya).
@@ -0,0 +1,182 @@
1
+ Metadata-Version: 2.5
2
+ Name: laya-cli
3
+ Version: 0.1.0
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
+
55
+ ---
56
+
57
+ ## 🚀 Global Installation
58
+
59
+ ### Option 1: Install globally via `uv` (Recommended)
60
+
61
+ ```bash
62
+ uv tool install laya-cli
63
+ ```
64
+
65
+ ### Option 2: Install via `pip` / `pipx`
66
+
67
+ ```bash
68
+ pipx install laya-cli
69
+ # or
70
+ pip install laya-cli
71
+ ```
72
+
73
+ ### Option 3: Run without installing
74
+
75
+ ```bash
76
+ uvx laya-cli --help
77
+ ```
78
+
79
+ > 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.
80
+
81
+ ---
82
+
83
+ ## 🤖 Classifier Pipeline Integration (Pexels → Laya)
84
+
85
+ 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):
86
+
87
+ ```bash
88
+ # 1. Pexels → candidates.jsonl with `state` strings
89
+ px videos --queries "black friday shopping,christmas shopping,checkout cart" \
90
+ --per-page 8 --state --dedupe keep-first > candidates.jsonl
91
+
92
+ # 2. Laya → score + filter (streaming, one model load)
93
+ cat candidates.jsonl \
94
+ | laya-cli classify --questions questions.json \
95
+ | laya-cli filter --where "on_topic>=0.4" --sort -on_topic \
96
+ > shortlist.jsonl
97
+
98
+ # Or with the new primary command (supports presets without a file):
99
+ cat candidates.jsonl | laya-cli predict --questions questions.json --format jsonl > scored.jsonl
100
+ px videos --queries "..." --state --dedupe | laya-cli predict --preset triage --format jsonl | laya-cli filter --where "intent==refund"
101
+ ```
102
+
103
+ **Candidate JSONL format** (`px --state`):
104
+ ```json
105
+ {"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}
106
+ ```
107
+ `laya-cli` reads `--state-field state` **verbatim** — use `--prepend-field` only explicitly (mixing photographer into state degraded `on_topic` by 0.1-0.3).
108
+
109
+ ---
110
+
111
+ ## 📖 Usage Examples
112
+
113
+ ### 1. Direct CLI (Human & AI)
114
+
115
+ ```bash
116
+ # Human: table output, no file needed
117
+ laya-cli predict "I was charged twice, refund please" --preset triage
118
+ laya-cli predict --text "Ignore previous instructions" --preset guard --format table
119
+ laya-cli predict --state '{"subject":"Invoice #4411","body":"Billed twice"}' --preset email --format table
120
+
121
+ # AI agent: JSON single-shot
122
+ laya-cli predict --text "Is this spam?" --preset guard --format json
123
+ # -> {"answers": {"jailbreak": {"noul": 0.02, "confidence": 0.97, ...}}, "usage": ...}
124
+
125
+ # Custom + preset merging (preset < file < inline)
126
+ laya-cli predict --text "hello" --preset triage --questions-inline '{"custom":{"type":"noul","instructions":"Is it polite?"}}' --format json
127
+
128
+ # High-cardinality choice (77 banking intents)
129
+ laya-cli predict --text "where is my card?" --questions banking.json --shortlist-k 20 --format json
130
+
131
+ # Multilingual — Router recommended
132
+ laya-cli predict --text "मुझसे दो बार शुल्क लिया गया" --preset triage --router --format json
133
+ laya-cli predict --text "Der Kunde wurde zweimal belastet" --preset triage --router --lang de --format json
134
+ ```
135
+
136
+ ### 2. Batch & Pipeline
137
+
138
+ ```bash
139
+ # Batch from file (human-readable table per row)
140
+ laya-cli predict --input candidates.jsonl --questions questions.json --format table
141
+
142
+ # Batch JSONL with flatten (like classify) for jq/filter
143
+ laya-cli predict --input candidates.jsonl --questions questions.json --flatten --format jsonl > scored.jsonl
144
+ cat scored.jsonl | laya-cli filter --where "on_topic>=0.4,is_relevant>=0.7" --sort -on_topic,+id
145
+
146
+ # Legacy streaming (kept for compatibility):
147
+ cat candidates.jsonl | laya-cli classify --questions questions.json | laya-cli filter --where "on_topic>=0.4" --sort -on_topic > shortlist.jsonl
148
+ ```
149
+
150
+ ### 3. Presets & Info
151
+
152
+ ```bash
153
+ laya-cli questions list --format table
154
+ laya-cli questions triage > questions.json # also: email, guard, moderation, router
155
+ laya-cli presets guard | laya-cli predict --text "test" --questions /dev/stdin
156
+
157
+ laya-cli info # python/torch/cuda/mps + cache
158
+ laya-cli evaluate --questions questions.json --labeled labeled.jsonl --label-field label --threshold 0.5
159
+ ```
160
+
161
+ ---
162
+
163
+ ## 🛠 Development
164
+
165
+ ```bash
166
+ uv sync --group dev # hatchling + ruff/mypy/pytest
167
+ uv run ruff check . # lint (E/F/W/I, line-length 120)
168
+ uv run ruff format . # format
169
+ uv run pytest -q # 26 tests, mocked laya (no model download)
170
+ uv run pytest --cov=src/laya_cli --cov-report=term-missing
171
+ uv build # hatchling -> dist/*.whl + sdist (src/ layout)
172
+ ```
173
+
174
+ Repo layout: `src/laya_cli/`, `tests/`, `pyproject.toml` (hatchling + `dependency-groups`), `uv.lock`, `.python-version` 3.11, `.github/workflows/ci.yml` + `publish.yml`.
175
+
176
+ ---
177
+
178
+ ## 📄 License
179
+
180
+ Apache-2.0 — same as [Laya](https://github.com/NandhaKishorM/laya) upstream. See [LICENSE](LICENSE).
181
+
182
+ This project is not affiliated with Convai Innovations. Laya weights are Apache-2.0 and hosted on Hugging Face (`convaiinnovations/laya`).
@@ -0,0 +1,147 @@
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
+
20
+ ---
21
+
22
+ ## 🚀 Global Installation
23
+
24
+ ### Option 1: Install globally via `uv` (Recommended)
25
+
26
+ ```bash
27
+ uv tool install laya-cli
28
+ ```
29
+
30
+ ### Option 2: Install via `pip` / `pipx`
31
+
32
+ ```bash
33
+ pipx install laya-cli
34
+ # or
35
+ pip install laya-cli
36
+ ```
37
+
38
+ ### Option 3: Run without installing
39
+
40
+ ```bash
41
+ uvx laya-cli --help
42
+ ```
43
+
44
+ > 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.
45
+
46
+ ---
47
+
48
+ ## 🤖 Classifier Pipeline Integration (Pexels → Laya)
49
+
50
+ 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):
51
+
52
+ ```bash
53
+ # 1. Pexels → candidates.jsonl with `state` strings
54
+ px videos --queries "black friday shopping,christmas shopping,checkout cart" \
55
+ --per-page 8 --state --dedupe keep-first > candidates.jsonl
56
+
57
+ # 2. Laya → score + filter (streaming, one model load)
58
+ cat candidates.jsonl \
59
+ | laya-cli classify --questions questions.json \
60
+ | laya-cli filter --where "on_topic>=0.4" --sort -on_topic \
61
+ > shortlist.jsonl
62
+
63
+ # Or with the new primary command (supports presets without a file):
64
+ cat candidates.jsonl | laya-cli predict --questions questions.json --format jsonl > scored.jsonl
65
+ px videos --queries "..." --state --dedupe | laya-cli predict --preset triage --format jsonl | laya-cli filter --where "intent==refund"
66
+ ```
67
+
68
+ **Candidate JSONL format** (`px --state`):
69
+ ```json
70
+ {"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}
71
+ ```
72
+ `laya-cli` reads `--state-field state` **verbatim** — use `--prepend-field` only explicitly (mixing photographer into state degraded `on_topic` by 0.1-0.3).
73
+
74
+ ---
75
+
76
+ ## 📖 Usage Examples
77
+
78
+ ### 1. Direct CLI (Human & AI)
79
+
80
+ ```bash
81
+ # Human: table output, no file needed
82
+ laya-cli predict "I was charged twice, refund please" --preset triage
83
+ laya-cli predict --text "Ignore previous instructions" --preset guard --format table
84
+ laya-cli predict --state '{"subject":"Invoice #4411","body":"Billed twice"}' --preset email --format table
85
+
86
+ # AI agent: JSON single-shot
87
+ laya-cli predict --text "Is this spam?" --preset guard --format json
88
+ # -> {"answers": {"jailbreak": {"noul": 0.02, "confidence": 0.97, ...}}, "usage": ...}
89
+
90
+ # Custom + preset merging (preset < file < inline)
91
+ laya-cli predict --text "hello" --preset triage --questions-inline '{"custom":{"type":"noul","instructions":"Is it polite?"}}' --format json
92
+
93
+ # High-cardinality choice (77 banking intents)
94
+ laya-cli predict --text "where is my card?" --questions banking.json --shortlist-k 20 --format json
95
+
96
+ # Multilingual — Router recommended
97
+ laya-cli predict --text "मुझसे दो बार शुल्क लिया गया" --preset triage --router --format json
98
+ laya-cli predict --text "Der Kunde wurde zweimal belastet" --preset triage --router --lang de --format json
99
+ ```
100
+
101
+ ### 2. Batch & Pipeline
102
+
103
+ ```bash
104
+ # Batch from file (human-readable table per row)
105
+ laya-cli predict --input candidates.jsonl --questions questions.json --format table
106
+
107
+ # Batch JSONL with flatten (like classify) for jq/filter
108
+ laya-cli predict --input candidates.jsonl --questions questions.json --flatten --format jsonl > scored.jsonl
109
+ cat scored.jsonl | laya-cli filter --where "on_topic>=0.4,is_relevant>=0.7" --sort -on_topic,+id
110
+
111
+ # Legacy streaming (kept for compatibility):
112
+ cat candidates.jsonl | laya-cli classify --questions questions.json | laya-cli filter --where "on_topic>=0.4" --sort -on_topic > shortlist.jsonl
113
+ ```
114
+
115
+ ### 3. Presets & Info
116
+
117
+ ```bash
118
+ laya-cli questions list --format table
119
+ laya-cli questions triage > questions.json # also: email, guard, moderation, router
120
+ laya-cli presets guard | laya-cli predict --text "test" --questions /dev/stdin
121
+
122
+ laya-cli info # python/torch/cuda/mps + cache
123
+ laya-cli evaluate --questions questions.json --labeled labeled.jsonl --label-field label --threshold 0.5
124
+ ```
125
+
126
+ ---
127
+
128
+ ## 🛠 Development
129
+
130
+ ```bash
131
+ uv sync --group dev # hatchling + ruff/mypy/pytest
132
+ uv run ruff check . # lint (E/F/W/I, line-length 120)
133
+ uv run ruff format . # format
134
+ uv run pytest -q # 26 tests, mocked laya (no model download)
135
+ uv run pytest --cov=src/laya_cli --cov-report=term-missing
136
+ uv build # hatchling -> dist/*.whl + sdist (src/ layout)
137
+ ```
138
+
139
+ Repo layout: `src/laya_cli/`, `tests/`, `pyproject.toml` (hatchling + `dependency-groups`), `uv.lock`, `.python-version` 3.11, `.github/workflows/ci.yml` + `publish.yml`.
140
+
141
+ ---
142
+
143
+ ## 📄 License
144
+
145
+ Apache-2.0 — same as [Laya](https://github.com/NandhaKishorM/laya) upstream. See [LICENSE](LICENSE).
146
+
147
+ This project is not affiliated with Convai Innovations. Laya weights are Apache-2.0 and hosted on Hugging Face (`convaiinnovations/laya`).
laya_cli-0.1.0/TASK.md ADDED
@@ -0,0 +1,128 @@
1
+ # laya-cli — задача на реалізацію
2
+
3
+ ## Навіщо
4
+
5
+ Зараз щоразу, коли треба класифікувати список кандидатів (текстів/футажу/тікетів) через
6
+ [Laya](https://github.com/NandhaKishorM/laya) (`pip install laya`, non-generative typed-decision
7
+ модель), пишеться одноразовий python-скрипт: завантажити модель, прогнати цикл `agent.predict()`,
8
+ зібрати результат, відсортувати/відфільтрувати, відформатувати звіт. Реальний кейс, з якого
9
+ виросла ця задача: відбір стокового відео (Pexels, через `px` CLI) під відео-епізод — 82
10
+ кандидати, кожен з коротким текстовим описом (`state`), треба відфільтрувати релевантні.
11
+
12
+ Мета — винести цю повторювану частину в окремий CLI (`laya-cli`), який приймає JSONL на stdin
13
+ (кожен рядок — кандидат з текстовим полем), питання в JSON-файлі, і віддає JSONL з доданими
14
+ відповідями на stdout. Стрімовий Unix-style інструмент, що ставиться в пайплайн з будь-чим,
15
+ що вміє JSONL (наприклад `px videos --state`).
16
+
17
+ ```bash
18
+ px videos --queries "..." --state --dedupe keep-first \
19
+ | laya-cli classify --questions questions.json \
20
+ | laya-cli filter --where "on_topic>=0.4" --sort -on_topic \
21
+ > scored.jsonl
22
+ ```
23
+
24
+ Референс по тому, як правильно юзати Laya (питання, чекпоінти, калібрація, пастки) —
25
+ `~/.claude/skills/laya-integration/SKILL.md` на цій машині. Реалізація мусить слідувати
26
+ принципам звідти, не вигадувати заново.
27
+
28
+ ## Технічна база
29
+
30
+ - Python, `pip install laya` як залежність (тягне torch/transformers — важке, тому окремий
31
+ проєкт, а не додаток до легких CLI типу px).
32
+ - `laya.load("convaiinnovations/laya", subfolder=...)` для конкретного чекпоінта,
33
+ `laya.Router(preload=True)` коли треба авто-вибір мови/чекпоінта.
34
+ - Модель вантажиться **один раз** на весь запуск CLI, не на кожен рядок вводу. Перший виклик
35
+ після завантаження — прогрів (throwaway `predict`) перед основним циклом, це у skill описано
36
+ explicitно ("Warm up").
37
+
38
+ ## Команди
39
+
40
+ ### `laya-cli classify --questions <file.json> [--state-field state] [--model ...] [--lang ...] [--device ...]`
41
+
42
+ - Читає JSONL з stdin, рядок за рядком.
43
+ - Для кожного рядка бере текст з поля `--state-field` (дефолт `state`) **як є, без жодних
44
+ домішок** — не конкатенувати нічого свого до цього тексту. Урок з реального кейсу: коли
45
+ джерело (px) саме собі домішало `(photographer: Ім'я)` в текст, on_topic-скор Laya падав
46
+ на 0.1-0.3 на тих самих кадрах (виміряно). Цей інструмент не повторює ту помилку і не додає
47
+ нічого до `state` мовчки. Якщо хтось хоче domішати контекст — окремий явний флаг
48
+ `--prepend-field <name>` (не дефолт, задокументувати ризик у `--help`).
49
+ - `--questions file.json` — той самий словник, що приймає `agent.predict(state, questions)`
50
+ (типи `choice`/`score`/`noul`, кожне питання — `type`, `instructions`, `criteria`). Формат
51
+ identичний до того, що описано в SKILL.md, ніякого власного DSL зверху.
52
+ - Виводить у stdout той самий вхідний об'єкт + додані поля з відповідей: для `choice` —
53
+ `{question_id}` = обране значення, `{question_id}_p` = ймовірність обраного,
54
+ `{question_id}_probs` = весь розподіл (опційно, за `--full-probs`, дефолт вимкнено щоб не
55
+ роздувати рядки); для `score` — `{question_id}_score`; для `noul` — `{question_id}` = P(true).
56
+ `{question_id}_confidence` завжди додається.
57
+ - `--model` — hf repo id, дефолт `convaiinnovations/laya` (English checkpoint).
58
+ - `--subfolder` — `multilingual` / `typed-decisions` / порожньо (bundle).
59
+ - `--lang` — примусова мова для `laya.Router` (якщо заданий `--router`); без цього флагу
60
+ Router сам детектить мову, а на мовах поза {en,fr,de,es,pt,it,nl} мовчки лягає на English
61
+ checkpoint — тому CLI мусить **попереджати в stderr**, коли `--router` активний і `--lang`
62
+ не заданий, з посиланням на це обмеження (SKILL.md розділ "Router").
63
+ - `--device` — `cpu`/`mps`/`cuda`, прокидається напряму в `laya.load(device=...)`.
64
+ - Concurrency: сам CLI однопотоковий по дизайну (один процес, послідовний цикл по stdin) —
65
+ жодних тредів/asyncio, це не потрібно й лише ускладнить lock-семантику з skill
66
+ ("guard predict with a lock, one GPU serves one forward pass at a time").
67
+
68
+ ### `laya-cli filter --where "<expr>" [--sort -field,+field2]`
69
+
70
+ - Легкий пост-фільтр/сортувальник над JSONL з stdout команди `classify` (або будь-яким JSONL
71
+ з числовими полями). `--where` — прості порівняння (`field>=0.4`, `field==value`,
72
+ `field!=value`), можна кілька через кому (AND). `--sort` — список полів, `-` префікс = desc.
73
+ - Це заміняє ручний python-скрипт сортування/фільтрації, який я сам писав під кожен кейс
74
+ (`build_shortlist*.py`).
75
+ - Не вигадувати повноцінну query-мову (jq вже існує для складного) — тільки прості
76
+ порівняння, найчастіший випадок.
77
+
78
+ ### `laya-cli questions <preset>`
79
+
80
+ - Друкує в stdout готовий `questions.json` з вбудованих пресетів Laya:
81
+ `triage`, `email`, `guard`, `moderation`, `router` — прямий прокид
82
+ `laya.triage_questions()` / `laya.email_questions()` / `laya.guard_questions()` /
83
+ `laya.moderation_questions()` / `laya.router_questions()` у JSON, щоб було з чого стартувати
84
+ й відредагувати руками, а не читати python-докстрінги.
85
+
86
+ ### `laya-cli evaluate --questions <file.json> --labeled <file.jsonl> --field <question_id>`
87
+
88
+ - Реалізація розділу skill "Evaluate before shipping": вхід — JSONL з `state` +
89
+ колонкою правильної відповіді (`--label-field`, дефолт `label`), прогонити класифікацію,
90
+ порахувати accuracy per question, скільки % проходить поріг (`--threshold`, дефолт 0.5) і
91
+ скільки з тих, що пройшли, правильні (precision @ threshold). Вивід — короткий текстовий
92
+ звіт у stdout (не JSONL), саме той, що треба показати юзеру перед тим як довіряти цифрам
93
+ ("Tell the user the measured numbers and the escalation rate, not just that it works" —
94
+ пряма цитата зі skill).
95
+ - Це не про калібрацію температури (то окремо, нижче) — це про "чи взагалі working" на
96
+ реальних прикладах юзера, мінімальний обов'язковий крок перед тим, як шортлист комусь
97
+ показати.
98
+
99
+ ## Explicitly НЕ в MVP (не робити, поки не попросять)
100
+
101
+ - Калібрація температури (`agent.temperature` fit під NLL) — skill описує процедуру, це
102
+ окрема команда `laya-cli calibrate`, але вона складніша (потребує NLL-оптимізацію,
103
+ тестовий split) — робити тільки якщо `evaluate` покаже, що calibration реально потрібна.
104
+ Не будувати наперед.
105
+ - HTTP-сервер / sidecar-режим (skill згадує FastAPI-приклад для не-python стеків) — YAGNI,
106
+ поки немає не-python консюмера. `classify` як stdin/stdout CLI покриває поточний кейс
107
+ (px | laya-cli).
108
+ - Власна DB/кеш результатів між запусками — CLI stateless, вхід/вихід через JSONL-файли,
109
+ версіонування кешу — задача того, хто його викликає (git, файли в проєкті).
110
+ - Multi-model ensemble / голосування кількох чекпоінтів — не було в жодному реальному кейсі.
111
+
112
+ ## Acceptance criteria
113
+
114
+ - [ ] `laya-cli classify` не додає нічого до тексту `state` за замовчуванням (перевірити на
115
+ прикладі: вхід і те, що фактично йде в `agent.predict`, ідентичні substring).
116
+ - [ ] Модель вантажиться рівно раз за запуск (лог/таймер підтверджує — не 82 завантаження на
117
+ 82 рядки).
118
+ - [ ] `classify | filter --where "on_topic>=0.4" --sort -on_topic` відтворює той самий
119
+ результат, що я зараз отримую вручну через `build_shortlist*.py` на тому ж вхідному
120
+ наборі.
121
+ - [ ] `questions triage` (і решта пресетів) видає валідний JSON, який приймає `classify`
122
+ без правок.
123
+ - [ ] `evaluate` на 20-50 розмічених прикладів дає числа (accuracy, поріг-precision), а не
124
+ просто "ok".
125
+ - [ ] Без мережі (з прогрітим HF-кешем, `HF_HUB_OFFLINE=1`) CLI працює — інструмент має
126
+ прокидати цю env-змінну прозоро, не блокувати офлайн-роботу.
127
+ - [ ] `--help` на кожній команді — з прикладом виклику, як у px (уже показав, що це реально
128
+ корисно при перевірці).