llm-openrouter-decisions 0.1a0__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.
- llm_openrouter_decisions-0.1a0/.gitignore +17 -0
- llm_openrouter_decisions-0.1a0/LICENSE +201 -0
- llm_openrouter_decisions-0.1a0/PKG-INFO +271 -0
- llm_openrouter_decisions-0.1a0/README.md +254 -0
- llm_openrouter_decisions-0.1a0/llm_openrouter_decisions.py +320 -0
- llm_openrouter_decisions-0.1a0/pyproject.toml +49 -0
- llm_openrouter_decisions-0.1a0/tests/conftest.py +25 -0
- llm_openrouter_decisions-0.1a0/tests/test_catalog.py +38 -0
- llm_openrouter_decisions-0.1a0/tests/test_errors.py +54 -0
- llm_openrouter_decisions-0.1a0/tests/test_openrouter_decisions.py +297 -0
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
Apache License
|
|
2
|
+
Version 2.0, January 2004
|
|
3
|
+
http://www.apache.org/licenses/
|
|
4
|
+
|
|
5
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
6
|
+
|
|
7
|
+
1. Definitions.
|
|
8
|
+
|
|
9
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
10
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
11
|
+
|
|
12
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
13
|
+
the copyright owner that is granting the License.
|
|
14
|
+
|
|
15
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
16
|
+
other entities that control, are controlled by, or are under common
|
|
17
|
+
control with that entity. For the purposes of this definition,
|
|
18
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
19
|
+
direction or management of such entity, whether by contract or
|
|
20
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
21
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
22
|
+
|
|
23
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
24
|
+
exercising permissions granted by this License.
|
|
25
|
+
|
|
26
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
27
|
+
including but not limited to software source code, documentation
|
|
28
|
+
source, and configuration files.
|
|
29
|
+
|
|
30
|
+
"Object" form shall mean any form resulting from mechanical
|
|
31
|
+
transformation or translation of a Source form, including but
|
|
32
|
+
not limited to compiled object code, generated documentation,
|
|
33
|
+
and conversions to other media types.
|
|
34
|
+
|
|
35
|
+
"Work" shall mean the work of authorship, whether in Source or
|
|
36
|
+
Object form, made available under the License, as indicated by a
|
|
37
|
+
copyright notice that is included in or attached to the work
|
|
38
|
+
(an example is provided in the Appendix below).
|
|
39
|
+
|
|
40
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
41
|
+
form, that is based on (or derived from) the Work and for which the
|
|
42
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
43
|
+
represent, as a whole, an original work of authorship. For the purposes
|
|
44
|
+
of this License, Derivative Works shall not include works that remain
|
|
45
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
|
46
|
+
the Work and Derivative Works thereof.
|
|
47
|
+
|
|
48
|
+
"Contribution" shall mean any work of authorship, including
|
|
49
|
+
the original version of the Work and any modifications or additions
|
|
50
|
+
to that Work or Derivative Works thereof, that is intentionally
|
|
51
|
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
52
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
|
53
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
|
54
|
+
means any form of electronic, verbal, or written communication sent
|
|
55
|
+
to the Licensor or its representatives, including but not limited to
|
|
56
|
+
communication on electronic mailing lists, source code control systems,
|
|
57
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
|
58
|
+
Licensor for the purpose of discussing and improving the Work, but
|
|
59
|
+
excluding communication that is conspicuously marked or otherwise
|
|
60
|
+
designated in writing by the copyright owner as "Not a Contribution."
|
|
61
|
+
|
|
62
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
63
|
+
on behalf of whom a Contribution has been received by Licensor and
|
|
64
|
+
subsequently incorporated within the Work.
|
|
65
|
+
|
|
66
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
67
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
68
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
69
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
70
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
71
|
+
Work and such Derivative Works in Source or Object form.
|
|
72
|
+
|
|
73
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
74
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
75
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
76
|
+
(except as stated in this section) patent license to make, have made,
|
|
77
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
78
|
+
where such license applies only to those patent claims licensable
|
|
79
|
+
by such Contributor that are necessarily infringed by their
|
|
80
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
|
81
|
+
with the Work to which such Contribution(s) was submitted. If You
|
|
82
|
+
institute patent litigation against any entity (including a
|
|
83
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
84
|
+
or a Contribution incorporated within the Work constitutes direct
|
|
85
|
+
or contributory patent infringement, then any patent licenses
|
|
86
|
+
granted to You under this License for that Work shall terminate
|
|
87
|
+
as of the date such litigation is filed.
|
|
88
|
+
|
|
89
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
90
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
91
|
+
modifications, and in Source or Object form, provided that You
|
|
92
|
+
meet the following conditions:
|
|
93
|
+
|
|
94
|
+
(a) You must give any other recipients of the Work or
|
|
95
|
+
Derivative Works a copy of this License; and
|
|
96
|
+
|
|
97
|
+
(b) You must cause any modified files to carry prominent notices
|
|
98
|
+
stating that You changed the files; and
|
|
99
|
+
|
|
100
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
101
|
+
that You distribute, all copyright, patent, trademark, and
|
|
102
|
+
attribution notices from the Source form of the Work,
|
|
103
|
+
excluding those notices that do not pertain to any part of
|
|
104
|
+
the Derivative Works; and
|
|
105
|
+
|
|
106
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
107
|
+
distribution, then any Derivative Works that You distribute must
|
|
108
|
+
include a readable copy of the attribution notices contained
|
|
109
|
+
within such NOTICE file, excluding those notices that do not
|
|
110
|
+
pertain to any part of the Derivative Works, in at least one
|
|
111
|
+
of the following places: within a NOTICE text file distributed
|
|
112
|
+
as part of the Derivative Works; within the Source form or
|
|
113
|
+
documentation, if provided along with the Derivative Works; or,
|
|
114
|
+
within a display generated by the Derivative Works, if and
|
|
115
|
+
wherever such third-party notices normally appear. The contents
|
|
116
|
+
of the NOTICE file are for informational purposes only and
|
|
117
|
+
do not modify the License. You may add Your own attribution
|
|
118
|
+
notices within Derivative Works that You distribute, alongside
|
|
119
|
+
or as an addendum to the NOTICE text from the Work, provided
|
|
120
|
+
that such additional attribution notices cannot be construed
|
|
121
|
+
as modifying the License.
|
|
122
|
+
|
|
123
|
+
You may add Your own copyright statement to Your modifications and
|
|
124
|
+
may provide additional or different license terms and conditions
|
|
125
|
+
for use, reproduction, or distribution of Your modifications, or
|
|
126
|
+
for any such Derivative Works as a whole, provided Your use,
|
|
127
|
+
reproduction, and distribution of the Work otherwise complies with
|
|
128
|
+
the conditions stated in this License.
|
|
129
|
+
|
|
130
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
131
|
+
any Contribution intentionally submitted for inclusion in the Work
|
|
132
|
+
by You to the Licensor shall be under the terms and conditions of
|
|
133
|
+
this License, without any additional terms or conditions.
|
|
134
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
|
135
|
+
the terms of any separate license agreement you may have executed
|
|
136
|
+
with Licensor regarding such Contributions.
|
|
137
|
+
|
|
138
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
139
|
+
names, trademarks, service marks, or product names of the Licensor,
|
|
140
|
+
except as required for reasonable and customary use in describing the
|
|
141
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
|
142
|
+
|
|
143
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
144
|
+
agreed to in writing, Licensor provides the Work (and each
|
|
145
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
146
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
147
|
+
implied, including, without limitation, any warranties or conditions
|
|
148
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
149
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
150
|
+
appropriateness of using or redistributing the Work and assume any
|
|
151
|
+
risks associated with Your exercise of permissions under this License.
|
|
152
|
+
|
|
153
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
|
154
|
+
whether in tort (including negligence), contract, or otherwise,
|
|
155
|
+
unless required by applicable law (such as deliberate and grossly
|
|
156
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
|
157
|
+
liable to You for damages, including any direct, indirect, special,
|
|
158
|
+
incidental, or consequential damages of any character arising as a
|
|
159
|
+
result of this License or out of the use or inability to use the
|
|
160
|
+
Work (including but not limited to damages for loss of goodwill,
|
|
161
|
+
work stoppage, computer failure or malfunction, or any and all
|
|
162
|
+
other commercial damages or losses), even if such Contributor
|
|
163
|
+
has been advised of the possibility of such damages.
|
|
164
|
+
|
|
165
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
|
166
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
|
167
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
168
|
+
or other liability obligations and/or rights consistent with this
|
|
169
|
+
License. However, in accepting such obligations, You may act only
|
|
170
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
|
171
|
+
of any other Contributor, and only if You agree to indemnify,
|
|
172
|
+
defend, and hold each Contributor harmless for any liability
|
|
173
|
+
incurred by, or claims asserted against, such Contributor by reason
|
|
174
|
+
of your accepting any such warranty or additional liability.
|
|
175
|
+
|
|
176
|
+
END OF TERMS AND CONDITIONS
|
|
177
|
+
|
|
178
|
+
APPENDIX: How to apply the Apache License to your work.
|
|
179
|
+
|
|
180
|
+
To apply the Apache License to your work, attach the following
|
|
181
|
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
182
|
+
replaced with your own identifying information. (Don't include
|
|
183
|
+
the brackets!) The text should be enclosed in the appropriate
|
|
184
|
+
comment syntax for the file format. We also recommend that a
|
|
185
|
+
file or class name and description of purpose be included on the
|
|
186
|
+
same "printed page" as the copyright notice for easier
|
|
187
|
+
identification within third-party archives.
|
|
188
|
+
|
|
189
|
+
Copyright [yyyy] [name of copyright owner]
|
|
190
|
+
|
|
191
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
192
|
+
you may not use this file except in compliance with the License.
|
|
193
|
+
You may obtain a copy of the License at
|
|
194
|
+
|
|
195
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
196
|
+
|
|
197
|
+
Unless required by applicable law or agreed to in writing, software
|
|
198
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
199
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
200
|
+
See the License for the specific language governing permissions and
|
|
201
|
+
limitations under the License.
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: llm-openrouter-decisions
|
|
3
|
+
Version: 0.1a0
|
|
4
|
+
Summary: LLM plugin for the decision models of the OpenRouter Decisions API
|
|
5
|
+
Project-URL: Homepage, https://github.com/aborruso/llm-openrouter-decisions
|
|
6
|
+
Project-URL: Changelog, https://github.com/aborruso/llm-openrouter-decisions/releases
|
|
7
|
+
Project-URL: Issues, https://github.com/aborruso/llm-openrouter-decisions/issues
|
|
8
|
+
Project-URL: CI, https://github.com/aborruso/llm-openrouter-decisions/actions
|
|
9
|
+
Author: Andrea Borruso
|
|
10
|
+
License-Expression: Apache-2.0
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Requires-Python: >=3.10
|
|
13
|
+
Requires-Dist: httpx2>=2.13.0
|
|
14
|
+
Requires-Dist: llm>=0.36
|
|
15
|
+
Requires-Dist: pydantic>=2
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
|
|
18
|
+
# llm-openrouter-decisions
|
|
19
|
+
|
|
20
|
+
[](https://pypi.org/project/llm-openrouter-decisions/)
|
|
21
|
+
[](https://github.com/aborruso/llm-openrouter-decisions/releases)
|
|
22
|
+
[](https://github.com/aborruso/llm-openrouter-decisions/actions/workflows/test.yml)
|
|
23
|
+
[](https://github.com/aborruso/llm-openrouter-decisions/blob/main/LICENSE)
|
|
24
|
+
|
|
25
|
+
A heartfelt thank you to [Simon Willison](https://simonwillison.net/), for the work he has been doing for years to explain AI to everyone, in the open, and for [LLM](https://llm.datasette.io/), an exceptional tool that lets anyone use AI models in a smart, convenient and elegant way. This plugin only exists because LLM does.
|
|
26
|
+
|
|
27
|
+
Use the decision models of the [OpenRouter Decisions API](https://openrouter.ai/docs/guides/community/jev.md) with [LLM](https://llm.datasette.io/).
|
|
28
|
+
|
|
29
|
+
Decision models (also called System One models) do not write text. You send them some content, the *state*, plus a closed question, and they answer with numbers: the probability that a statement is true, a category with a probability for each option, a position on an ordered scale, or a box around something in an image. They are fast and very cheap, which makes them a good fit for classifying, filtering and triaging many items from the command line.
|
|
30
|
+
|
|
31
|
+
Decision models are not just TypeSafe and OpenAI: there are many of them, from many makers. This plugin registers every model in the [OpenRouter decision models catalog](https://openrouter.ai/models?output_modalities=decisions), 30 from 13 makers as of October 2026: TypeSafe Jev, OpenAI GPT-6 Luna Decisions, Perplexity Decider, Cloudflare Clef, Liquid d1, Inception Mercury Decide, Upstage Solar Decide, Microsoft Decision-1, the open weight Kev and more. With a single OpenRouter API key you can try them all and compare them on your own data, changing nothing but `-m`. Models differ in accuracy, speed, price and input types, and their probabilities differ on the same question, so it is worth testing more than one before picking.
|
|
32
|
+
|
|
33
|
+
- Installation
|
|
34
|
+
- Usage
|
|
35
|
+
- Yes/no
|
|
36
|
+
- Categories
|
|
37
|
+
- Scores
|
|
38
|
+
- Images
|
|
39
|
+
- Several questions in one request
|
|
40
|
+
- JSON input
|
|
41
|
+
- Classifying the rows of a CSV file
|
|
42
|
+
- Large batches from Python
|
|
43
|
+
- Things to know
|
|
44
|
+
- Further reading
|
|
45
|
+
- Development
|
|
46
|
+
- Credits
|
|
47
|
+
|
|
48
|
+
## Installation
|
|
49
|
+
|
|
50
|
+
Install this plugin in the same environment as [LLM](https://llm.datasette.io/). It needs LLM 0.36 or later.
|
|
51
|
+
```bash
|
|
52
|
+
llm install llm-openrouter-decisions
|
|
53
|
+
```
|
|
54
|
+
Then set an [OpenRouter API key](https://openrouter.ai/settings/keys), the same one used by [llm-openrouter](https://github.com/simonw/llm-openrouter):
|
|
55
|
+
```bash
|
|
56
|
+
llm keys set openrouter
|
|
57
|
+
```
|
|
58
|
+
You can also use the `OPENROUTER_KEY` environment variable. Models are only registered when a key is available.
|
|
59
|
+
|
|
60
|
+
## Usage
|
|
61
|
+
|
|
62
|
+
Every model is registered with the prefix `openrouter-decisions/`:
|
|
63
|
+
```bash
|
|
64
|
+
llm models | grep openrouter-decisions
|
|
65
|
+
```
|
|
66
|
+
The catalog is downloaded from OpenRouter and cached for an hour in `openrouter_decisions_models.json` in your LLM user directory.
|
|
67
|
+
|
|
68
|
+
Supply the content as the prompt, on stdin or as image attachments, and the question with `-s/--system`. The answer is printed as JSON.
|
|
69
|
+
|
|
70
|
+
The examples below use Jev 1.14. A shorter name helps:
|
|
71
|
+
```bash
|
|
72
|
+
llm aliases set jev14 openrouter-decisions/typesafe/jev-1.14
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Yes/no
|
|
76
|
+
|
|
77
|
+
The default answer type is `noul` (from "Bernoulli"): the probability, from 0 to 1, that the statement is true.
|
|
78
|
+
```bash
|
|
79
|
+
llm -m jev14 'Please refund my last payment.' \
|
|
80
|
+
-s 'Does this message explicitly request a refund?'
|
|
81
|
+
```
|
|
82
|
+
```json
|
|
83
|
+
{"type": "noul", "noul": 0.99}
|
|
84
|
+
```
|
|
85
|
+
Optional definitions for true and false can be passed with `-o criteria '{"true": "...", "false": "..."}'`.
|
|
86
|
+
|
|
87
|
+
## Categories
|
|
88
|
+
|
|
89
|
+
Use `-o answer_type choice` and a JSON object that maps category names to descriptions (or `null`):
|
|
90
|
+
```bash
|
|
91
|
+
llm -m jev14 'Please refund my last payment.' \
|
|
92
|
+
-s 'Which department should handle this message?' \
|
|
93
|
+
-o answer_type choice \
|
|
94
|
+
-o criteria '{
|
|
95
|
+
"billing": "Charges, invoices, refunds",
|
|
96
|
+
"technical": "Problems using the product",
|
|
97
|
+
"other": null
|
|
98
|
+
}'
|
|
99
|
+
```
|
|
100
|
+
```json
|
|
101
|
+
{"type": "choice", "choice": "billing", "probabilities": {"billing": 1, "technical": 0, "other": 0}, "confidence": 1}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## Scores
|
|
105
|
+
|
|
106
|
+
Use `-o answer_type score` and a JSON array of 2 to 10 levels, lowest first. The score is a probability-weighted position from 0 to the number of levels minus one:
|
|
107
|
+
```bash
|
|
108
|
+
llm -m jev14 'Please refund my last payment.' \
|
|
109
|
+
-s 'How urgent is this message?' \
|
|
110
|
+
-o answer_type score \
|
|
111
|
+
-o criteria '["low", "medium", "high"]'
|
|
112
|
+
```
|
|
113
|
+
```json
|
|
114
|
+
{"type": "score", "score": 0.94, "legend": {"0": "low", "1": "medium", "2": "high"}, "probabilities": {"0": 0.17, "1": 0.72, "2": 0.11}, "confidence": 0.58}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## Images
|
|
118
|
+
|
|
119
|
+
Models that read images accept them with `-a`, as a local path or a URL. The image is added to the state, after any text, and can be the whole state:
|
|
120
|
+
```bash
|
|
121
|
+
llm -m jev14 -a https://static.simonwillison.net/static/2025/two-pelicans.jpg \
|
|
122
|
+
-s 'Does this image contain birds?' ''
|
|
123
|
+
```
|
|
124
|
+
```json
|
|
125
|
+
{"type": "noul", "noul": 0.97}
|
|
126
|
+
```
|
|
127
|
+
Jev 1.14 also answers `box` questions: where in the image is the thing described. The answer gives the edges as fractions of the image width and height, from the top-left corner. Here is [examples/chart.png](https://raw.githubusercontent.com/aborruso/llm-openrouter-decisions/main/examples/chart.png):
|
|
128
|
+
|
|
129
|
+

|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
llm -m jev14 -a examples/chart.png -s 'the legend of the chart' -o answer_type box ''
|
|
133
|
+
```
|
|
134
|
+
```json
|
|
135
|
+
{"type": "box", "box": {"top": 0.06, "bottom": 0.15, "left": 0.86, "right": 0.93}, "confidence": 0.69}
|
|
136
|
+
```
|
|
137
|
+
Drawn on the image (red rectangle), the box finds the legend, but covers only its left half: the color swatches and part of the labels.
|
|
138
|
+
|
|
139
|
+

|
|
140
|
+
|
|
141
|
+
Boxes are approximate, and a box question always returns a box, even when the target is not in the image. Ask a yes/no question about it in the same request (see the next section) and only trust the box when that answer is high.
|
|
142
|
+
|
|
143
|
+
Images are sent as base64 data URLs; LLM downloads remote images first, because OpenRouter does not fetch URLs. PNG, JPEG and WebP are accepted, but Jev only reads PNG and JPEG, and wants images between 256 and 1,568 pixels on each side. Text-only models reject attachments before any request is made. See [Multimodal Decisions](https://openrouter.ai/docs/guides/community/multimodal-decisions.md) for the limits of each model.
|
|
144
|
+
|
|
145
|
+
## Several questions in one request
|
|
146
|
+
|
|
147
|
+
Decision models evaluate many questions about the same state in parallel, so asking them together is faster and cheaper than one call per question. Use `-o questions` with a JSON object of named questions, each with a `type`, `instructions` and optional `criteria`, instead of `-s`. The output is an object with one answer per question:
|
|
148
|
+
```bash
|
|
149
|
+
llm -m jev14 -a examples/chart.png -o questions '{
|
|
150
|
+
"is_chart": {"type": "noul", "instructions": "The image is a data chart."},
|
|
151
|
+
"chart_type": {
|
|
152
|
+
"type": "choice",
|
|
153
|
+
"instructions": "What kind of chart is it?",
|
|
154
|
+
"criteria": {"population pyramid": null, "line chart": null, "bar chart": null, "pie chart": null, "map": null}
|
|
155
|
+
},
|
|
156
|
+
"has_legend": {"type": "noul", "instructions": "The chart has a legend."},
|
|
157
|
+
"legend": {"type": "box", "instructions": "the legend of the chart"}
|
|
158
|
+
}' ''
|
|
159
|
+
```
|
|
160
|
+
```json
|
|
161
|
+
{
|
|
162
|
+
"is_chart": {"type": "noul", "noul": 0.95},
|
|
163
|
+
"chart_type": {"type": "choice", "choice": "population pyramid", "probabilities": {"population pyramid": 0.99, "line chart": 0, "bar chart": 0.01, "pie chart": 0, "map": 0}, "confidence": 0.99},
|
|
164
|
+
"has_legend": {"type": "noul", "noul": 0.99},
|
|
165
|
+
"legend": {"type": "box", "box": {"top": 0.06, "bottom": 0.15, "left": 0.86, "right": 0.93}, "confidence": 0.69}
|
|
166
|
+
}
|
|
167
|
+
```
|
|
168
|
+
That request cost $0.00009. The question object is passed to the API as it is, so it follows the [Decisions API request format](https://openrouter.ai/docs/api/api-reference/alphadecisions/submit-a-decisions-request.md).
|
|
169
|
+
|
|
170
|
+
## JSON input
|
|
171
|
+
|
|
172
|
+
With `-o input_format json` the prompt is parsed as JSON and sent as a structured state (a string, an object or an array). This is handy for a record with several fields:
|
|
173
|
+
```bash
|
|
174
|
+
echo '{"id": 1791, "channel": "email", "text": "Strong smell of gas in the street next to the elementary school."}' | \
|
|
175
|
+
llm -m jev14 -o input_format json \
|
|
176
|
+
-s 'The report describes an immediate danger to people'"'"'s safety.'
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## Classifying the rows of a CSV file
|
|
180
|
+
|
|
181
|
+
[examples/city_reports.csv](examples/city_reports.csv) holds 16 made-up reports sent by residents to a city, and [examples/questions.json](examples/questions.json) asks three questions about each one: which department should handle it, whether it is urgent, and whether it contains personal data. This loop turns every row into a JSON object with [DuckDB](https://duckdb.org/), asks the questions and keeps the answers with [jq](https://jqlang.org/):
|
|
182
|
+
```bash
|
|
183
|
+
cd examples
|
|
184
|
+
duckdb -noheader -list -c "select to_json(t) from 'city_reports.csv' t" |
|
|
185
|
+
while IFS= read -r row; do
|
|
186
|
+
llm -m jev14 -o input_format json -o questions "$(cat questions.json)" "$row" </dev/null |
|
|
187
|
+
jq -c --argjson r "$row" '{text: $r.text[0:50], department: .department.choice,
|
|
188
|
+
confidence: .department.confidence, urgent: .urgent.noul, personal_data: .personal_data.noul}'
|
|
189
|
+
done > answers.jsonl
|
|
190
|
+
duckdb -c "select * from 'answers.jsonl' order by urgent desc limit 6"
|
|
191
|
+
```
|
|
192
|
+
The `</dev/null` matters: without it `llm` reads the rest of the loop input from stdin and adds it to the first prompt.
|
|
193
|
+
|
|
194
|
+
| text | department | confidence | urgent | personal_data |
|
|
195
|
+
| - | - | - | - | - |
|
|
196
|
+
| Huge pothole in the road outside number 12, a scoo | public works | 1.0 | 0.95 | 0.02 |
|
|
197
|
+
| A tree is about to fall after yesterday's storm, a | public works | 1.0 | 0.94 | 0.02 |
|
|
198
|
+
| The pedestrian traffic light outside the hospital | public works | 1.0 | 0.89 | 0.02 |
|
|
199
|
+
| Strong smell of gas in the street next to the elem | public works | 0.47 | 0.88 | 0.02 |
|
|
200
|
+
| The street light on the corner has been out for th | public works | 1.0 | 0.61 | 0.02 |
|
|
201
|
+
| Recycling bins have been full for days, there is a | environment | 0.99 | 0.56 | 0.02 |
|
|
202
|
+
|
|
203
|
+
All 16 rows, three questions each, cost $0.00033. The report with a social security number got `personal_data` 0.99, every other row 0.04 or less. Note the gas leak: it is urgent, but the department confidence is only 0.47, because none of the categories is a good fit for a utility. Sorting by confidence is a cheap way to find the rows a person should look at.
|
|
204
|
+
|
|
205
|
+
You can save a question as an [LLM template](https://llm.datasette.io/en/stable/templates.html) and reuse it:
|
|
206
|
+
```bash
|
|
207
|
+
llm -m jev14 -s 'The report describes an immediate danger to people'"'"'s safety.' --save urgent
|
|
208
|
+
cat report.txt | llm -t urgent
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
## Large batches from Python
|
|
212
|
+
|
|
213
|
+
Each `llm` call in a shell loop starts a new process, about four seconds per row in the loop above. For thousands of rows use the async Python API, which sends the requests concurrently:
|
|
214
|
+
```python
|
|
215
|
+
import asyncio, csv, json
|
|
216
|
+
import llm
|
|
217
|
+
|
|
218
|
+
model = llm.get_async_model("openrouter-decisions/typesafe/jev-1.14")
|
|
219
|
+
QUESTION = "The publication is about the quality of open government data."
|
|
220
|
+
|
|
221
|
+
async def ask(title, sem):
|
|
222
|
+
async with sem:
|
|
223
|
+
response = model.prompt(title, system=QUESTION)
|
|
224
|
+
return title, json.loads(await response.text())["noul"]
|
|
225
|
+
|
|
226
|
+
async def main():
|
|
227
|
+
titles = [row["title"] for row in csv.DictReader(open("publications.csv"))]
|
|
228
|
+
sem = asyncio.Semaphore(8)
|
|
229
|
+
for title, p in await asyncio.gather(*(ask(t, sem) for t in titles)):
|
|
230
|
+
print(f"{p:.2f} {title}")
|
|
231
|
+
|
|
232
|
+
asyncio.run(main())
|
|
233
|
+
```
|
|
234
|
+
On 145 publication titles from [OpenAlex](https://openalex.org/) this took 5.4 seconds.
|
|
235
|
+
|
|
236
|
+
## Things to know
|
|
237
|
+
|
|
238
|
+
- **Single-turn only.** These models cannot continue a conversation: `llm -c` and `llm chat` are rejected.
|
|
239
|
+
- **The answer is a number, not an explanation.** If you need a reason, ask a chat model afterwards, or send the low confidence rows to a person.
|
|
240
|
+
- **Confidence is not calibrated.** Use it to rank answers, and pick thresholds on a few hundred labelled examples for each model and question. Probabilities differ between models for the same question.
|
|
241
|
+
- **Free models.** The `:free` models are excluded when your [OpenRouter privacy settings](https://openrouter.ai/settings/privacy) do not allow providers to train on your data. The request fails with HTTP 404 and `Free model training violation`.
|
|
242
|
+
- **Rolling aliases.** IDs that start with `~`, such as `~typesafe/jev-latest`, follow the newest release, so answers can change over time. Use a pinned ID for repeatable results.
|
|
243
|
+
- **Model-specific shapes.** Box questions are Jev 1.14 only. ScaleDown Classify-1 and Respan Span-01 expect their own state shapes, and only Cloudflare Clef Omni reads audio and video, which this plugin does not send yet.
|
|
244
|
+
- **Costs.** The full API response, including `usage.cost` in dollars, is stored in the LLM logs: `llm logs --json`.
|
|
245
|
+
|
|
246
|
+
## Further reading
|
|
247
|
+
|
|
248
|
+
- [Jev introduces a new shape of LLM](https://simonwillison.net/2026/Sep/21/jev/), Simon Willison: what decision models are, and why evals and bias checks matter even more than with chat models.
|
|
249
|
+
- [Small decisions at scale](https://attentionmarkets.substack.com/p/small-decisions-at-scale), Nick Hagar: four newsroom experiments, including scoring every Federal Register document of the last 24 hours in a couple of seconds, for under 10 cents in total.
|
|
250
|
+
- [Can Jev Parse CSV?](https://remy.wang/blog/jev-csv.html), Remy Wang: repairing broken CSV files with one yes/no question per comma, 12,044 decisions for 8 cents.
|
|
251
|
+
- [24 Ways to Use Jev](https://thorstenmeyerai.com/reality-check/24-ways-to-use-jev-a-decision-model-playbook/), Thorsten Meyer: a language check on 78,889 articles for $2.01, and how to test a model on past decisions before using it.
|
|
252
|
+
- [Can You Trust Jev's Confidence?](https://anth.us/articles/can-you-trust-jev-confidence/), Ryan Porter: confidence ranks right and wrong answers well but overstates them by 7 to 15 points; a small isotonic calibration fixes most of it.
|
|
253
|
+
- [Typed Decision Models on Tabular Data](https://arxiv.org/html/2610.19536), Zhang, Ye and Zha: at least a third of the decisions on table rows change when rows or columns are reordered.
|
|
254
|
+
- [Microsoft Decision-1 vs Every Decision Model on OpenRouter](https://dev.to/akartit/microsoft-decision-1-vs-every-decision-model-on-openrouter-6th-of-14-42l1), akartit: 14 models on 82 labelled questions, for $0.0195 in total.
|
|
255
|
+
- [Jev 1.14 vs OpenCV YOLOX](https://quirkyagents.com/wizard/blog/jev-images-bounding-boxes-vs-yolox/): image questions and boxes compared with a classic object detector.
|
|
256
|
+
|
|
257
|
+
## Development
|
|
258
|
+
|
|
259
|
+
To set up this plugin locally, first checkout the code. Then run the tests with `uv`:
|
|
260
|
+
```bash
|
|
261
|
+
cd llm-openrouter-decisions
|
|
262
|
+
uv run pytest
|
|
263
|
+
```
|
|
264
|
+
To run LLM with your in-development plugin:
|
|
265
|
+
```bash
|
|
266
|
+
uv run llm --help
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
## Credits
|
|
270
|
+
|
|
271
|
+
The request validation, response handling and test structure are derived from [llm-typesafe](https://github.com/simonw/llm-typesafe) by Simon Willison, released under the Apache License 2.0. The project skeleton comes from the [llm-plugin](https://github.com/simonw/llm-plugin) cookiecutter template.
|