luar 0.2.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.
- luar-0.2.0/LICENSE +21 -0
- luar-0.2.0/PKG-INFO +227 -0
- luar-0.2.0/README.md +196 -0
- luar-0.2.0/pyproject.toml +49 -0
- luar-0.2.0/setup.cfg +4 -0
- luar-0.2.0/src/luar/__init__.py +16 -0
- luar-0.2.0/src/luar/__main__.py +5 -0
- luar-0.2.0/src/luar/app.py +256 -0
- luar-0.2.0/src/luar/backends/__init__.py +17 -0
- luar-0.2.0/src/luar/backends/base.py +29 -0
- luar-0.2.0/src/luar/backends/laya_backend.py +96 -0
- luar-0.2.0/src/luar/backends/lmstudio_backend.py +163 -0
- luar-0.2.0/src/luar/cli.py +103 -0
- luar-0.2.0/src/luar/engine.py +190 -0
- luar-0.2.0/src/luar/questions.py +134 -0
- luar-0.2.0/src/luar/report.py +95 -0
- luar-0.2.0/src/luar/tables.py +87 -0
- luar-0.2.0/src/luar.egg-info/PKG-INFO +227 -0
- luar-0.2.0/src/luar.egg-info/SOURCES.txt +26 -0
- luar-0.2.0/src/luar.egg-info/dependency_links.txt +1 -0
- luar-0.2.0/src/luar.egg-info/entry_points.txt +2 -0
- luar-0.2.0/src/luar.egg-info/requires.txt +9 -0
- luar-0.2.0/src/luar.egg-info/top_level.txt +1 -0
- luar-0.2.0/tests/test_engine.py +84 -0
- luar-0.2.0/tests/test_laya_backend.py +59 -0
- luar-0.2.0/tests/test_lmstudio_backend.py +129 -0
- luar-0.2.0/tests/test_questions.py +66 -0
- luar-0.2.0/tests/test_tables.py +65 -0
luar-0.2.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Alexandre Alves
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
luar-0.2.0/PKG-INFO
ADDED
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: luar
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: LUAR (Local Utility for Automated Reviews): catalog spreadsheet rows with a local decision model.
|
|
5
|
+
Author-email: Alexandre Alves <alexxalvess@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/HayateV30/luar
|
|
8
|
+
Project-URL: Documentation, https://github.com/HayateV30/luar#readme
|
|
9
|
+
Project-URL: Issues, https://github.com/HayateV30/luar/issues
|
|
10
|
+
Project-URL: Changelog, https://github.com/HayateV30/luar/releases
|
|
11
|
+
Keywords: classification,spreadsheet,csv,local-ai,laya,lm-studio,gradio
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Environment :: Web Environment
|
|
14
|
+
Classifier: Intended Audience :: End Users/Desktop
|
|
15
|
+
Classifier: Intended Audience :: Science/Research
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
19
|
+
Classifier: Topic :: Office/Business
|
|
20
|
+
Requires-Python: >=3.10
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
License-File: LICENSE
|
|
23
|
+
Requires-Dist: pandas>=2.0
|
|
24
|
+
Requires-Dist: openpyxl>=3.1
|
|
25
|
+
Requires-Dist: laya>=0.3.21
|
|
26
|
+
Provides-Extra: ui
|
|
27
|
+
Requires-Dist: gradio>=5.0; extra == "ui"
|
|
28
|
+
Provides-Extra: dev
|
|
29
|
+
Requires-Dist: pytest>=8; extra == "dev"
|
|
30
|
+
Dynamic: license-file
|
|
31
|
+
|
|
32
|
+
# 🌙 LUAR: Local Utility for Automated Reviews
|
|
33
|
+
|
|
34
|
+
Drop a spreadsheet, say what you want to know about each row, and get back a copy with the answers
|
|
35
|
+
plus a Markdown summary. Everything runs **on your own computer**: no cloud, no data sent anywhere.
|
|
36
|
+
|
|
37
|
+
**Who it's for:** anyone with a spreadsheet of text (reviews, survey answers, messages, notes) who
|
|
38
|
+
wants it labeled without writing code, SQL or Docker files. One `pip install`, one file, your own
|
|
39
|
+
questions.
|
|
40
|
+
|
|
41
|
+
LUAR has two local engines:
|
|
42
|
+
|
|
43
|
+
| Engine | What it is | Speed* | When to use |
|
|
44
|
+
|---|---|---|---|
|
|
45
|
+
| **Laya** (default) | [Laya](https://huggingface.co/convaiinnovations/laya), an open decision model in the style of Jev: it answers *structured* questions with a probability, instead of generating text | ~0.6 s per row | large files, quick passes |
|
|
46
|
+
| **LM Studio** | any chat model you run in [LM Studio](https://lmstudio.ai) (tested with Qwen 3.5 4B) | ~5 s per row *per question* | smaller files, when accuracy matters most |
|
|
47
|
+
|
|
48
|
+
<sub>*On a laptop CPU without a dedicated GPU. Both engines give a real probability as confidence.</sub>
|
|
49
|
+
|
|
50
|
+
*[Leia em português](https://github.com/HayateV30/luar/blob/main/README.pt-BR.md)*
|
|
51
|
+
|
|
52
|
+
## What it does
|
|
53
|
+
|
|
54
|
+
| You give it | You get back |
|
|
55
|
+
|---|---|
|
|
56
|
+
| A `.csv` or `.xlsx` file | `<name>_luar.csv/.xlsx`: a **copy** with new columns (your original is never touched) |
|
|
57
|
+
| The column(s) to read | For each question: `<id>` (the answer) and `<id>_confidence` (0–1) |
|
|
58
|
+
| Your questions | `needs_review` = `yes` when any answer is below the confidence threshold, and `review_reasons` |
|
|
59
|
+
| | `<name>_luar_summary.md`: counts per answer, rows to review, accuracy (see below) |
|
|
60
|
+
|
|
61
|
+
### Question types
|
|
62
|
+
|
|
63
|
+
| Type | Answers | Example |
|
|
64
|
+
|---|---|---|
|
|
65
|
+
| `choice` | one of your options (2–20) | *What is this review about?* delivery / product / support / price |
|
|
66
|
+
| `noul` | `yes` or `no` | *Does the customer ask for their money back?* |
|
|
67
|
+
| `score` ⚠️ | a level on an ordered scale | *How severe is it?* low / medium / high |
|
|
68
|
+
|
|
69
|
+
> ⚠️ **`score` is experimental.** It was the least reliable type in our tests: 71% (Laya) and 79%
|
|
70
|
+
> (LM Studio) agreement with hand labels on the bundled severity example. Prefer `choice` or `noul`,
|
|
71
|
+
> or check `score` results by hand.
|
|
72
|
+
|
|
73
|
+
## Install
|
|
74
|
+
|
|
75
|
+
Requires Python 3.10+. The first run downloads the Laya model (a few hundred MB) from Hugging Face.
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
pip install "luar[ui]"
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Leave out `[ui]` if you only need the command line. To get the bundled examples (and the example
|
|
82
|
+
picker in the web interface), install from the repository instead:
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
git clone https://github.com/HayateV30/luar.git
|
|
86
|
+
cd luar
|
|
87
|
+
pip install -e ".[ui]"
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Use it
|
|
91
|
+
|
|
92
|
+
### Web interface
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
luar ui
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
This opens `http://127.0.0.1:7860` in your browser (local only). Then:
|
|
99
|
+
|
|
100
|
+
1. Drop your file and tick the column(s) the model should read.
|
|
101
|
+
2. Fill in the questions table (or load a questions `.json`, or pick a bundled example).
|
|
102
|
+
3. Pick the engine (Laya or LM Studio), click **Run** and download the result copy and the summary.
|
|
103
|
+
|
|
104
|
+
### Command line
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
luar columns examples/reviews.csv
|
|
108
|
+
luar run examples/reviews.csv -q examples/reviews_questions.json -c review
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Options: `-t/--threshold` (default `0.7`), `-o/--out-dir`, `--sheet` (Excel), `-e/--engine`
|
|
112
|
+
(`laya` or `lmstudio`). Laya: `--checkpoint` (`auto`, `multilingual`, `english`), `--device`
|
|
113
|
+
(`cpu`/`cuda`). LM Studio: `--model`, `--lmstudio-url`.
|
|
114
|
+
|
|
115
|
+
### Using LM Studio
|
|
116
|
+
|
|
117
|
+
1. Install [LM Studio](https://lmstudio.ai) and download a chat model (e.g. `qwen3.5-4b`).
|
|
118
|
+
2. Start the server and load the model, in the app (*Developer → Start Server*) or with its CLI:
|
|
119
|
+
```bash
|
|
120
|
+
lms server start
|
|
121
|
+
lms load qwen3.5-4b
|
|
122
|
+
```
|
|
123
|
+
3. Run with `--engine lmstudio` (or choose **LM Studio** in the web interface).
|
|
124
|
+
|
|
125
|
+
The server URL defaults to `http://localhost:1234/v1` (change it with `LUAR_LMSTUDIO_URL`). If you turned
|
|
126
|
+
on *Require API key* in LM Studio, put the key in the `LMSTUDIO_API_KEY` environment variable, never in
|
|
127
|
+
a file you commit. LUAR turns the model's "thinking" off (`reasoning_effort: none`): each answer is a
|
|
128
|
+
single token.
|
|
129
|
+
|
|
130
|
+
### Python
|
|
131
|
+
|
|
132
|
+
```python
|
|
133
|
+
from luar import load_questions, run_file
|
|
134
|
+
from luar.backends import make_backend
|
|
135
|
+
|
|
136
|
+
backend = make_backend("laya") # or make_backend("lmstudio", model="qwen3.5-4b")
|
|
137
|
+
result = run_file("data.csv", load_questions("questions.json"), ["text"], backend)
|
|
138
|
+
print(result.table_path, result.summary_path)
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## Questions file
|
|
142
|
+
|
|
143
|
+
```json
|
|
144
|
+
[
|
|
145
|
+
{
|
|
146
|
+
"id": "topic",
|
|
147
|
+
"type": "choice",
|
|
148
|
+
"question": "What is this review mainly about?",
|
|
149
|
+
"options": {
|
|
150
|
+
"delivery": "shipping, delays, courier, package condition",
|
|
151
|
+
"product": "quality, defects, how the item works"
|
|
152
|
+
}
|
|
153
|
+
},
|
|
154
|
+
{"id": "sentiment", "type": "choice", "question": "Overall sentiment?", "options": ["positive", "neutral", "negative"]},
|
|
155
|
+
{"id": "wants_refund", "type": "noul", "question": "Does the customer explicitly ask for their money back?"}
|
|
156
|
+
]
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
- `id`: letters, digits and `_`; becomes the column name.
|
|
160
|
+
- `options`: a list of labels, or `{label: description}`. Descriptions help the model a lot.
|
|
161
|
+
- Laya's native format (`{"id": {"type", "instructions", "criteria"}}`) is accepted too.
|
|
162
|
+
|
|
163
|
+
## Check before you trust it
|
|
164
|
+
|
|
165
|
+
A model can be confidently wrong, so measure it on your own data before using the results:
|
|
166
|
+
|
|
167
|
+
1. Label 20–50 rows by hand in columns named **`expected_<id>`** (e.g. `expected_topic`).
|
|
168
|
+
For `noul`, `yes/no`, `sim/não`, `true/false` and `1/0` all work.
|
|
169
|
+
2. Run LUAR. The summary shows **accuracy against `expected_<id>`** for each question.
|
|
170
|
+
3. Adjust the questions (wording, option descriptions) or the threshold until you are happy.
|
|
171
|
+
|
|
172
|
+
`expected_*` columns are never offered to the model as input.
|
|
173
|
+
|
|
174
|
+
## Tips from testing
|
|
175
|
+
|
|
176
|
+
- **Accuracy on the bundled examples:** Laya got 86–100% on `choice`/`noul`; LM Studio with
|
|
177
|
+
Qwen 3.5 4B got 100% on all of them, but took over 20x longer (3 questions: ~14 s vs ~0.6 s per row).
|
|
178
|
+
- **Language (Laya):** `auto` picks the `english` model when the file is in English and `multilingual`
|
|
179
|
+
otherwise. On English text, the English model was clearly better; on Portuguese, the multilingual one.
|
|
180
|
+
- **Describe options.** `"price": "cost, value for money, charges"` beats a bare `"price"`.
|
|
181
|
+
- **Option order can change answers.** Put the most specific options first and test with `expected_*`.
|
|
182
|
+
- **Keep to about 20 options per question.** Split larger lists into two questions.
|
|
183
|
+
- **Confidence is a hint, not a guarantee.** Use `needs_review` to decide where a human should look.
|
|
184
|
+
|
|
185
|
+
## Related projects
|
|
186
|
+
|
|
187
|
+
Other open tools around Laya, in case one fits you better:
|
|
188
|
+
|
|
189
|
+
- [laya-studio](https://github.com/felix-homelab/laya-studio): a full web platform (Docker + PostgreSQL)
|
|
190
|
+
with batch evaluation, calibration monitoring, a review queue and automations. Pick it if you
|
|
191
|
+
are building a decision system for a team; pick LUAR if you just want a labeled spreadsheet.
|
|
192
|
+
- [vgi-laya](https://github.com/lmangani/vgi-laya): Laya as SQL functions inside DuckDB. Pick it if
|
|
193
|
+
your data already lives in a database and you are comfortable with SQL.
|
|
194
|
+
- More in the [laya.tools](https://laya.tools) directory.
|
|
195
|
+
|
|
196
|
+
## Architecture
|
|
197
|
+
|
|
198
|
+
```
|
|
199
|
+
src/luar/
|
|
200
|
+
questions.py question model + validation (choice / score / noul)
|
|
201
|
+
tables.py CSV/XLSX reading (separator & encoding sniffing) and never-overwrite writing
|
|
202
|
+
engine.py runs the questions over rows, confidence threshold, accuracy
|
|
203
|
+
report.py Markdown summary
|
|
204
|
+
backends/ the "socket" for decision engines: Laya and LM Studio
|
|
205
|
+
cli.py, app.py command line and Gradio interface
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
A new engine only needs to implement
|
|
209
|
+
`Backend.decide(texts, questions) -> [{question_id: Answer(value, confidence)}]`.
|
|
210
|
+
|
|
211
|
+
**How the LM Studio engine gets a confidence:** each option is shown with a letter (A, B, C…), the model
|
|
212
|
+
answers with one token, and LUAR reads the probability the model gave each letter (`logprobs`),
|
|
213
|
+
renormalized over the valid letters. That is the model's actual probability, not a number it writes
|
|
214
|
+
about itself.
|
|
215
|
+
|
|
216
|
+
## Development
|
|
217
|
+
|
|
218
|
+
```bash
|
|
219
|
+
pip install -e ".[ui,dev]"
|
|
220
|
+
pytest # fast tests, no model needed
|
|
221
|
+
pytest -m slow -s # runs the real models on the examples (LM Studio tests skip if the server is off)
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
## License
|
|
225
|
+
|
|
226
|
+
MIT for LUAR's code. Laya (the package and the model weights) is a separate project with its own
|
|
227
|
+
license; check its [model card](https://huggingface.co/convaiinnovations/laya) before redistributing it.
|
luar-0.2.0/README.md
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
# 🌙 LUAR: Local Utility for Automated Reviews
|
|
2
|
+
|
|
3
|
+
Drop a spreadsheet, say what you want to know about each row, and get back a copy with the answers
|
|
4
|
+
plus a Markdown summary. Everything runs **on your own computer**: no cloud, no data sent anywhere.
|
|
5
|
+
|
|
6
|
+
**Who it's for:** anyone with a spreadsheet of text (reviews, survey answers, messages, notes) who
|
|
7
|
+
wants it labeled without writing code, SQL or Docker files. One `pip install`, one file, your own
|
|
8
|
+
questions.
|
|
9
|
+
|
|
10
|
+
LUAR has two local engines:
|
|
11
|
+
|
|
12
|
+
| Engine | What it is | Speed* | When to use |
|
|
13
|
+
|---|---|---|---|
|
|
14
|
+
| **Laya** (default) | [Laya](https://huggingface.co/convaiinnovations/laya), an open decision model in the style of Jev: it answers *structured* questions with a probability, instead of generating text | ~0.6 s per row | large files, quick passes |
|
|
15
|
+
| **LM Studio** | any chat model you run in [LM Studio](https://lmstudio.ai) (tested with Qwen 3.5 4B) | ~5 s per row *per question* | smaller files, when accuracy matters most |
|
|
16
|
+
|
|
17
|
+
<sub>*On a laptop CPU without a dedicated GPU. Both engines give a real probability as confidence.</sub>
|
|
18
|
+
|
|
19
|
+
*[Leia em português](https://github.com/HayateV30/luar/blob/main/README.pt-BR.md)*
|
|
20
|
+
|
|
21
|
+
## What it does
|
|
22
|
+
|
|
23
|
+
| You give it | You get back |
|
|
24
|
+
|---|---|
|
|
25
|
+
| A `.csv` or `.xlsx` file | `<name>_luar.csv/.xlsx`: a **copy** with new columns (your original is never touched) |
|
|
26
|
+
| The column(s) to read | For each question: `<id>` (the answer) and `<id>_confidence` (0–1) |
|
|
27
|
+
| Your questions | `needs_review` = `yes` when any answer is below the confidence threshold, and `review_reasons` |
|
|
28
|
+
| | `<name>_luar_summary.md`: counts per answer, rows to review, accuracy (see below) |
|
|
29
|
+
|
|
30
|
+
### Question types
|
|
31
|
+
|
|
32
|
+
| Type | Answers | Example |
|
|
33
|
+
|---|---|---|
|
|
34
|
+
| `choice` | one of your options (2–20) | *What is this review about?* delivery / product / support / price |
|
|
35
|
+
| `noul` | `yes` or `no` | *Does the customer ask for their money back?* |
|
|
36
|
+
| `score` ⚠️ | a level on an ordered scale | *How severe is it?* low / medium / high |
|
|
37
|
+
|
|
38
|
+
> ⚠️ **`score` is experimental.** It was the least reliable type in our tests: 71% (Laya) and 79%
|
|
39
|
+
> (LM Studio) agreement with hand labels on the bundled severity example. Prefer `choice` or `noul`,
|
|
40
|
+
> or check `score` results by hand.
|
|
41
|
+
|
|
42
|
+
## Install
|
|
43
|
+
|
|
44
|
+
Requires Python 3.10+. The first run downloads the Laya model (a few hundred MB) from Hugging Face.
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
pip install "luar[ui]"
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Leave out `[ui]` if you only need the command line. To get the bundled examples (and the example
|
|
51
|
+
picker in the web interface), install from the repository instead:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
git clone https://github.com/HayateV30/luar.git
|
|
55
|
+
cd luar
|
|
56
|
+
pip install -e ".[ui]"
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Use it
|
|
60
|
+
|
|
61
|
+
### Web interface
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
luar ui
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
This opens `http://127.0.0.1:7860` in your browser (local only). Then:
|
|
68
|
+
|
|
69
|
+
1. Drop your file and tick the column(s) the model should read.
|
|
70
|
+
2. Fill in the questions table (or load a questions `.json`, or pick a bundled example).
|
|
71
|
+
3. Pick the engine (Laya or LM Studio), click **Run** and download the result copy and the summary.
|
|
72
|
+
|
|
73
|
+
### Command line
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
luar columns examples/reviews.csv
|
|
77
|
+
luar run examples/reviews.csv -q examples/reviews_questions.json -c review
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Options: `-t/--threshold` (default `0.7`), `-o/--out-dir`, `--sheet` (Excel), `-e/--engine`
|
|
81
|
+
(`laya` or `lmstudio`). Laya: `--checkpoint` (`auto`, `multilingual`, `english`), `--device`
|
|
82
|
+
(`cpu`/`cuda`). LM Studio: `--model`, `--lmstudio-url`.
|
|
83
|
+
|
|
84
|
+
### Using LM Studio
|
|
85
|
+
|
|
86
|
+
1. Install [LM Studio](https://lmstudio.ai) and download a chat model (e.g. `qwen3.5-4b`).
|
|
87
|
+
2. Start the server and load the model, in the app (*Developer → Start Server*) or with its CLI:
|
|
88
|
+
```bash
|
|
89
|
+
lms server start
|
|
90
|
+
lms load qwen3.5-4b
|
|
91
|
+
```
|
|
92
|
+
3. Run with `--engine lmstudio` (or choose **LM Studio** in the web interface).
|
|
93
|
+
|
|
94
|
+
The server URL defaults to `http://localhost:1234/v1` (change it with `LUAR_LMSTUDIO_URL`). If you turned
|
|
95
|
+
on *Require API key* in LM Studio, put the key in the `LMSTUDIO_API_KEY` environment variable, never in
|
|
96
|
+
a file you commit. LUAR turns the model's "thinking" off (`reasoning_effort: none`): each answer is a
|
|
97
|
+
single token.
|
|
98
|
+
|
|
99
|
+
### Python
|
|
100
|
+
|
|
101
|
+
```python
|
|
102
|
+
from luar import load_questions, run_file
|
|
103
|
+
from luar.backends import make_backend
|
|
104
|
+
|
|
105
|
+
backend = make_backend("laya") # or make_backend("lmstudio", model="qwen3.5-4b")
|
|
106
|
+
result = run_file("data.csv", load_questions("questions.json"), ["text"], backend)
|
|
107
|
+
print(result.table_path, result.summary_path)
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## Questions file
|
|
111
|
+
|
|
112
|
+
```json
|
|
113
|
+
[
|
|
114
|
+
{
|
|
115
|
+
"id": "topic",
|
|
116
|
+
"type": "choice",
|
|
117
|
+
"question": "What is this review mainly about?",
|
|
118
|
+
"options": {
|
|
119
|
+
"delivery": "shipping, delays, courier, package condition",
|
|
120
|
+
"product": "quality, defects, how the item works"
|
|
121
|
+
}
|
|
122
|
+
},
|
|
123
|
+
{"id": "sentiment", "type": "choice", "question": "Overall sentiment?", "options": ["positive", "neutral", "negative"]},
|
|
124
|
+
{"id": "wants_refund", "type": "noul", "question": "Does the customer explicitly ask for their money back?"}
|
|
125
|
+
]
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
- `id`: letters, digits and `_`; becomes the column name.
|
|
129
|
+
- `options`: a list of labels, or `{label: description}`. Descriptions help the model a lot.
|
|
130
|
+
- Laya's native format (`{"id": {"type", "instructions", "criteria"}}`) is accepted too.
|
|
131
|
+
|
|
132
|
+
## Check before you trust it
|
|
133
|
+
|
|
134
|
+
A model can be confidently wrong, so measure it on your own data before using the results:
|
|
135
|
+
|
|
136
|
+
1. Label 20–50 rows by hand in columns named **`expected_<id>`** (e.g. `expected_topic`).
|
|
137
|
+
For `noul`, `yes/no`, `sim/não`, `true/false` and `1/0` all work.
|
|
138
|
+
2. Run LUAR. The summary shows **accuracy against `expected_<id>`** for each question.
|
|
139
|
+
3. Adjust the questions (wording, option descriptions) or the threshold until you are happy.
|
|
140
|
+
|
|
141
|
+
`expected_*` columns are never offered to the model as input.
|
|
142
|
+
|
|
143
|
+
## Tips from testing
|
|
144
|
+
|
|
145
|
+
- **Accuracy on the bundled examples:** Laya got 86–100% on `choice`/`noul`; LM Studio with
|
|
146
|
+
Qwen 3.5 4B got 100% on all of them, but took over 20x longer (3 questions: ~14 s vs ~0.6 s per row).
|
|
147
|
+
- **Language (Laya):** `auto` picks the `english` model when the file is in English and `multilingual`
|
|
148
|
+
otherwise. On English text, the English model was clearly better; on Portuguese, the multilingual one.
|
|
149
|
+
- **Describe options.** `"price": "cost, value for money, charges"` beats a bare `"price"`.
|
|
150
|
+
- **Option order can change answers.** Put the most specific options first and test with `expected_*`.
|
|
151
|
+
- **Keep to about 20 options per question.** Split larger lists into two questions.
|
|
152
|
+
- **Confidence is a hint, not a guarantee.** Use `needs_review` to decide where a human should look.
|
|
153
|
+
|
|
154
|
+
## Related projects
|
|
155
|
+
|
|
156
|
+
Other open tools around Laya, in case one fits you better:
|
|
157
|
+
|
|
158
|
+
- [laya-studio](https://github.com/felix-homelab/laya-studio): a full web platform (Docker + PostgreSQL)
|
|
159
|
+
with batch evaluation, calibration monitoring, a review queue and automations. Pick it if you
|
|
160
|
+
are building a decision system for a team; pick LUAR if you just want a labeled spreadsheet.
|
|
161
|
+
- [vgi-laya](https://github.com/lmangani/vgi-laya): Laya as SQL functions inside DuckDB. Pick it if
|
|
162
|
+
your data already lives in a database and you are comfortable with SQL.
|
|
163
|
+
- More in the [laya.tools](https://laya.tools) directory.
|
|
164
|
+
|
|
165
|
+
## Architecture
|
|
166
|
+
|
|
167
|
+
```
|
|
168
|
+
src/luar/
|
|
169
|
+
questions.py question model + validation (choice / score / noul)
|
|
170
|
+
tables.py CSV/XLSX reading (separator & encoding sniffing) and never-overwrite writing
|
|
171
|
+
engine.py runs the questions over rows, confidence threshold, accuracy
|
|
172
|
+
report.py Markdown summary
|
|
173
|
+
backends/ the "socket" for decision engines: Laya and LM Studio
|
|
174
|
+
cli.py, app.py command line and Gradio interface
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
A new engine only needs to implement
|
|
178
|
+
`Backend.decide(texts, questions) -> [{question_id: Answer(value, confidence)}]`.
|
|
179
|
+
|
|
180
|
+
**How the LM Studio engine gets a confidence:** each option is shown with a letter (A, B, C…), the model
|
|
181
|
+
answers with one token, and LUAR reads the probability the model gave each letter (`logprobs`),
|
|
182
|
+
renormalized over the valid letters. That is the model's actual probability, not a number it writes
|
|
183
|
+
about itself.
|
|
184
|
+
|
|
185
|
+
## Development
|
|
186
|
+
|
|
187
|
+
```bash
|
|
188
|
+
pip install -e ".[ui,dev]"
|
|
189
|
+
pytest # fast tests, no model needed
|
|
190
|
+
pytest -m slow -s # runs the real models on the examples (LM Studio tests skip if the server is off)
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
## License
|
|
194
|
+
|
|
195
|
+
MIT for LUAR's code. Laya (the package and the model weights) is a separate project with its own
|
|
196
|
+
license; check its [model card](https://huggingface.co/convaiinnovations/laya) before redistributing it.
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "luar"
|
|
7
|
+
version = "0.2.0"
|
|
8
|
+
description = "LUAR (Local Utility for Automated Reviews): catalog spreadsheet rows with a local decision model."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
authors = [{ name = "Alexandre Alves", email = "alexxalvess@gmail.com" }]
|
|
12
|
+
requires-python = ">=3.10"
|
|
13
|
+
keywords = ["classification", "spreadsheet", "csv", "local-ai", "laya", "lm-studio", "gradio"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 3 - Alpha",
|
|
16
|
+
"Environment :: Web Environment",
|
|
17
|
+
"Intended Audience :: End Users/Desktop",
|
|
18
|
+
"Intended Audience :: Science/Research",
|
|
19
|
+
"Operating System :: OS Independent",
|
|
20
|
+
"Programming Language :: Python :: 3",
|
|
21
|
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
22
|
+
"Topic :: Office/Business",
|
|
23
|
+
]
|
|
24
|
+
dependencies = [
|
|
25
|
+
"pandas>=2.0",
|
|
26
|
+
"openpyxl>=3.1",
|
|
27
|
+
"laya>=0.3.21",
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
[project.urls]
|
|
31
|
+
Homepage = "https://github.com/HayateV30/luar"
|
|
32
|
+
Documentation = "https://github.com/HayateV30/luar#readme"
|
|
33
|
+
Issues = "https://github.com/HayateV30/luar/issues"
|
|
34
|
+
Changelog = "https://github.com/HayateV30/luar/releases"
|
|
35
|
+
|
|
36
|
+
[project.optional-dependencies]
|
|
37
|
+
ui = ["gradio>=5.0"]
|
|
38
|
+
dev = ["pytest>=8"]
|
|
39
|
+
|
|
40
|
+
[project.scripts]
|
|
41
|
+
luar = "luar.cli:main"
|
|
42
|
+
|
|
43
|
+
[tool.setuptools.packages.find]
|
|
44
|
+
where = ["src"]
|
|
45
|
+
|
|
46
|
+
[tool.pytest.ini_options]
|
|
47
|
+
testpaths = ["tests"]
|
|
48
|
+
markers = ["slow: runs the real Laya model (downloads weights; run with -m slow)"]
|
|
49
|
+
addopts = "-m 'not slow'"
|
luar-0.2.0/setup.cfg
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""LUAR - Local Utility for Automated Reviews."""
|
|
2
|
+
from .engine import DEFAULT_THRESHOLD, Result, classify, run_file
|
|
3
|
+
from .questions import Question, QuestionError, load_questions, parse_questions
|
|
4
|
+
|
|
5
|
+
__version__ = "0.2.0"
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
"DEFAULT_THRESHOLD",
|
|
9
|
+
"Question",
|
|
10
|
+
"QuestionError",
|
|
11
|
+
"Result",
|
|
12
|
+
"classify",
|
|
13
|
+
"load_questions",
|
|
14
|
+
"parse_questions",
|
|
15
|
+
"run_file",
|
|
16
|
+
]
|