open-cricket 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.
Files changed (52) hide show
  1. open_cricket-0.1.0/LICENSE +21 -0
  2. open_cricket-0.1.0/MANIFEST.in +4 -0
  3. open_cricket-0.1.0/PKG-INFO +353 -0
  4. open_cricket-0.1.0/README.md +322 -0
  5. open_cricket-0.1.0/docs/README.md +44 -0
  6. open_cricket-0.1.0/docs/api.md +155 -0
  7. open_cricket-0.1.0/docs/assets/open-cricket-icon.svg +11 -0
  8. open_cricket-0.1.0/docs/jev-migration.md +170 -0
  9. open_cricket-0.1.0/docs/renaming.md +28 -0
  10. open_cricket-0.1.0/docs/sdk.md +163 -0
  11. open_cricket-0.1.0/docs/server.md +98 -0
  12. open_cricket-0.1.0/docs/technical/README.md +16 -0
  13. open_cricket-0.1.0/docs/technical/architecture.md +95 -0
  14. open_cricket-0.1.0/docs/technical/backends.md +90 -0
  15. open_cricket-0.1.0/docs/technical/contributing.md +89 -0
  16. open_cricket-0.1.0/docs/technical/releases.md +64 -0
  17. open_cricket-0.1.0/docs/technical/scoring.md +99 -0
  18. open_cricket-0.1.0/examples/benchmark_backend.py +86 -0
  19. open_cricket-0.1.0/examples/demo-result.json +20 -0
  20. open_cricket-0.1.0/examples/langchain_chat.py +31 -0
  21. open_cricket-0.1.0/examples/langgraph_workflow.py +48 -0
  22. open_cricket-0.1.0/examples/support.json +24 -0
  23. open_cricket-0.1.0/open_cricket/__init__.py +6 -0
  24. open_cricket-0.1.0/open_cricket/backend.py +34 -0
  25. open_cricket-0.1.0/open_cricket/cli.py +132 -0
  26. open_cricket-0.1.0/open_cricket/client.py +38 -0
  27. open_cricket-0.1.0/open_cricket/core.py +122 -0
  28. open_cricket-0.1.0/open_cricket/hf.py +71 -0
  29. open_cricket-0.1.0/open_cricket/integrations.py +182 -0
  30. open_cricket-0.1.0/open_cricket/local.py +87 -0
  31. open_cricket-0.1.0/open_cricket/mlx.py +49 -0
  32. open_cricket-0.1.0/open_cricket/questionnaire.py +94 -0
  33. open_cricket-0.1.0/open_cricket/sdk.py +35 -0
  34. open_cricket-0.1.0/open_cricket/server.py +88 -0
  35. open_cricket-0.1.0/open_cricket/systemone.py +199 -0
  36. open_cricket-0.1.0/open_cricket.egg-info/PKG-INFO +353 -0
  37. open_cricket-0.1.0/open_cricket.egg-info/SOURCES.txt +50 -0
  38. open_cricket-0.1.0/open_cricket.egg-info/dependency_links.txt +1 -0
  39. open_cricket-0.1.0/open_cricket.egg-info/entry_points.txt +2 -0
  40. open_cricket-0.1.0/open_cricket.egg-info/requires.txt +29 -0
  41. open_cricket-0.1.0/open_cricket.egg-info/top_level.txt +1 -0
  42. open_cricket-0.1.0/pyproject.toml +27 -0
  43. open_cricket-0.1.0/setup.cfg +4 -0
  44. open_cricket-0.1.0/tests/test_cli.py +125 -0
  45. open_cricket-0.1.0/tests/test_client.py +35 -0
  46. open_cricket-0.1.0/tests/test_core.py +104 -0
  47. open_cricket-0.1.0/tests/test_integrations.py +122 -0
  48. open_cricket-0.1.0/tests/test_local.py +124 -0
  49. open_cricket-0.1.0/tests/test_sdk.py +99 -0
  50. open_cricket-0.1.0/tests/test_server.py +14 -0
  51. open_cricket-0.1.0/tests/test_systemone.py +235 -0
  52. open_cricket-0.1.0/uv.lock +2561 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Jonathan
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.
@@ -0,0 +1,4 @@
1
+ include LICENSE README.md uv.lock
2
+ recursive-include docs *.md *.svg
3
+ recursive-include examples *.py *.json
4
+ recursive-include tests *.py
@@ -0,0 +1,353 @@
1
+ Metadata-Version: 2.4
2
+ Name: open-cricket
3
+ Version: 0.1.0
4
+ Summary: Questionnaire classification using causal-LM token probabilities
5
+ License-Expression: MIT
6
+ Requires-Python: >=3.10
7
+ Description-Content-Type: text/markdown
8
+ License-File: LICENSE
9
+ Requires-Dist: pydantic<3,>=2.9
10
+ Provides-Extra: hf
11
+ Requires-Dist: torch<3,>=2.2; extra == "hf"
12
+ Requires-Dist: transformers<5,>=4.45; extra == "hf"
13
+ Provides-Extra: mlx
14
+ Requires-Dist: mlx-lm<0.29,>=0.28; (sys_platform == "darwin" and platform_machine == "arm64") and extra == "mlx"
15
+ Provides-Extra: langchain
16
+ Requires-Dist: langchain-core<2,>=1; extra == "langchain"
17
+ Provides-Extra: langgraph
18
+ Requires-Dist: langchain-core<2,>=1; extra == "langgraph"
19
+ Requires-Dist: langgraph<2,>=1; extra == "langgraph"
20
+ Provides-Extra: server
21
+ Requires-Dist: langchain-core<2,>=1; extra == "server"
22
+ Requires-Dist: fastapi<1,>=0.115; extra == "server"
23
+ Requires-Dist: uvicorn<1,>=0.30; extra == "server"
24
+ Provides-Extra: test
25
+ Requires-Dist: langchain-core<2,>=1; extra == "test"
26
+ Requires-Dist: langgraph<2,>=1; extra == "test"
27
+ Requires-Dist: fastapi<1,>=0.115; extra == "test"
28
+ Requires-Dist: httpx<1,>=0.27; extra == "test"
29
+ Requires-Dist: typesafe-sdk<0.8,>=0.7; extra == "test"
30
+ Dynamic: license-file
31
+
32
+ # Open Cricket
33
+
34
+ ![Open Cricket mascot](docs/assets/open-cricket-icon.svg)
35
+
36
+ Local structured decisions using causal language models. The CLI, Python SDK,
37
+ REST API, and LangChain integrations share one request contract:
38
+ **`state`, `model`, and a map of typed `questions`**. Responses contain `model`,
39
+ `answers`, and `usage`. Choice selects a category, Score evaluates a rubric,
40
+ and Noul returns the relative probability of a yes/true answer.
41
+
42
+ The default checkpoint is `Qwen/Qwen2.5-1.5B-Instruct`. Open Cricket is independent
43
+ of TypeSafe; its API follows Jev's general call shapes, but model predictions
44
+ and confidence calibration differ.
45
+
46
+ ## Documentation
47
+
48
+ See the [documentation index](docs/README.md) for the [server guide](docs/server.md),
49
+ [API reference](docs/api.md), and [Python SDK guide](docs/sdk.md).
50
+ The [technical section](docs/technical/README.md) covers architecture, scoring,
51
+ backend development, and testing for contributors.
52
+
53
+ The package and command are `open-cricket`; Python imports use `open_cricket`.
54
+ See the [rename guide](docs/renaming.md) for configuration changes and the
55
+ [release guide](docs/technical/releases.md) for GitHub-to-PyPI setup.
56
+
57
+ ## Quick start: local model, no server
58
+
59
+ ```bash
60
+ uv sync --extra hf
61
+ uv run open-cricket --input examples/support.json --pretty --time
62
+ ```
63
+
64
+ The same input file can be sent to the REST API or passed to `LocalClient.invoke`.
65
+ For example:
66
+
67
+ ```json
68
+ {
69
+ "state": "I was charged twice. Please refund the extra payment.",
70
+ "model": "Qwen/Qwen2.5-1.5B-Instruct",
71
+ "questions": {
72
+ "route": {
73
+ "type": "choice",
74
+ "instructions": "Which team should handle this?",
75
+ "criteria": {
76
+ "billing": "Payments, invoices, and refunds",
77
+ "technical": "Product errors and troubleshooting",
78
+ "other": null
79
+ }
80
+ }
81
+ }
82
+ }
83
+ ```
84
+
85
+ `examples/support.json` demonstrates Choice, Score, and Noul questions about
86
+ department routing, urgency, and incorrect charges.
87
+ Descriptions belong in `criteria`; use null for a label needing no description.
88
+ State, instructions, and descriptions also accept structured JSON objects and arrays.
89
+
90
+ CLI output defaults to JSON. `--pretty` renders answers and probabilities;
91
+ `--time` reports request time excluding model initialization. The file's `model`
92
+ selects the checkpoint; `--model` overrides it explicitly. `--backend hf|mlx`,
93
+ `--device`, `--revision`, `--mode`, and `--temperature` configure local inference.
94
+
95
+ The first run downloads the selected model from Hugging Face. Remote model code
96
+ is disabled. To explore the scoring mathematics with synthetic probabilities:
97
+
98
+ ```bash
99
+ uv run open-cricket --demo --pretty
100
+ uv run open-cricket --demo --mode constrained
101
+ ```
102
+
103
+ ## Direct Python SDK
104
+
105
+ ```python
106
+ import json
107
+ from open_cricket import LocalClient, Choice, Score, Noul
108
+
109
+ client = LocalClient(model="Qwen/Qwen2.5-1.5B-Instruct")
110
+ result = client.system_one(
111
+ state="I was charged twice. Please fix this today.",
112
+ questions={
113
+ "route": Choice(instructions="Which team?", criteria={"billing": "Payments", "other": None}),
114
+ "urgency": Score(criteria=["Can wait", "Within a few days", "Today"]),
115
+ "billing": Noul(instructions="Does this concern billing?"),
116
+ },
117
+ )
118
+ print(result["answers"]["route"]["choice"])
119
+ print(result["answers"]["urgency"]["score"])
120
+ print(result["answers"]["billing"]["noul"])
121
+
122
+ with open("examples/support.json") as f:
123
+ result = client.invoke(json.load(f))
124
+ ```
125
+
126
+ `LocalClient` keeps the model loaded and serializes access to it. `await
127
+ client.ainvoke(payload)` runs inference in a worker thread. Use `runtime="mlx"`
128
+ for Apple silicon. `backend=...` accepts a custom token backend. The requested
129
+ model must match the client's loaded model. There are no branded model aliases.
130
+
131
+ ## REST API and HTTP SDK
132
+
133
+ ```bash
134
+ uv sync --extra hf --extra server
135
+ export OPEN_CRICKET_MODEL=Qwen/Qwen2.5-1.5B-Instruct
136
+ export OPEN_CRICKET_API_KEY="choose-a-local-server-key"
137
+ uv run uvicorn open_cricket.server:app --host 127.0.0.1 --port 8000
138
+ ```
139
+
140
+ ```bash
141
+ curl http://127.0.0.1:8000/v1/systemone \
142
+ -H "Authorization: Bearer $OPEN_CRICKET_API_KEY" \
143
+ -H "Content-Type: application/json" \
144
+ --data-binary @examples/support.json
145
+ ```
146
+
147
+ `GET /v1/models` lists the configured checkpoint. `GET /health` reports readiness;
148
+ `/docs` exposes OpenAPI documentation. Set `OPEN_CRICKET_BACKEND=mlx` to serve MLX.
149
+ Authentication is disabled if `OPEN_CRICKET_API_KEY` is unset. Requests select the
150
+ configured model by its exact name; they do not trigger model downloads.
151
+
152
+ ```python
153
+ import json
154
+ from open_cricket import Client, Choice
155
+
156
+ client = Client("http://127.0.0.1:8000", api_key="choose-a-local-server-key")
157
+ result = client.system_one(
158
+ state="I was charged twice.",
159
+ questions={"route": Choice(criteria={"billing": None, "other": None})},
160
+ )
161
+ with open("examples/support.json") as f:
162
+ result = client.invoke(json.load(f))
163
+ ```
164
+
165
+ The HTTP and local SDKs return dictionaries with the same answer shape.
166
+ The server accepts an injected typed runnable through
167
+ `create_app(classifier, model_name="your-checkpoint")`.
168
+
169
+ ## LangChain and LangGraph
170
+
171
+ ```bash
172
+ uv sync --extra hf --extra langgraph
173
+ ```
174
+
175
+ ```python
176
+ import json
177
+ from open_cricket.hf import HuggingFaceBackend
178
+ from open_cricket.integrations import as_runnable
179
+
180
+ judge = as_runnable(HuggingFaceBackend(), model="Qwen/Qwen2.5-1.5B-Instruct")
181
+ with open("examples/support.json") as f:
182
+ payload = json.load(f)
183
+ result = judge.invoke(payload)
184
+ results = judge.batch([payload, payload])
185
+ route = judge | (lambda result: result["answers"]["department"]["choice"])
186
+ ```
187
+
188
+ `ainvoke`, `abatch`, and `Client.as_runnable()` use this same contract.
189
+ `graph_node(judge)` reads `state`, `model`, and `questions` from graph state and
190
+ writes the answer envelope to `classification`, preserving unrelated graph data.
191
+ See `examples/langgraph_workflow.py` for conditional routing.
192
+
193
+ `chat_runnable(chat_model, model_name="provider-model")` also accepts the same
194
+ contract. It scores single-token answer codes and requires all requested code
195
+ probabilities in provider metadata. Missing probabilities raise an error. It has
196
+ a maximum of 26 categories, further limited by `top_logprobs`. See
197
+ `examples/langchain_chat.py`; that example calls a paid provider when run.
198
+
199
+ ## Switching from Jev
200
+
201
+ Use the same state/question structure and `system_one` call pattern with your
202
+ local checkpoint name. The [migration guide](docs/jev-migration.md) covers
203
+ existing TypeSafe client configuration and the differences in model behavior,
204
+ confidence, and token accounting.
205
+
206
+ ## Faster local inference and Apple silicon
207
+
208
+ Local classification reuses a request-local attention (KV) cache across answer
209
+ prefixes. Branch changes trim ordinary KV caches back to their shared prefix;
210
+ unsupported sliding-window or recurrent cache formats safely recompute instead.
211
+ Hugging Face models that support `logits_to_keep` also compute only the final
212
+ position's vocabulary logits. No labels or answer tokens are skipped, and
213
+ normalisation still uses the full vocabulary. `model_calls` counts scored trie
214
+ nodes, so it does not decrease even though each call does much less work.
215
+ Custom backends implementing the original three-method protocol still work.
216
+ For Hugging Face cache acceleration, use Transformers 4.57; older supported
217
+ versions can fall back to full-prefix scoring.
218
+
219
+ For Apple silicon, install and select the optional MLX runtime:
220
+
221
+ ```bash
222
+ uv sync --extra mlx
223
+ uv run open-cricket --backend mlx --input examples/support.json --pretty --time
224
+ ```
225
+
226
+ MLX accepts compatible Hugging Face checkpoints and MLX-converted quantized
227
+ checkpoints via `--model`. The selected checkpoint determines weight precision;
228
+ quantization and lower precision can change probabilities and close rankings.
229
+ The MLX extra is restricted to Apple silicon macOS and uses MLX-LM 0.28.x to
230
+ remain compatible with this project's Transformers 4.x dependency. It requires
231
+ an accessible Metal GPU. Hugging Face remains the default runtime.
232
+
233
+ In Python, use `from open_cricket.mlx import MLXBackend` and pass `MLXBackend()`
234
+ to `classify` or `as_runnable`. For the HTTP service:
235
+
236
+ ```bash
237
+ uv sync --extra mlx --extra server
238
+ OPEN_CRICKET_BACKEND=mlx uv run uvicorn open_cricket.server:app --host 127.0.0.1
239
+ ```
240
+
241
+ Keep the model loaded between requests (for example through the HTTP service).
242
+ Starting the CLI for every message reloads the model. The existing runnable
243
+ serializes access to its model; async requests do not provide GPU batching.
244
+ Caches are isolated per classification and are not retained across requests.
245
+
246
+ ### Measured performance
247
+
248
+ A local Qwen2.5-0.5B-Instruct benchmark with four options, 112 prompt tokens and
249
+ 11 scored prefixes produced these warmed median request times, excluding load:
250
+
251
+ | Runtime | Full-prefix baseline | Optimized | Speedup | Repeats |
252
+ | --- | ---: | ---: | ---: | ---: |
253
+ | Hugging Face, CPU, float32 | 1.350 s | 0.259 s | 5.2× | 5 |
254
+ | MLX, Metal, checkpoint precision | 0.169 s | 0.057 s | 3.0× | 3 |
255
+
256
+ These measurements use the 0.5B checkpoint; they do not measure the new 1.5B
257
+ default. The benchmark script keeps 0.5B as its default for reproducibility.
258
+ These are one local workload, not a general performance guarantee or a comparison
259
+ of MLX against Hugging Face MPS. The largest absolute probability difference
260
+ between baseline and optimized scoring was 0.0000033 for HF and 0.024 for MLX
261
+ (about 2.4 percentage points). MLX's lower-precision model arithmetic can vary
262
+ between full-sequence and incremental evaluation. Float32 miniature-model
263
+ regression tests verify both runtimes' branch rollback against uncached scoring
264
+ to five decimal places. Evaluate your categories before switching runtime or
265
+ weight precision.
266
+
267
+ Reproduce the benchmark with:
268
+
269
+ ```bash
270
+ uv run python examples/benchmark_backend.py --device cpu --repeats 5
271
+ uv run python examples/benchmark_backend.py --backend mlx --repeats 5
272
+ ```
273
+
274
+ The benchmark alternates cached and uncached runs after warmup and reports both
275
+ speed and probability differences. Rust has not been introduced: eliminating
276
+ repeated model computation provides the demonstrated gain, while rewriting the
277
+ small Python trie would leave that model work unchanged.
278
+
279
+ ## What happens mathematically?
280
+
281
+ The questionnaire renders message data as a quoted JSON string, followed by the
282
+ question, allowed options, and `Answer:`. The HF adapter uses the tokenizer's
283
+ chat template and assistant-generation boundary where available. The system
284
+ instruction asks for one JSON-quoted option and immediate end of turn.
285
+
286
+ For an option with token sequence `t1 ... tn EOS`:
287
+
288
+ ```text
289
+ score(option) = sum_j log P(tj | questionnaire, t1 ... t(j-1))
290
+ P(option | supplied candidates) = softmax(score(option) / temperature)
291
+ ```
292
+
293
+ Logits become log probabilities using full-vocabulary `log_softmax`. We add
294
+ log probabilities, not probabilities, and do not softmax already normalised
295
+ probabilities. Labels share a token trie: common prefixes are evaluated once
296
+ per question. Every branch is explored; this is not greedy token generation.
297
+ An explicit EOS/end-of-turn token distinguishes a complete answer from a label
298
+ that merely begins the same way. JSON quoting handles multiword labels and
299
+ embedded quotes. Every output is one of the supplied labels by construction.
300
+
301
+ The exact path scores **one canonical tokenisation** of each JSON answer plus
302
+ one tokenizer-defined EOS token, starting at a fixed prompt token boundary.
303
+ It does not sum alternative tokenisations, whitespace, synonyms, or other
304
+ valid end-of-turn tokens. Thus "exact" refers to these particular token-sequence
305
+ events, not all text representations of the underlying category.
306
+
307
+ `mode="constrained"` instead renormalises at every trie branch. It implements
308
+ locally masked generation. This is mathematically different from conditioning
309
+ the original model on the complete candidate set and can change the winner.
310
+ The default is `sequence`. Final temperature acts on completed scores in both
311
+ modes. No length normalisation is used: longer spellings may be penalised.
312
+ Code scoring reduces spelling-length effects but introduces code/order bias.
313
+
314
+ The synthetic demo makes the distinction testable:
315
+
316
+ | Option | Original joint probability | Sequence mode | Constrained mode |
317
+ |---|---:|---:|---:|
318
+ | refund | .06 | .105263 | .074074 |
319
+ | refund status | .24 | .421053 | .592593 |
320
+ | technical | .27 | .473684 | .333333 |
321
+
322
+ ## Limits and verification
323
+
324
+ Probabilities are relative to the supplied categories, even if none fits well.
325
+ Include an `other` category when appropriate. Confidence is 1 minus normalized
326
+ Shannon entropy; it is not a calibrated probability of correctness. Score is the
327
+ probability-weighted rubric index; Noul returns the relative probability of true.
328
+ Question wording, label spelling, and model precision can affect the result.
329
+
330
+ Local context limits come from the checkpoint. Requests evaluate each question
331
+ independently and sequentially. Input usage sums prompt lengths across questions;
332
+ local scoring generates no output text, so output usage is zero. An injected
333
+ classifier may report null input usage when no count is available.
334
+
335
+ ```bash
336
+ uv sync --extra test
337
+ uv run python -m unittest discover -s tests -v
338
+ ```
339
+
340
+ Tests cover scoring mathematics, cache isolation, typed requests and responses,
341
+ CLI validation, local/HTTP SDKs, official TypeSafe SDK interoperability,
342
+ LangChain/LangGraph, authentication, and schema validation. Optional runtime tests
343
+ require their respective HF or MLX dependencies and hardware.
344
+
345
+ ## Sources
346
+
347
+ - [TypeSafe's Jev announcement](https://typesafe.ai/blog/introducing-system-one-models-and-jev): describes its architecture, parallel sampler and RLCD training.
348
+ - [Hugging Face model outputs](https://huggingface.co/docs/transformers/main_classes/output): causal-LM logits.
349
+ - [Qwen demonstration model](https://huggingface.co/Qwen/Qwen2.5-1.5B-Instruct): loading and chat-template usage.
350
+ - [LangChain ChatOpenAI](https://docs.langchain.com/oss/python/integrations/chat/openai): chat interface and logprobs metadata.
351
+ - [LangGraph graph API](https://docs.langchain.com/oss/python/langgraph/graph-api): state updates and conditional edges.
352
+
353
+ Version 0.1.0. An independent educational prototype; no affiliation with TypeSafe.