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.
@@ -0,0 +1,17 @@
1
+ .venv
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ venv
6
+ .eggs
7
+ .pytest_cache
8
+ *.egg-info
9
+ .DS_Store
10
+ .vscode
11
+ dist
12
+ build
13
+ uv.lock
14
+ tasks/
15
+ tmp/
16
+ examples/answers.jsonl
17
+ .claude/
@@ -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
+ [![PyPI](https://img.shields.io/pypi/v/llm-openrouter-decisions.svg)](https://pypi.org/project/llm-openrouter-decisions/)
21
+ [![Changelog](https://img.shields.io/github/v/release/aborruso/llm-openrouter-decisions?include_prereleases&label=changelog)](https://github.com/aborruso/llm-openrouter-decisions/releases)
22
+ [![Tests](https://github.com/aborruso/llm-openrouter-decisions/actions/workflows/test.yml/badge.svg)](https://github.com/aborruso/llm-openrouter-decisions/actions/workflows/test.yml)
23
+ [![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](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
+ ![A population pyramid of Madrid by age and sex, with a legend in the top right corner](https://raw.githubusercontent.com/aborruso/llm-openrouter-decisions/main/examples/chart.png)
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
+ ![The same chart with a red rectangle over the left half of the legend](https://raw.githubusercontent.com/aborruso/llm-openrouter-decisions/main/examples/chart-box.png)
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.