jevkit 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.
jevkit-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 HCTDIP
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.
jevkit-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,147 @@
1
+ Metadata-Version: 2.4
2
+ Name: jevkit
3
+ Version: 0.1.0
4
+ Summary: First open-source third-party Python client for OpenRouter's Decisions API (Jev, by TypeSafe) - calibrated-probability decision model, not a chat model.
5
+ Author: jevkit contributors
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/block/buzz
8
+ Keywords: jev,openrouter,decisions,calibration,agent,noul,gating
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
14
+ Requires-Python: >=3.9
15
+ Description-Content-Type: text/markdown
16
+ License-File: LICENSE
17
+ Dynamic: license-file
18
+
19
+ # jevkit — Python client for the Jev decision model
20
+
21
+ > ⚠️ **Disclaimer**: This is a third-party, unofficial wrapper around OpenRouter's
22
+ > Decisions API — not affiliated with, endorsed by, or maintained by OpenRouter or
23
+ > TypeSafe. The API is in alpha and may change without notice.
24
+
25
+ The first open-source third-party client for OpenRouter's **Decisions API**
26
+ (Jev, by TypeSafe) — a calibrated-probability decision model, not a chat model.
27
+
28
+ > Jev answers typed questions with calibrated probabilities — zero text generation.
29
+ > Three primitives: **noul** (yes/no calibrated probability), **choice** (pick one),
30
+ > **score** (scale rating). 70-500ms latency, output tokens free.
31
+
32
+ ## Why
33
+
34
+ Chat models guess; Jev **calibrates**. If you need routing, classification, or
35
+ gating decisions ("is this worth doing?"), a chat model wrapped in JSON is
36
+ slower, uncalibrated, and costs more. Jev is the ultimate classifier:
37
+
38
+ - **Calibrated probabilities** — real confidence, not vibes
39
+ - **70-500ms** — System-1 speed for gating decisions
40
+ - **$0.042/M input, output free** — 14 questions ≈ $0.00004
41
+ - **Typed questions** — noul / choice / score, with explicit criteria
42
+
43
+ ## Install
44
+
45
+ ```bash
46
+ pip install jevkit # (after publish — for now: copy the jevkit/ directory)
47
+ export OPENROUTER_API_KEY=<your key> # free from openrouter.ai
48
+ ```
49
+
50
+ ## Quickstart
51
+
52
+ ```python
53
+ from jevkit import Client, gate
54
+
55
+ client = Client() # reads OPENROUTER_API_KEY
56
+
57
+ # noul — yes/no calibrated probability
58
+ p = client.noul(
59
+ name="worth_outreach",
60
+ instructions="Is this lead worth cold outreach?",
61
+ criteria={
62
+ "false": "No budget, or the poster is promoting themselves.",
63
+ "true": "Explicit budget + concrete need, reachable for a pitch.",
64
+ },
65
+ state="HN post: [Hiring] N8n automation expert, $2k budget, urgent",
66
+ )
67
+ # → 0.63
68
+
69
+ # gate — confidence-gated decision (act / confirm / escalate)
70
+ action = gate(p) # confirm (borderline — flips, don't auto-send)
71
+
72
+ # choice — pick one from options
73
+ r = client.decide({
74
+ "category": {
75
+ "type": "choice",
76
+ "instructions": "What does the customer need?",
77
+ "options": ["coding", "design", "content", "infra"],
78
+ }
79
+ }, state="I need someone to build a checkout flow")
80
+ # → {"choice": "coding", "confidence": 0.8, ...}
81
+ ```
82
+
83
+
84
+ # score — scale rating
85
+ r = client.decide({
86
+ "urgency": {
87
+ "type": "score",
88
+ "instructions": "How urgent is this?",
89
+ "legend": {
90
+ "0": "Can wait",
91
+ "1": "This week",
92
+ "2": "Blocking revenue now",
93
+ },
94
+ }
95
+ }, state="Payments are failing for customers right now")
96
+ # → {"confidence": 0.99, "legend": {...}, ...}
97
+
98
+ ## CLI
99
+
100
+ ```bash
101
+ jevkit noul "worth outreach?" --state "lead: ..." # → 0.61
102
+ jevkit gate 0.63 # → confirm
103
+ jevkit decide questions.json --state "..." # → full answers
104
+ ```
105
+
106
+ ## Gotchas (learned the hard way)
107
+
108
+ 1. `questions` expects a **record** (`{name: {...}}`), not an array
109
+ 2. noul's `criteria` is `{"true": str, "false": str}` — descriptions, not options
110
+ 3. The response is `answers.{name}.noul` — not a top-level probability
111
+ 4. **Borderline probabilities flip** — never auto-act on 0.5-0.7, gate them
112
+ 5. noul(false) + noul(true) don't have to sum to 1
113
+
114
+ ## The gate pattern
115
+
116
+ ```python
117
+ from jevkit import gate
118
+
119
+ p = client.noul(...) # System 1: calibrated fast decision
120
+ action = gate(p) # act(>=0.7) / confirm(0.5-0.7) / escalate(<0.5)
121
+ if action == "act": # only auto-act on high confidence
122
+ do_the_thing()
123
+ elif action == "confirm":
124
+ queue_for_review() # borderline → human or System 2 (LLM review)
125
+ ```
126
+
127
+
128
+ Default thresholds: `act >= 0.7`, `confirm >= 0.5` — tuned for the "borderline
129
+ flips" property (never auto-act in 0.5-0.7). Override per use case:
130
+
131
+ ```python
132
+ gate(p, act=0.8, confirm=0.6) # stricter gate
133
+ ```
134
+
135
+ System 1 (Jev) decides **who/what to act on**; System 2 (a chat model) handles
136
+ **the acting itself** (writing, reasoning). This division keeps costs low and
137
+ decisions calibrated.
138
+
139
+ ## Status
140
+
141
+ - ✅ Client + CLI + gate pattern (validated with real API calls, R3 read-back)
142
+ - ✅ Error handling: RuntimeError on HTTP errors (fallback-friendly)
143
+ - 🚧 Tests + CI (in progress)
144
+
145
+ ## License
146
+
147
+ MIT
jevkit-0.1.0/README.md ADDED
@@ -0,0 +1,129 @@
1
+ # jevkit — Python client for the Jev decision model
2
+
3
+ > ⚠️ **Disclaimer**: This is a third-party, unofficial wrapper around OpenRouter's
4
+ > Decisions API — not affiliated with, endorsed by, or maintained by OpenRouter or
5
+ > TypeSafe. The API is in alpha and may change without notice.
6
+
7
+ The first open-source third-party client for OpenRouter's **Decisions API**
8
+ (Jev, by TypeSafe) — a calibrated-probability decision model, not a chat model.
9
+
10
+ > Jev answers typed questions with calibrated probabilities — zero text generation.
11
+ > Three primitives: **noul** (yes/no calibrated probability), **choice** (pick one),
12
+ > **score** (scale rating). 70-500ms latency, output tokens free.
13
+
14
+ ## Why
15
+
16
+ Chat models guess; Jev **calibrates**. If you need routing, classification, or
17
+ gating decisions ("is this worth doing?"), a chat model wrapped in JSON is
18
+ slower, uncalibrated, and costs more. Jev is the ultimate classifier:
19
+
20
+ - **Calibrated probabilities** — real confidence, not vibes
21
+ - **70-500ms** — System-1 speed for gating decisions
22
+ - **$0.042/M input, output free** — 14 questions ≈ $0.00004
23
+ - **Typed questions** — noul / choice / score, with explicit criteria
24
+
25
+ ## Install
26
+
27
+ ```bash
28
+ pip install jevkit # (after publish — for now: copy the jevkit/ directory)
29
+ export OPENROUTER_API_KEY=<your key> # free from openrouter.ai
30
+ ```
31
+
32
+ ## Quickstart
33
+
34
+ ```python
35
+ from jevkit import Client, gate
36
+
37
+ client = Client() # reads OPENROUTER_API_KEY
38
+
39
+ # noul — yes/no calibrated probability
40
+ p = client.noul(
41
+ name="worth_outreach",
42
+ instructions="Is this lead worth cold outreach?",
43
+ criteria={
44
+ "false": "No budget, or the poster is promoting themselves.",
45
+ "true": "Explicit budget + concrete need, reachable for a pitch.",
46
+ },
47
+ state="HN post: [Hiring] N8n automation expert, $2k budget, urgent",
48
+ )
49
+ # → 0.63
50
+
51
+ # gate — confidence-gated decision (act / confirm / escalate)
52
+ action = gate(p) # confirm (borderline — flips, don't auto-send)
53
+
54
+ # choice — pick one from options
55
+ r = client.decide({
56
+ "category": {
57
+ "type": "choice",
58
+ "instructions": "What does the customer need?",
59
+ "options": ["coding", "design", "content", "infra"],
60
+ }
61
+ }, state="I need someone to build a checkout flow")
62
+ # → {"choice": "coding", "confidence": 0.8, ...}
63
+ ```
64
+
65
+
66
+ # score — scale rating
67
+ r = client.decide({
68
+ "urgency": {
69
+ "type": "score",
70
+ "instructions": "How urgent is this?",
71
+ "legend": {
72
+ "0": "Can wait",
73
+ "1": "This week",
74
+ "2": "Blocking revenue now",
75
+ },
76
+ }
77
+ }, state="Payments are failing for customers right now")
78
+ # → {"confidence": 0.99, "legend": {...}, ...}
79
+
80
+ ## CLI
81
+
82
+ ```bash
83
+ jevkit noul "worth outreach?" --state "lead: ..." # → 0.61
84
+ jevkit gate 0.63 # → confirm
85
+ jevkit decide questions.json --state "..." # → full answers
86
+ ```
87
+
88
+ ## Gotchas (learned the hard way)
89
+
90
+ 1. `questions` expects a **record** (`{name: {...}}`), not an array
91
+ 2. noul's `criteria` is `{"true": str, "false": str}` — descriptions, not options
92
+ 3. The response is `answers.{name}.noul` — not a top-level probability
93
+ 4. **Borderline probabilities flip** — never auto-act on 0.5-0.7, gate them
94
+ 5. noul(false) + noul(true) don't have to sum to 1
95
+
96
+ ## The gate pattern
97
+
98
+ ```python
99
+ from jevkit import gate
100
+
101
+ p = client.noul(...) # System 1: calibrated fast decision
102
+ action = gate(p) # act(>=0.7) / confirm(0.5-0.7) / escalate(<0.5)
103
+ if action == "act": # only auto-act on high confidence
104
+ do_the_thing()
105
+ elif action == "confirm":
106
+ queue_for_review() # borderline → human or System 2 (LLM review)
107
+ ```
108
+
109
+
110
+ Default thresholds: `act >= 0.7`, `confirm >= 0.5` — tuned for the "borderline
111
+ flips" property (never auto-act in 0.5-0.7). Override per use case:
112
+
113
+ ```python
114
+ gate(p, act=0.8, confirm=0.6) # stricter gate
115
+ ```
116
+
117
+ System 1 (Jev) decides **who/what to act on**; System 2 (a chat model) handles
118
+ **the acting itself** (writing, reasoning). This division keeps costs low and
119
+ decisions calibrated.
120
+
121
+ ## Status
122
+
123
+ - ✅ Client + CLI + gate pattern (validated with real API calls, R3 read-back)
124
+ - ✅ Error handling: RuntimeError on HTTP errors (fallback-friendly)
125
+ - 🚧 Tests + CI (in progress)
126
+
127
+ ## License
128
+
129
+ MIT
@@ -0,0 +1,5 @@
1
+ """jevkit package — Jev decision model client."""
2
+ from .client import Client, Answer, gate
3
+
4
+ __all__ = ['Client', 'Answer', 'gate']
5
+ __version__ = '0.1.0'
@@ -0,0 +1,60 @@
1
+ #!/usr/bin/env python3
2
+ """jevkit CLI — 命令行入口。
3
+
4
+ 用法:
5
+ jevkit noul "worth outreach?" --state "lead: ..."
6
+ jevkit gate 0.63
7
+ jevkit decide questions.json --state "..."
8
+ """
9
+ import argparse
10
+ import json
11
+ import sys
12
+
13
+ from .client import Client, gate
14
+
15
+
16
+ def main():
17
+ ap = argparse.ArgumentParser(prog='jevkit',
18
+ description='Jev decision model CLI (calibrated probabilities, not chat)')
19
+ sub = ap.add_subparsers(dest='cmd')
20
+
21
+ # noul
22
+ p_noul = sub.add_parser('noul', help='yes/no calibrated probability')
23
+ p_noul.add_argument('question', help='是/否问题')
24
+ p_noul.add_argument('--state', default='', help='上下文')
25
+ p_noul.add_argument('--name', default='q', help='question name')
26
+ p_noul.add_argument('--true-desc', default='The condition described holds.', help='true criteria')
27
+ p_noul.add_argument('--false-desc', default='The condition described does not hold.', help='false criteria')
28
+
29
+ # gate
30
+ p_gate = sub.add_parser('gate', help='act/confirm/escalate decision gate')
31
+ p_gate.add_argument('p', type=float, help='probability 0.0-1.0')
32
+ p_gate.add_argument('--act', type=float, default=0.7)
33
+ p_gate.add_argument('--confirm', type=float, default=0.5)
34
+
35
+ # decide
36
+ p_dec = sub.add_parser('decide', help='full decisions call (questions record JSON)')
37
+ p_dec.add_argument('questions', help='questions record JSON 文件路径')
38
+ p_dec.add_argument('--state', default='', help='上下文')
39
+
40
+ args = ap.parse_args()
41
+
42
+ if args.cmd == 'noul':
43
+ client = Client()
44
+ criteria = {'true': args.true_desc, 'false': args.false_desc}
45
+ p = client.noul(args.name, args.question, criteria, state=args.state)
46
+ action = gate(p)
47
+ print(f'{p:.2f} → {action}')
48
+ elif args.cmd == 'gate':
49
+ print(gate(args.p, act=args.act, confirm=args.confirm))
50
+ elif args.cmd == 'decide':
51
+ client = Client()
52
+ questions = json.loads(open(args.questions).read())
53
+ r = client.decide(questions, state=args.state)
54
+ print(json.dumps(r.get('answers', r), ensure_ascii=False, indent=2))
55
+ else:
56
+ ap.print_help()
57
+
58
+
59
+ if __name__ == '__main__':
60
+ main()
@@ -0,0 +1,88 @@
1
+ """jevkit — Python client for the Jev decision model (OpenRouter Decisions API)."""
2
+ import json
3
+ import os
4
+ import urllib.request
5
+ import urllib.error
6
+ from dataclasses import dataclass
7
+
8
+ API = "https://openrouter.ai/api/alpha/decisions"
9
+ DEFAULT_MODEL = "typesafe/jev-1.13"
10
+
11
+
12
+ @dataclass
13
+ class Answer:
14
+ """单个问题的回答(校准结果)。"""
15
+ name: str
16
+ type: str # noul / choice / score
17
+ noul: float = None # noul 概率
18
+ choice: str = None # choice 选中项
19
+ confidence: float = None # choice/score 置信
20
+ probabilities: dict = None # choice 分布
21
+
22
+
23
+ class Client:
24
+ """Jev Decisions API client。
25
+
26
+ 不是聊天模型:state + typed questions → 校准概率,零文本生成。
27
+ """
28
+
29
+ def __init__(self, api_key: str = None, model: str = DEFAULT_MODEL):
30
+ self.api_key = api_key or os.environ.get("OPENROUTER_API_KEY") or os.environ.get("JEV_API_KEY")
31
+ self.model = model
32
+
33
+ def decide(self, questions: dict, state: str = "", timeout: int = 30) -> dict:
34
+ """单次 decisions 调用。questions 是 record: {name: {type, instructions, ...}}。"""
35
+ if not self.api_key:
36
+ raise RuntimeError("OPENROUTER_API_KEY not set.")
37
+ payload = {"model": self.model, "state": state, "questions": questions}
38
+ req = urllib.request.Request(
39
+ API,
40
+ data=json.dumps(payload).encode("utf-8"),
41
+ headers={
42
+ "Authorization": f"Bearer {self.api_key}",
43
+ "Content-Type": "application/json",
44
+ },
45
+ )
46
+ try:
47
+ with urllib.request.urlopen(req, timeout=timeout) as r:
48
+ return json.loads(r.read())
49
+ except urllib.error.HTTPError as e:
50
+ err = e.read().decode("utf-8", errors="ignore")
51
+ # RuntimeError — except Exception 能接住(fallback 友好)
52
+ raise RuntimeError(f"Jev HTTP {e.code}: {err[:300]}")
53
+
54
+ def noul(self, name: str, instructions: str, criteria: dict,
55
+ state: str = "", timeout: int = 30) -> float:
56
+ """是/否校准概率。criteria: {"true": str, "false": str}(描述,不是 options)。"""
57
+ r = self.decide(
58
+ {name: {"type": "noul", "instructions": instructions, "criteria": criteria}},
59
+ state=state, timeout=timeout,
60
+ )
61
+ a = r.get("answers", {}).get(name, {})
62
+ if "noul" in a:
63
+ return float(a["noul"])
64
+ raise RuntimeError(f"Jev 响应无 noul: {json.dumps(r)[:200]}")
65
+
66
+ def choice(self, name: str, instructions: str, options: list,
67
+ state: str = "", timeout: int = 30) -> Answer:
68
+ """挑一。"""
69
+ r = self.decide(
70
+ {name: {"type": "choice", "instructions": instructions, "options": options}},
71
+ state=state, timeout=timeout,
72
+ )
73
+ a = r.get("answers", {}).get(name, {})
74
+ return Answer(name=name, type="choice", choice=a.get("choice"),
75
+ confidence=a.get("confidence"), probabilities=a.get("probabilities"))
76
+
77
+
78
+ def gate(p: float, act: float = 0.7, confirm: float = 0.5) -> str:
79
+ """confidence-gated act/confirm/escalate。
80
+
81
+ act(>=0.7) → 直接执行;confirm(0.5-0.7) → 待审(borderline 会翻转,不自动执行);
82
+ escalate(<0.5) → 升级/跳过。
83
+ """
84
+ if p >= act:
85
+ return "act"
86
+ if p >= confirm:
87
+ return "confirm"
88
+ return "escalate"
@@ -0,0 +1,72 @@
1
+ #!/usr/bin/env python3
2
+ """test_jevkit.py — jevkit 测试(需要 OPENROUTER_API_KEY 才跑真调用)。
3
+
4
+ 用法:
5
+ python3 -m jevkit.test_jevkit # 真调用测试
6
+ """
7
+ import os
8
+ import unittest
9
+
10
+
11
+ class TestGate(unittest.TestCase):
12
+ """gate 决策门(不需要 key)。"""
13
+
14
+ def test_act(self):
15
+ from jevkit import gate
16
+ self.assertEqual(gate(0.8), 'act')
17
+ self.assertEqual(gate(0.7), 'act')
18
+
19
+ def test_confirm(self):
20
+ from jevkit import gate
21
+ self.assertEqual(gate(0.6), 'confirm')
22
+ self.assertEqual(gate(0.5), 'confirm')
23
+
24
+ def test_escalate(self):
25
+ from jevkit import gate
26
+ self.assertEqual(gate(0.4), 'escalate')
27
+ self.assertEqual(gate(0.0), 'escalate')
28
+
29
+ def test_borderline_flips(self):
30
+ """borderline 会翻转 — confirm 档不自动执行(坑 2)。"""
31
+ from jevkit import gate
32
+ # 0.5-0.7 区间永远 confirm,不会 act
33
+ for p in (0.5, 0.55, 0.6, 0.65, 0.69):
34
+ self.assertEqual(gate(p), 'confirm', f'{p} 应该是 confirm(不自动执行)')
35
+
36
+
37
+ class TestClient(unittest.TestCase):
38
+ """Client(需要 key — 真调用)。"""
39
+
40
+ def setUp(self):
41
+ if not os.environ.get('OPENROUTER_API_KEY'):
42
+ self.skipTest('OPENROUTER_API_KEY not set')
43
+
44
+ def test_noul_real(self):
45
+ from jevkit import Client
46
+ client = Client()
47
+ p = client.noul(
48
+ 'worth_outreach',
49
+ 'Is this lead worth cold outreach?',
50
+ {'true': 'Explicit budget + concrete need.',
51
+ 'false': 'No budget or self-promotion.'},
52
+ state='HN post: [Hiring] N8n automation expert, $2k budget, urgent',
53
+ )
54
+ self.assertIsInstance(p, float)
55
+ self.assertGreaterEqual(p, 0.0)
56
+ self.assertLessEqual(p, 1.0)
57
+
58
+ def test_no_key_error(self):
59
+ """无 key 时 RuntimeError(fallback 友好 — except Exception 能接住)。"""
60
+ from jevkit import Client
61
+ saved = os.environ.pop('OPENROUTER_API_KEY', None)
62
+ try:
63
+ client = Client(api_key='')
64
+ with self.assertRaises(RuntimeError):
65
+ client.noul('q', 'i', {'true': 't', 'false': 'f'}, state='s')
66
+ finally:
67
+ if saved:
68
+ os.environ['OPENROUTER_API_KEY'] = saved
69
+
70
+
71
+ if __name__ == '__main__':
72
+ unittest.main(verbosity=2)
@@ -0,0 +1,147 @@
1
+ Metadata-Version: 2.4
2
+ Name: jevkit
3
+ Version: 0.1.0
4
+ Summary: First open-source third-party Python client for OpenRouter's Decisions API (Jev, by TypeSafe) - calibrated-probability decision model, not a chat model.
5
+ Author: jevkit contributors
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/block/buzz
8
+ Keywords: jev,openrouter,decisions,calibration,agent,noul,gating
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
14
+ Requires-Python: >=3.9
15
+ Description-Content-Type: text/markdown
16
+ License-File: LICENSE
17
+ Dynamic: license-file
18
+
19
+ # jevkit — Python client for the Jev decision model
20
+
21
+ > ⚠️ **Disclaimer**: This is a third-party, unofficial wrapper around OpenRouter's
22
+ > Decisions API — not affiliated with, endorsed by, or maintained by OpenRouter or
23
+ > TypeSafe. The API is in alpha and may change without notice.
24
+
25
+ The first open-source third-party client for OpenRouter's **Decisions API**
26
+ (Jev, by TypeSafe) — a calibrated-probability decision model, not a chat model.
27
+
28
+ > Jev answers typed questions with calibrated probabilities — zero text generation.
29
+ > Three primitives: **noul** (yes/no calibrated probability), **choice** (pick one),
30
+ > **score** (scale rating). 70-500ms latency, output tokens free.
31
+
32
+ ## Why
33
+
34
+ Chat models guess; Jev **calibrates**. If you need routing, classification, or
35
+ gating decisions ("is this worth doing?"), a chat model wrapped in JSON is
36
+ slower, uncalibrated, and costs more. Jev is the ultimate classifier:
37
+
38
+ - **Calibrated probabilities** — real confidence, not vibes
39
+ - **70-500ms** — System-1 speed for gating decisions
40
+ - **$0.042/M input, output free** — 14 questions ≈ $0.00004
41
+ - **Typed questions** — noul / choice / score, with explicit criteria
42
+
43
+ ## Install
44
+
45
+ ```bash
46
+ pip install jevkit # (after publish — for now: copy the jevkit/ directory)
47
+ export OPENROUTER_API_KEY=<your key> # free from openrouter.ai
48
+ ```
49
+
50
+ ## Quickstart
51
+
52
+ ```python
53
+ from jevkit import Client, gate
54
+
55
+ client = Client() # reads OPENROUTER_API_KEY
56
+
57
+ # noul — yes/no calibrated probability
58
+ p = client.noul(
59
+ name="worth_outreach",
60
+ instructions="Is this lead worth cold outreach?",
61
+ criteria={
62
+ "false": "No budget, or the poster is promoting themselves.",
63
+ "true": "Explicit budget + concrete need, reachable for a pitch.",
64
+ },
65
+ state="HN post: [Hiring] N8n automation expert, $2k budget, urgent",
66
+ )
67
+ # → 0.63
68
+
69
+ # gate — confidence-gated decision (act / confirm / escalate)
70
+ action = gate(p) # confirm (borderline — flips, don't auto-send)
71
+
72
+ # choice — pick one from options
73
+ r = client.decide({
74
+ "category": {
75
+ "type": "choice",
76
+ "instructions": "What does the customer need?",
77
+ "options": ["coding", "design", "content", "infra"],
78
+ }
79
+ }, state="I need someone to build a checkout flow")
80
+ # → {"choice": "coding", "confidence": 0.8, ...}
81
+ ```
82
+
83
+
84
+ # score — scale rating
85
+ r = client.decide({
86
+ "urgency": {
87
+ "type": "score",
88
+ "instructions": "How urgent is this?",
89
+ "legend": {
90
+ "0": "Can wait",
91
+ "1": "This week",
92
+ "2": "Blocking revenue now",
93
+ },
94
+ }
95
+ }, state="Payments are failing for customers right now")
96
+ # → {"confidence": 0.99, "legend": {...}, ...}
97
+
98
+ ## CLI
99
+
100
+ ```bash
101
+ jevkit noul "worth outreach?" --state "lead: ..." # → 0.61
102
+ jevkit gate 0.63 # → confirm
103
+ jevkit decide questions.json --state "..." # → full answers
104
+ ```
105
+
106
+ ## Gotchas (learned the hard way)
107
+
108
+ 1. `questions` expects a **record** (`{name: {...}}`), not an array
109
+ 2. noul's `criteria` is `{"true": str, "false": str}` — descriptions, not options
110
+ 3. The response is `answers.{name}.noul` — not a top-level probability
111
+ 4. **Borderline probabilities flip** — never auto-act on 0.5-0.7, gate them
112
+ 5. noul(false) + noul(true) don't have to sum to 1
113
+
114
+ ## The gate pattern
115
+
116
+ ```python
117
+ from jevkit import gate
118
+
119
+ p = client.noul(...) # System 1: calibrated fast decision
120
+ action = gate(p) # act(>=0.7) / confirm(0.5-0.7) / escalate(<0.5)
121
+ if action == "act": # only auto-act on high confidence
122
+ do_the_thing()
123
+ elif action == "confirm":
124
+ queue_for_review() # borderline → human or System 2 (LLM review)
125
+ ```
126
+
127
+
128
+ Default thresholds: `act >= 0.7`, `confirm >= 0.5` — tuned for the "borderline
129
+ flips" property (never auto-act in 0.5-0.7). Override per use case:
130
+
131
+ ```python
132
+ gate(p, act=0.8, confirm=0.6) # stricter gate
133
+ ```
134
+
135
+ System 1 (Jev) decides **who/what to act on**; System 2 (a chat model) handles
136
+ **the acting itself** (writing, reasoning). This division keeps costs low and
137
+ decisions calibrated.
138
+
139
+ ## Status
140
+
141
+ - ✅ Client + CLI + gate pattern (validated with real API calls, R3 read-back)
142
+ - ✅ Error handling: RuntimeError on HTTP errors (fallback-friendly)
143
+ - 🚧 Tests + CI (in progress)
144
+
145
+ ## License
146
+
147
+ MIT
@@ -0,0 +1,12 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ jevkit/__init__.py
5
+ jevkit/cli.py
6
+ jevkit/client.py
7
+ jevkit/test_jevkit.py
8
+ jevkit.egg-info/PKG-INFO
9
+ jevkit.egg-info/SOURCES.txt
10
+ jevkit.egg-info/dependency_links.txt
11
+ jevkit.egg-info/entry_points.txt
12
+ jevkit.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ jevkit = jevkit.cli:main
@@ -0,0 +1 @@
1
+ jevkit
@@ -0,0 +1,29 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "jevkit"
7
+ version = "0.1.0"
8
+ description = "First open-source third-party Python client for OpenRouter's Decisions API (Jev, by TypeSafe) - calibrated-probability decision model, not a chat model."
9
+ readme = "README.md"
10
+ requires-python = ">=3.9"
11
+ license = { text = "MIT" }
12
+ keywords = ["jev", "openrouter", "decisions", "calibration", "agent", "noul", "gating"]
13
+ authors = [{ name = "jevkit contributors" }]
14
+ classifiers = [
15
+ "Development Status :: 4 - Beta",
16
+ "Intended Audience :: Developers",
17
+ "License :: OSI Approved :: MIT License",
18
+ "Programming Language :: Python :: 3",
19
+ "Topic :: Software Development :: Libraries :: Python Modules",
20
+ ]
21
+
22
+ [project.urls]
23
+ Homepage = "https://github.com/block/buzz"
24
+
25
+ [tool.setuptools.packages.find]
26
+ include = ["jevkit*"]
27
+
28
+ [project.scripts]
29
+ jevkit = "jevkit.cli:main"
jevkit-0.1.0/setup.cfg ADDED
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+