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.
- laya_cli-0.1.0/.github/workflows/ci.yml +41 -0
- laya_cli-0.1.0/.github/workflows/publish.yml +30 -0
- laya_cli-0.1.0/.gitignore +47 -0
- laya_cli-0.1.0/.python-version +1 -0
- laya_cli-0.1.0/LICENSE +17 -0
- laya_cli-0.1.0/PKG-INFO +182 -0
- laya_cli-0.1.0/README.md +147 -0
- laya_cli-0.1.0/TASK.md +128 -0
- laya_cli-0.1.0/pyproject.toml +105 -0
- laya_cli-0.1.0/src/laya_cli/__init__.py +3 -0
- laya_cli-0.1.0/src/laya_cli/cli.py +1332 -0
- laya_cli-0.1.0/tests/__init__.py +0 -0
- laya_cli-0.1.0/tests/conftest.py +147 -0
- laya_cli-0.1.0/tests/test_classify.py +78 -0
- laya_cli-0.1.0/tests/test_cli.py +107 -0
- laya_cli-0.1.0/tests/test_evaluate.py +79 -0
- laya_cli-0.1.0/tests/test_filter.py +56 -0
- laya_cli-0.1.0/tests/test_predict.py +160 -0
- laya_cli-0.1.0/tests/test_questions.py +60 -0
- laya_cli-0.1.0/uv.lock +1892 -0
|
@@ -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).
|
laya_cli-0.1.0/PKG-INFO
ADDED
|
@@ -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
|
+
[](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
|
+
|
|
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`).
|
laya_cli-0.1.0/README.md
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
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
|
+
|
|
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
|
+
корисно при перевірці).
|