logitgate 0.2.2__tar.gz → 0.3.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 (67) hide show
  1. {logitgate-0.2.2/src/logitgate.egg-info → logitgate-0.3.0}/PKG-INFO +121 -20
  2. {logitgate-0.2.2 → logitgate-0.3.0}/README.md +120 -17
  3. {logitgate-0.2.2 → logitgate-0.3.0}/pyproject.toml +3 -3
  4. {logitgate-0.2.2 → logitgate-0.3.0}/src/logitgate/__init__.py +83 -8
  5. {logitgate-0.2.2 → logitgate-0.3.0}/src/logitgate/backends/__init__.py +8 -0
  6. {logitgate-0.2.2 → logitgate-0.3.0}/src/logitgate/backends/base.py +123 -29
  7. {logitgate-0.2.2 → logitgate-0.3.0}/src/logitgate/backends/dory_pytorch.py +4 -3
  8. logitgate-0.3.0/src/logitgate/backends/dory_recurrent.py +113 -0
  9. {logitgate-0.2.2 → logitgate-0.3.0}/src/logitgate/backends/llamacpp.py +8 -0
  10. {logitgate-0.2.2 → logitgate-0.3.0}/src/logitgate/backends/ollama.py +71 -85
  11. {logitgate-0.2.2 → logitgate-0.3.0}/src/logitgate/backends/openai_compat.py +17 -0
  12. logitgate-0.3.0/src/logitgate/backends/pytorch_native.py +198 -0
  13. {logitgate-0.2.2 → logitgate-0.3.0}/src/logitgate/backends/recurrent.py +8 -2
  14. logitgate-0.3.0/src/logitgate/benchmark.py +376 -0
  15. logitgate-0.3.0/src/logitgate/calibration/__init__.py +11 -0
  16. logitgate-0.3.0/src/logitgate/calibration/conformal.py +334 -0
  17. logitgate-0.3.0/src/logitgate/cascade.py +232 -0
  18. logitgate-0.3.0/src/logitgate/cli.py +681 -0
  19. {logitgate-0.2.2 → logitgate-0.3.0}/src/logitgate/config.py +39 -1
  20. {logitgate-0.2.2 → logitgate-0.3.0}/src/logitgate/debiasing/__init__.py +10 -0
  21. {logitgate-0.2.2 → logitgate-0.3.0}/src/logitgate/debiasing/inversion.py +29 -4
  22. {logitgate-0.2.2 → logitgate-0.3.0}/src/logitgate/debiasing/permutation.py +178 -0
  23. logitgate-0.3.0/src/logitgate/exceptions.py +54 -0
  24. {logitgate-0.2.2 → logitgate-0.3.0}/src/logitgate/gates/__init__.py +4 -2
  25. logitgate-0.3.0/src/logitgate/gates/ast_gate.py +451 -0
  26. {logitgate-0.2.2 → logitgate-0.3.0}/src/logitgate/gates/base.py +48 -1
  27. logitgate-0.3.0/src/logitgate/gates/judge_gate.py +324 -0
  28. logitgate-0.3.0/src/logitgate/gates/logit_gate.py +804 -0
  29. logitgate-0.3.0/src/logitgate/hardware/__init__.py +9 -0
  30. logitgate-0.3.0/src/logitgate/hardware/memory_guard.py +82 -0
  31. logitgate-0.3.0/src/logitgate/integrations/__init__.py +49 -0
  32. logitgate-0.3.0/src/logitgate/integrations/langgraph.py +86 -0
  33. logitgate-0.3.0/src/logitgate/integrations/pytest_plugin.py +143 -0
  34. {logitgate-0.2.2 → logitgate-0.3.0}/src/logitgate/models.py +23 -0
  35. {logitgate-0.2.2 → logitgate-0.3.0}/src/logitgate/snapshot.py +107 -0
  36. {logitgate-0.2.2 → logitgate-0.3.0/src/logitgate.egg-info}/PKG-INFO +121 -20
  37. {logitgate-0.2.2 → logitgate-0.3.0}/src/logitgate.egg-info/SOURCES.txt +12 -0
  38. {logitgate-0.2.2 → logitgate-0.3.0}/tests/test_ast_gate.py +59 -1
  39. {logitgate-0.2.2 → logitgate-0.3.0}/tests/test_backends_and_cli.py +357 -1
  40. logitgate-0.3.0/tests/test_calibration.py +151 -0
  41. logitgate-0.3.0/tests/test_cascade_routing.py +303 -0
  42. logitgate-0.3.0/tests/test_conformal_guarantees.py +64 -0
  43. {logitgate-0.2.2 → logitgate-0.3.0}/tests/test_debiasing_math.py +85 -1
  44. logitgate-0.3.0/tests/test_dx_and_tooling.py +407 -0
  45. {logitgate-0.2.2 → logitgate-0.3.0}/tests/test_logit_gate.py +21 -0
  46. logitgate-0.3.0/tests/test_multi_rubric_gate.py +282 -0
  47. {logitgate-0.2.2 → logitgate-0.3.0}/tests/test_usability_and_validation.py +147 -0
  48. logitgate-0.2.2/src/logitgate/cascade.py +0 -579
  49. logitgate-0.2.2/src/logitgate/cli.py +0 -264
  50. logitgate-0.2.2/src/logitgate/exceptions.py +0 -29
  51. logitgate-0.2.2/src/logitgate/gates/ast_gate.py +0 -560
  52. logitgate-0.2.2/src/logitgate/gates/judge_gate.py +0 -320
  53. logitgate-0.2.2/src/logitgate/gates/logit_gate.py +0 -985
  54. logitgate-0.2.2/src/logitgate/integrations/__init__.py +0 -10
  55. logitgate-0.2.2/src/logitgate/integrations/langgraph.py +0 -30
  56. logitgate-0.2.2/src/logitgate/integrations/pytest_plugin.py +0 -68
  57. {logitgate-0.2.2 → logitgate-0.3.0}/LICENSE +0 -0
  58. {logitgate-0.2.2 → logitgate-0.3.0}/setup.cfg +0 -0
  59. {logitgate-0.2.2 → logitgate-0.3.0}/src/logitgate/debiasing/softmax.py +0 -0
  60. {logitgate-0.2.2 → logitgate-0.3.0}/src/logitgate/integrations/deepeval.py +0 -0
  61. {logitgate-0.2.2 → logitgate-0.3.0}/src/logitgate/py.typed +0 -0
  62. {logitgate-0.2.2 → logitgate-0.3.0}/src/logitgate.egg-info/dependency_links.txt +0 -0
  63. {logitgate-0.2.2 → logitgate-0.3.0}/src/logitgate.egg-info/entry_points.txt +0 -0
  64. {logitgate-0.2.2 → logitgate-0.3.0}/src/logitgate.egg-info/requires.txt +0 -0
  65. {logitgate-0.2.2 → logitgate-0.3.0}/src/logitgate.egg-info/top_level.txt +0 -0
  66. {logitgate-0.2.2 → logitgate-0.3.0}/tests/test_cascade.py +0 -0
  67. {logitgate-0.2.2 → logitgate-0.3.0}/tests/test_integrations.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: logitgate
3
- Version: 0.2.2
3
+ Version: 0.3.0
4
4
  Summary: Fast, calibrated first-token logit evaluation and 3-layer cascaded routing for LLMs.
5
5
  Author-email: Jason Bradley <jasonbdly@gmail.com>
6
6
  License: Apache-2.0
@@ -16,8 +16,6 @@ Classifier: Programming Language :: Python :: 3.10
16
16
  Classifier: Programming Language :: Python :: 3.11
17
17
  Classifier: Programming Language :: Python :: 3.12
18
18
  Classifier: Programming Language :: Python :: 3.13
19
- Classifier: Programming Language :: Python :: 3.14
20
- Classifier: Programming Language :: Python :: 3.15
21
19
  Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
22
20
  Requires-Python: >=3.10
23
21
  Description-Content-Type: text/markdown
@@ -65,13 +63,15 @@ Dynamic: license-file
65
63
 
66
64
  **Fast, calibrated first-token logit evaluation and 3-layer cascaded routing for LLMs.**
67
65
 
68
- LogitGate replaces slow, multi-token LLM-as-a-judge critiques (5–20s) with calibrated first-token logit gates (**36–110 ms**) and a 3-layer cascaded routing pipeline (`ASTGate >> LogitGate >> JudgeGate`), saving 90%–98% of evaluation tokens and wall-clock time while canceling token prior and positional biases.
66
+ LLM-as-a-judge asks a model to write a critique and then a verdict. That costs a full autoregressive generation per check: 1.5–6 s and a few hundred tokens on our hardware. LogitGate reads the verdict straight from the **first-token log-probabilities** (`max_tokens=1`, 0 generated tokens), cancels the model's label-order prior with a forward + reverse pass, and only falls back to a generative judge when that read is genuinely ambiguous.
67
+
68
+ Measured on the repo's 24-case multi-domain suite (RTX 4070, Ollama, [full methodology](docs/benchmarks.md)): **36–41 ms** single-pass and **90–113 ms** debiased per check. End-to-end, the cascade ran **10.6x–17x** faster than a `think=False` generative judge, at 23/24 accuracy versus the judge's 23–24/24. On pure CPU the gap shrinks to 1.4x, because prefill dominates there. Don't take our word for it: [`logitgate benchmark`](#reproduce-the-numbers-on-your-hardware) reruns the comparison on your model in about a minute.
69
69
 
70
70
  ---
71
71
 
72
72
  ## How It Works in 60 Seconds
73
73
 
74
- When evaluating whether an LLM output satisfies a rubric, traditional frameworks prompt a model to generate a 200–500 word critique before outputting `PASS` or `FAIL`. This takes **5 to 20 seconds**, consumes hundreds of tokens, and suffers from recency and positional biases.
74
+ When evaluating whether an LLM output satisfies a rubric, traditional frameworks prompt a model to generate a 200–500 word critique before outputting `PASS` or `FAIL`. That takes **seconds per check**, consumes hundreds of tokens, and is sensitive to label order and position.
75
75
 
76
76
  `LogitGate` uses a **3-layer cascaded pipeline**:
77
77
 
@@ -104,7 +104,7 @@ When evaluating whether an LLM output satisfies a rubric, traditional frameworks
104
104
  ## Installation
105
105
 
106
106
  ```bash
107
- # Core package (zero dependencies except pydantic & httpx)
107
+ # Core package: only pydantic + httpx
108
108
  pip install logitgate
109
109
 
110
110
  # With optional framework integrations:
@@ -118,6 +118,28 @@ pip install "logitgate[all]" # All integrations
118
118
 
119
119
  ---
120
120
 
121
+ ## Pick a Backend (10 seconds)
122
+
123
+ LogitGate needs an endpoint that returns **token log-probabilities**. Anything below works; a gate's `backend=` argument overrides the global default.
124
+
125
+ ```bash
126
+ export LOGITGATE_BACKEND="ollama://localhost:11434/qwen2.5:7b" # Ollama
127
+ export LOGITGATE_BACKEND="vllm://localhost:8000/v1" # vLLM / any OpenAI-compatible server
128
+ export LOGITGATE_BACKEND="llamacpp://localhost:8080" # llama-server
129
+ export LOGITGATE_BACKEND="stub://" # offline stub for CI plumbing (fixed P=0.95)
130
+ ```
131
+
132
+ With nothing configured, LogitGate falls back to the offline stub and emits a one-time `StubBackendWarning`, so a fake `P=0.95` never passes for a real result. Errors say what to do next instead of dumping an `httpx` traceback:
133
+
134
+ ```text
135
+ logitgate.exceptions.BackendUnavailableError: Cannot connect to Ollama at http://localhost:11434.
136
+ Is ollama running? (start it with `ollama serve`) Or set backend='stub://' for offline testing.
137
+ ```
138
+
139
+ A missing Ollama model suggests the exact `ollama pull <model>` command, an OpenAI 401 points at `OPENAI_API_KEY`, and a cold-model timeout says so. `BackendUnavailableError` is also a `ConnectionError`, so existing retry logic keeps working.
140
+
141
+ ---
142
+
121
143
  ## 30-Second Quickstart
122
144
 
123
145
  ### 1. Single Atomic Rubric (3-Layer Cascaded Fast Path)
@@ -159,26 +181,28 @@ print(f"Tokens Generated: {verdict.total_tokens_generated}") # 0 tokens!
159
181
 
160
182
  ### 2. Multi-Rubric Batching (Prefix-Preserved KV-Cache Reuse)
161
183
 
162
- When verifying multiple criteria against a candidate artifact, **do not combine them into a single monolithic compound rubric** (which dilutes logit signals and conflates orthogonal requirements). Instead, evaluate isolated atomic rubrics in a single batch using `evaluate_batch`.
184
+ When verifying multiple criteria against a candidate artifact, **do not combine them into a single monolithic compound rubric** (which dilutes logit signals and conflates orthogonal requirements). Instead, evaluate isolated atomic rubrics with `MultiRubricGate`.
163
185
 
164
- The candidate code is anchored at Token 0, sharing 95–99% of prefix tokens in KV cache across all checks:
186
+ The candidate code is anchored at Token 0, sharing 95–99% of prefix tokens in KV-cache across all checks. `MultiRubricGate` enforces weakest-link aggregation: passes only if *every* atomic rubric passes, and records per-rubric traces so diagnostics show exactly which requirement failed:
165
187
 
166
188
  ```python
167
- from logitgate import LogitGate
189
+ from logitgate import MultiRubricGate
168
190
 
169
- # Best Practice: Keep rubrics atomic — evaluate multiple criteria via evaluate_batch
191
+ # Best Practice: Keep rubrics atomic — evaluate multiple criteria via MultiRubricGate
170
192
  rubrics = [
171
193
  "Does the function calculate Fibonacci numbers recursively?",
172
194
  "Does the function explicitly handle non-positive inputs (n <= 0)?",
173
195
  "Does the function include parameter and return type annotations?",
174
196
  ]
175
197
 
176
- gate = LogitGate("Base rubric")
198
+ gate = MultiRubricGate(rubrics)
177
199
 
178
200
  # Evaluates all 3 rubrics in a single batch reusing the shared KV-cache prefix
179
- verdicts = gate.evaluate_batch(candidate_code, rubrics)
201
+ verdict = gate(candidate_code)
202
+ print(f"Passed: {verdict.passed} | {verdict.reason}")
180
203
 
181
- for rubric, v in zip(rubrics, verdicts):
204
+ # Or inspect individual per-rubric verdicts:
205
+ for rubric, v in zip(rubrics, gate.evaluate_each(candidate_code)):
182
206
  status = "PASS" if v.passed else "FAIL"
183
207
  print(f"[{status}] P={v.probability:.4f} | {rubric}")
184
208
  ```
@@ -192,6 +216,7 @@ for rubric, v in zip(rubrics, verdicts):
192
216
  | **`ASTGate`** | Layer 0 | **< 0.1 ms** | 0 | Sub-millisecond deterministic checks: Python syntax, forbidden imports, regex, acyclic DAGs, custom callables. |
193
217
  | **`ContractGate`** | Layer 0 | **< 0.1 ms** | 0 | General-domain contract validation: Pydantic models, JSON Schemas, length bounds, required sections. |
194
218
  | **`LogitGate`** | Layer 1 | **~40–110 ms** | 0 | Calibrated binary classification from first-token logits using symmetric log-odds debiasing ($\bar{L} = \frac{L_1 + L_2}{2T}$). |
219
+ | **`MultiRubricGate`** | Layer 1 | **~50–140 ms** | 0 | Atomic multi-rubric verification sharing KV prefix. Weakest-link aggregation (fails if any criterion fails). |
195
220
  | **`ChoiceGate`** | Layer 1 | **~40–120 ms** | 0 | Discrete K-ary routing and intent classification across arbitrary string options with permutation averaging. |
196
221
  | **`ScoreGate`** | Layer 1 | **~50–160 ms** | 0 | Continuous probability-weighted rubric scoring (𝔼[S] = Σ s · P(s)) with inverted scale debiasing. |
197
222
  | **`JudgeGate`** | Layer 2 | **1.5–5.0 s** | 60–300 | Generative System-2 reasoning judge with reason-then-verdict parsing and self-healing 1-token logit probes. |
@@ -201,6 +226,73 @@ for rubric, v in zip(rubrics, verdicts):
201
226
 
202
227
  ---
203
228
 
229
+ ## Where LogitGate Fits (and Where It Doesn't)
230
+
231
+ | Tool | What it does | How LogitGate relates |
232
+ | :--- | :--- | :--- |
233
+ | **DeepEval / G-Eval** | Offline evaluation suites and CI scoring; G-Eval weights score tokens by probability. | Complementary. LogitGate is the *inline* path: a sub-100 ms check you can put between agent turns. It ships a DeepEval integration (`LogitGateMetric`) for suites. |
234
+ | **RouteLLM** | Picks *which model generates* a response, before generation. | Different stage. LogitGate judges the output *after* generation. `ChoiceGate` can route, but on content, not on model cost. |
235
+ | **Flow-Judge, Lynx, Prometheus 2** | Dedicated fine-tuned judge models that reason autoregressively. | LogitGate is non-autoregressive and model-agnostic. It projects a verdict from the first-token logits of a model you already serve, with no extra VRAM. A fine-tuned judge works fine as the Layer-2 `JudgeGate` backend. |
236
+
237
+ **When not to use it:**
238
+
239
+ - **No logprobs, no LogitGate.** APIs that don't expose `top_logprobs` (e.g. Anthropic) can't host Layer 1. OpenAI caps `top_logprobs` at 20, which is enough for binary and small-K gates.
240
+ - **Multi-step reasoning rubrics** ("is this 12-step derivation correct?") exceed what a single forward pass can judge. Accuracy drops on our reasoning-depth suite ([falsification boundaries](docs/benchmarks.md)). Let those escalate to `JudgeGate`, or decompose them into atomic rubrics.
241
+ - **Tiny models on CPU.** 1–1.5B models are fast but reach 58–63% cascade accuracy on our multi-domain suite. Measure before you trust them.
242
+
243
+ ---
244
+
245
+ ## Reproduce the Numbers on Your Hardware
246
+
247
+ ```bash
248
+ logitgate benchmark --backend ollama://localhost:11434 --model qwen2.5:7b
249
+ logitgate benchmark --backend vllm://localhost:8000/v1 --model Qwen/Qwen2.5-7B-Instruct \
250
+ --dataset benchmarks/data/multi_domain_24.json --repeat 3 --json > results.json
251
+ ```
252
+
253
+ The command runs the same cases through three configurations and reports p50/p95 latency, generated tokens, LLM calls per case, accuracy, and the cascade's fast-path rate:
254
+
255
+ 1. A generative `JudgeGate` alone (`think=False` by default, so the baseline is a *fast* judge)
256
+ 2. A single `LogitGate`
257
+ 3. `LogitGate >> JudgeGate` (`on_ambiguous_only`)
258
+
259
+ Modes run as separate passes after an untimed warm-up, so model load time isn't charged to whichever mode runs first. `--json` records the LogitGate version, backend, model, platform, and a dataset hash, so a result can be traced to exactly what produced it. With the debiased default, LogitGate makes **two** forward passes per check, and the report counts both.
260
+
261
+ ---
262
+
263
+ ## Custom Gates & Routing Traits
264
+
265
+ `Cascade` never checks concrete classes. It routes on three traits declared on every gate, so wrappers, decorators, and your own gates get the same short-circuiting as the built-ins:
266
+
267
+ | Trait | Meaning | Built-ins |
268
+ | :--- | :--- | :--- |
269
+ | `layer_tier` | `0` deterministic (failures are final), `1` single forward pass (failures may escalate), `2` generative | `ASTGate`=0, `LogitGate`/`ChoiceGate`/`ScoreGate`=1, `JudgeGate`=2 |
270
+ | `can_fast_path` | A PASSED verdict skips the next generative gate | `False` for `ASTGate` (a valid schema says nothing about semantics) |
271
+ | `escalation_policy` | When a tier-2 gate runs: `on_ambiguous_or_fail`, `on_ambiguous_only`, `always` | `JudgeGate(trigger=...)` |
272
+
273
+ ```python
274
+ from logitgate import BaseGate, DecisionStatus, JudgeGate, LayerTrace, Verdict
275
+
276
+ class RegexPIIGate(BaseGate):
277
+ """Deterministic: a hit is a decisive FAIL; a miss proves nothing semantic."""
278
+
279
+ name = "RegexPII"
280
+ layer_tier = 0
281
+ can_fast_path = False
282
+
283
+ def evaluate(self, candidate, **kwargs):
284
+ leaked = "@" in str(candidate)
285
+ status = DecisionStatus.FAILED if leaked else DecisionStatus.PASSED
286
+ trace = LayerTrace(layer_name=self.name, status=status, latency_ms=0.01)
287
+ return Verdict(passed=not leaked, status=status, triggered_layer=self.name,
288
+ total_latency_ms=0.01, traces=[trace])
289
+
290
+ pipeline = RegexPIIGate() >> JudgeGate(rubric="Does the reply avoid leaking personal data?")
291
+ print(pipeline.evaluate("Contact jane@example.com").triggered_layer) # RegexPII; judge never called
292
+ ```
293
+
294
+ ---
295
+
204
296
  ## Benchmark Highlights
205
297
 
206
298
  Across a 24-item multi-domain suite (RAG groundedness, customer support policy, SQL safety, and Python security), `LogitGate` resolves **95.8%–100.0% of cases at Layers 0 and 1**, avoiding multi-token generation:
@@ -253,13 +345,11 @@ For complete research data, mathematical derivations, and production recipes, ex
253
345
 
254
346
  ## Common Recipes
255
347
 
256
- ### Multi-Rubric Batch Evaluation (`evaluate_batch`)
348
+ ### Multi-Rubric Batch Evaluation (`MultiRubricGate`)
257
349
  Evaluates multiple rubrics against a single candidate artifact while anchoring the candidate at Token 0 to share 95–99% of prefix tokens in KV-cache:
258
350
 
259
351
  ```python
260
- from logitgate import LogitGate
261
-
262
- gate = LogitGate("Base rubric")
352
+ from logitgate import MultiRubricGate
263
353
 
264
354
  rubrics = [
265
355
  "Does the code include full type annotations?",
@@ -267,8 +357,9 @@ rubrics = [
267
357
  "Does the code implement proper error handling?",
268
358
  ]
269
359
 
360
+ gate = MultiRubricGate(rubrics)
270
361
  candidate_code = "def fetch(url: str, timeout: int = 10) -> str: pass"
271
- verdicts = gate.evaluate_batch(candidate_code, rubrics)
362
+ verdicts = gate.evaluate_each(candidate_code)
272
363
 
273
364
  for rubric, v in zip(rubrics, verdicts):
274
365
  print(f"[{'PASS' if v.passed else 'FAIL'}] P={v.probability:.2f} | {rubric}")
@@ -302,7 +393,9 @@ from logitgate import ScoreGate
302
393
 
303
394
  scorer = ScoreGate(
304
395
  rubric="Rate the architectural code quality from 1 (disastrous) to 5 (flawless):",
305
- scale=(1, 5),
396
+ min_score=1,
397
+ max_score=5,
398
+ threshold=3.5,
306
399
  symmetric_debiasing=True, # Evaluates 1->5 and 5->1 to eliminate scale anchoring
307
400
  )
308
401
 
@@ -327,10 +420,18 @@ cat schema.json | logitgate eval --rubric "Must be valid JSON schema"
327
420
  # Replay and explain a frozen failure snapshot from CI
328
421
  logitgate explain .logitgate/failures/latest.json
329
422
 
423
+ # Inspect margins, entropy, and label-order bias without calling any model
424
+ logitgate explain --offline
425
+
426
+ # Judge vs LogitGate vs cascade latency/cost on your backend
427
+ logitgate benchmark --backend "ollama://localhost:11434" --model "qwen2.5:7b"
428
+
330
429
  # Auto-calibrate model temperature on micro-dataset
331
430
  logitgate calibrate --backend "ollama://localhost:11434" --model "qwen2.5:14b"
332
431
  ```
333
432
 
433
+ Exit codes: `0` passed / ok, `1` verdict failed or bad input, `2` backend unreachable or misconfigured.
434
+
334
435
  ---
335
436
 
336
437
  ## BibTeX Citation
@@ -342,7 +443,7 @@ If you use LogitGate in your research or production systems, please cite it as f
342
443
  author = {Jason Bradley},
343
444
  title = {LogitGate: Calibrated First-Token Logit Evaluation and Cascaded Routing for LLMs},
344
445
  year = {2026},
345
- version = {0.2.2},
446
+ version = {0.3.0},
346
447
  url = {https://github.com/jasonbdly/LogitGate},
347
448
  license = {Apache-2.0}
348
449
  }
@@ -10,13 +10,15 @@
10
10
 
11
11
  **Fast, calibrated first-token logit evaluation and 3-layer cascaded routing for LLMs.**
12
12
 
13
- LogitGate replaces slow, multi-token LLM-as-a-judge critiques (5–20s) with calibrated first-token logit gates (**36–110 ms**) and a 3-layer cascaded routing pipeline (`ASTGate >> LogitGate >> JudgeGate`), saving 90%–98% of evaluation tokens and wall-clock time while canceling token prior and positional biases.
13
+ LLM-as-a-judge asks a model to write a critique and then a verdict. That costs a full autoregressive generation per check: 1.5–6 s and a few hundred tokens on our hardware. LogitGate reads the verdict straight from the **first-token log-probabilities** (`max_tokens=1`, 0 generated tokens), cancels the model's label-order prior with a forward + reverse pass, and only falls back to a generative judge when that read is genuinely ambiguous.
14
+
15
+ Measured on the repo's 24-case multi-domain suite (RTX 4070, Ollama, [full methodology](docs/benchmarks.md)): **36–41 ms** single-pass and **90–113 ms** debiased per check. End-to-end, the cascade ran **10.6x–17x** faster than a `think=False` generative judge, at 23/24 accuracy versus the judge's 23–24/24. On pure CPU the gap shrinks to 1.4x, because prefill dominates there. Don't take our word for it: [`logitgate benchmark`](#reproduce-the-numbers-on-your-hardware) reruns the comparison on your model in about a minute.
14
16
 
15
17
  ---
16
18
 
17
19
  ## How It Works in 60 Seconds
18
20
 
19
- When evaluating whether an LLM output satisfies a rubric, traditional frameworks prompt a model to generate a 200–500 word critique before outputting `PASS` or `FAIL`. This takes **5 to 20 seconds**, consumes hundreds of tokens, and suffers from recency and positional biases.
21
+ When evaluating whether an LLM output satisfies a rubric, traditional frameworks prompt a model to generate a 200–500 word critique before outputting `PASS` or `FAIL`. That takes **seconds per check**, consumes hundreds of tokens, and is sensitive to label order and position.
20
22
 
21
23
  `LogitGate` uses a **3-layer cascaded pipeline**:
22
24
 
@@ -49,7 +51,7 @@ When evaluating whether an LLM output satisfies a rubric, traditional frameworks
49
51
  ## Installation
50
52
 
51
53
  ```bash
52
- # Core package (zero dependencies except pydantic & httpx)
54
+ # Core package: only pydantic + httpx
53
55
  pip install logitgate
54
56
 
55
57
  # With optional framework integrations:
@@ -63,6 +65,28 @@ pip install "logitgate[all]" # All integrations
63
65
 
64
66
  ---
65
67
 
68
+ ## Pick a Backend (10 seconds)
69
+
70
+ LogitGate needs an endpoint that returns **token log-probabilities**. Anything below works; a gate's `backend=` argument overrides the global default.
71
+
72
+ ```bash
73
+ export LOGITGATE_BACKEND="ollama://localhost:11434/qwen2.5:7b" # Ollama
74
+ export LOGITGATE_BACKEND="vllm://localhost:8000/v1" # vLLM / any OpenAI-compatible server
75
+ export LOGITGATE_BACKEND="llamacpp://localhost:8080" # llama-server
76
+ export LOGITGATE_BACKEND="stub://" # offline stub for CI plumbing (fixed P=0.95)
77
+ ```
78
+
79
+ With nothing configured, LogitGate falls back to the offline stub and emits a one-time `StubBackendWarning`, so a fake `P=0.95` never passes for a real result. Errors say what to do next instead of dumping an `httpx` traceback:
80
+
81
+ ```text
82
+ logitgate.exceptions.BackendUnavailableError: Cannot connect to Ollama at http://localhost:11434.
83
+ Is ollama running? (start it with `ollama serve`) Or set backend='stub://' for offline testing.
84
+ ```
85
+
86
+ A missing Ollama model suggests the exact `ollama pull <model>` command, an OpenAI 401 points at `OPENAI_API_KEY`, and a cold-model timeout says so. `BackendUnavailableError` is also a `ConnectionError`, so existing retry logic keeps working.
87
+
88
+ ---
89
+
66
90
  ## 30-Second Quickstart
67
91
 
68
92
  ### 1. Single Atomic Rubric (3-Layer Cascaded Fast Path)
@@ -104,26 +128,28 @@ print(f"Tokens Generated: {verdict.total_tokens_generated}") # 0 tokens!
104
128
 
105
129
  ### 2. Multi-Rubric Batching (Prefix-Preserved KV-Cache Reuse)
106
130
 
107
- When verifying multiple criteria against a candidate artifact, **do not combine them into a single monolithic compound rubric** (which dilutes logit signals and conflates orthogonal requirements). Instead, evaluate isolated atomic rubrics in a single batch using `evaluate_batch`.
131
+ When verifying multiple criteria against a candidate artifact, **do not combine them into a single monolithic compound rubric** (which dilutes logit signals and conflates orthogonal requirements). Instead, evaluate isolated atomic rubrics with `MultiRubricGate`.
108
132
 
109
- The candidate code is anchored at Token 0, sharing 95–99% of prefix tokens in KV cache across all checks:
133
+ The candidate code is anchored at Token 0, sharing 95–99% of prefix tokens in KV-cache across all checks. `MultiRubricGate` enforces weakest-link aggregation: passes only if *every* atomic rubric passes, and records per-rubric traces so diagnostics show exactly which requirement failed:
110
134
 
111
135
  ```python
112
- from logitgate import LogitGate
136
+ from logitgate import MultiRubricGate
113
137
 
114
- # Best Practice: Keep rubrics atomic — evaluate multiple criteria via evaluate_batch
138
+ # Best Practice: Keep rubrics atomic — evaluate multiple criteria via MultiRubricGate
115
139
  rubrics = [
116
140
  "Does the function calculate Fibonacci numbers recursively?",
117
141
  "Does the function explicitly handle non-positive inputs (n <= 0)?",
118
142
  "Does the function include parameter and return type annotations?",
119
143
  ]
120
144
 
121
- gate = LogitGate("Base rubric")
145
+ gate = MultiRubricGate(rubrics)
122
146
 
123
147
  # Evaluates all 3 rubrics in a single batch reusing the shared KV-cache prefix
124
- verdicts = gate.evaluate_batch(candidate_code, rubrics)
148
+ verdict = gate(candidate_code)
149
+ print(f"Passed: {verdict.passed} | {verdict.reason}")
125
150
 
126
- for rubric, v in zip(rubrics, verdicts):
151
+ # Or inspect individual per-rubric verdicts:
152
+ for rubric, v in zip(rubrics, gate.evaluate_each(candidate_code)):
127
153
  status = "PASS" if v.passed else "FAIL"
128
154
  print(f"[{status}] P={v.probability:.4f} | {rubric}")
129
155
  ```
@@ -137,6 +163,7 @@ for rubric, v in zip(rubrics, verdicts):
137
163
  | **`ASTGate`** | Layer 0 | **< 0.1 ms** | 0 | Sub-millisecond deterministic checks: Python syntax, forbidden imports, regex, acyclic DAGs, custom callables. |
138
164
  | **`ContractGate`** | Layer 0 | **< 0.1 ms** | 0 | General-domain contract validation: Pydantic models, JSON Schemas, length bounds, required sections. |
139
165
  | **`LogitGate`** | Layer 1 | **~40–110 ms** | 0 | Calibrated binary classification from first-token logits using symmetric log-odds debiasing ($\bar{L} = \frac{L_1 + L_2}{2T}$). |
166
+ | **`MultiRubricGate`** | Layer 1 | **~50–140 ms** | 0 | Atomic multi-rubric verification sharing KV prefix. Weakest-link aggregation (fails if any criterion fails). |
140
167
  | **`ChoiceGate`** | Layer 1 | **~40–120 ms** | 0 | Discrete K-ary routing and intent classification across arbitrary string options with permutation averaging. |
141
168
  | **`ScoreGate`** | Layer 1 | **~50–160 ms** | 0 | Continuous probability-weighted rubric scoring (𝔼[S] = Σ s · P(s)) with inverted scale debiasing. |
142
169
  | **`JudgeGate`** | Layer 2 | **1.5–5.0 s** | 60–300 | Generative System-2 reasoning judge with reason-then-verdict parsing and self-healing 1-token logit probes. |
@@ -146,6 +173,73 @@ for rubric, v in zip(rubrics, verdicts):
146
173
 
147
174
  ---
148
175
 
176
+ ## Where LogitGate Fits (and Where It Doesn't)
177
+
178
+ | Tool | What it does | How LogitGate relates |
179
+ | :--- | :--- | :--- |
180
+ | **DeepEval / G-Eval** | Offline evaluation suites and CI scoring; G-Eval weights score tokens by probability. | Complementary. LogitGate is the *inline* path: a sub-100 ms check you can put between agent turns. It ships a DeepEval integration (`LogitGateMetric`) for suites. |
181
+ | **RouteLLM** | Picks *which model generates* a response, before generation. | Different stage. LogitGate judges the output *after* generation. `ChoiceGate` can route, but on content, not on model cost. |
182
+ | **Flow-Judge, Lynx, Prometheus 2** | Dedicated fine-tuned judge models that reason autoregressively. | LogitGate is non-autoregressive and model-agnostic. It projects a verdict from the first-token logits of a model you already serve, with no extra VRAM. A fine-tuned judge works fine as the Layer-2 `JudgeGate` backend. |
183
+
184
+ **When not to use it:**
185
+
186
+ - **No logprobs, no LogitGate.** APIs that don't expose `top_logprobs` (e.g. Anthropic) can't host Layer 1. OpenAI caps `top_logprobs` at 20, which is enough for binary and small-K gates.
187
+ - **Multi-step reasoning rubrics** ("is this 12-step derivation correct?") exceed what a single forward pass can judge. Accuracy drops on our reasoning-depth suite ([falsification boundaries](docs/benchmarks.md)). Let those escalate to `JudgeGate`, or decompose them into atomic rubrics.
188
+ - **Tiny models on CPU.** 1–1.5B models are fast but reach 58–63% cascade accuracy on our multi-domain suite. Measure before you trust them.
189
+
190
+ ---
191
+
192
+ ## Reproduce the Numbers on Your Hardware
193
+
194
+ ```bash
195
+ logitgate benchmark --backend ollama://localhost:11434 --model qwen2.5:7b
196
+ logitgate benchmark --backend vllm://localhost:8000/v1 --model Qwen/Qwen2.5-7B-Instruct \
197
+ --dataset benchmarks/data/multi_domain_24.json --repeat 3 --json > results.json
198
+ ```
199
+
200
+ The command runs the same cases through three configurations and reports p50/p95 latency, generated tokens, LLM calls per case, accuracy, and the cascade's fast-path rate:
201
+
202
+ 1. A generative `JudgeGate` alone (`think=False` by default, so the baseline is a *fast* judge)
203
+ 2. A single `LogitGate`
204
+ 3. `LogitGate >> JudgeGate` (`on_ambiguous_only`)
205
+
206
+ Modes run as separate passes after an untimed warm-up, so model load time isn't charged to whichever mode runs first. `--json` records the LogitGate version, backend, model, platform, and a dataset hash, so a result can be traced to exactly what produced it. With the debiased default, LogitGate makes **two** forward passes per check, and the report counts both.
207
+
208
+ ---
209
+
210
+ ## Custom Gates & Routing Traits
211
+
212
+ `Cascade` never checks concrete classes. It routes on three traits declared on every gate, so wrappers, decorators, and your own gates get the same short-circuiting as the built-ins:
213
+
214
+ | Trait | Meaning | Built-ins |
215
+ | :--- | :--- | :--- |
216
+ | `layer_tier` | `0` deterministic (failures are final), `1` single forward pass (failures may escalate), `2` generative | `ASTGate`=0, `LogitGate`/`ChoiceGate`/`ScoreGate`=1, `JudgeGate`=2 |
217
+ | `can_fast_path` | A PASSED verdict skips the next generative gate | `False` for `ASTGate` (a valid schema says nothing about semantics) |
218
+ | `escalation_policy` | When a tier-2 gate runs: `on_ambiguous_or_fail`, `on_ambiguous_only`, `always` | `JudgeGate(trigger=...)` |
219
+
220
+ ```python
221
+ from logitgate import BaseGate, DecisionStatus, JudgeGate, LayerTrace, Verdict
222
+
223
+ class RegexPIIGate(BaseGate):
224
+ """Deterministic: a hit is a decisive FAIL; a miss proves nothing semantic."""
225
+
226
+ name = "RegexPII"
227
+ layer_tier = 0
228
+ can_fast_path = False
229
+
230
+ def evaluate(self, candidate, **kwargs):
231
+ leaked = "@" in str(candidate)
232
+ status = DecisionStatus.FAILED if leaked else DecisionStatus.PASSED
233
+ trace = LayerTrace(layer_name=self.name, status=status, latency_ms=0.01)
234
+ return Verdict(passed=not leaked, status=status, triggered_layer=self.name,
235
+ total_latency_ms=0.01, traces=[trace])
236
+
237
+ pipeline = RegexPIIGate() >> JudgeGate(rubric="Does the reply avoid leaking personal data?")
238
+ print(pipeline.evaluate("Contact jane@example.com").triggered_layer) # RegexPII; judge never called
239
+ ```
240
+
241
+ ---
242
+
149
243
  ## Benchmark Highlights
150
244
 
151
245
  Across a 24-item multi-domain suite (RAG groundedness, customer support policy, SQL safety, and Python security), `LogitGate` resolves **95.8%–100.0% of cases at Layers 0 and 1**, avoiding multi-token generation:
@@ -198,13 +292,11 @@ For complete research data, mathematical derivations, and production recipes, ex
198
292
 
199
293
  ## Common Recipes
200
294
 
201
- ### Multi-Rubric Batch Evaluation (`evaluate_batch`)
295
+ ### Multi-Rubric Batch Evaluation (`MultiRubricGate`)
202
296
  Evaluates multiple rubrics against a single candidate artifact while anchoring the candidate at Token 0 to share 95–99% of prefix tokens in KV-cache:
203
297
 
204
298
  ```python
205
- from logitgate import LogitGate
206
-
207
- gate = LogitGate("Base rubric")
299
+ from logitgate import MultiRubricGate
208
300
 
209
301
  rubrics = [
210
302
  "Does the code include full type annotations?",
@@ -212,8 +304,9 @@ rubrics = [
212
304
  "Does the code implement proper error handling?",
213
305
  ]
214
306
 
307
+ gate = MultiRubricGate(rubrics)
215
308
  candidate_code = "def fetch(url: str, timeout: int = 10) -> str: pass"
216
- verdicts = gate.evaluate_batch(candidate_code, rubrics)
309
+ verdicts = gate.evaluate_each(candidate_code)
217
310
 
218
311
  for rubric, v in zip(rubrics, verdicts):
219
312
  print(f"[{'PASS' if v.passed else 'FAIL'}] P={v.probability:.2f} | {rubric}")
@@ -247,7 +340,9 @@ from logitgate import ScoreGate
247
340
 
248
341
  scorer = ScoreGate(
249
342
  rubric="Rate the architectural code quality from 1 (disastrous) to 5 (flawless):",
250
- scale=(1, 5),
343
+ min_score=1,
344
+ max_score=5,
345
+ threshold=3.5,
251
346
  symmetric_debiasing=True, # Evaluates 1->5 and 5->1 to eliminate scale anchoring
252
347
  )
253
348
 
@@ -272,10 +367,18 @@ cat schema.json | logitgate eval --rubric "Must be valid JSON schema"
272
367
  # Replay and explain a frozen failure snapshot from CI
273
368
  logitgate explain .logitgate/failures/latest.json
274
369
 
370
+ # Inspect margins, entropy, and label-order bias without calling any model
371
+ logitgate explain --offline
372
+
373
+ # Judge vs LogitGate vs cascade latency/cost on your backend
374
+ logitgate benchmark --backend "ollama://localhost:11434" --model "qwen2.5:7b"
375
+
275
376
  # Auto-calibrate model temperature on micro-dataset
276
377
  logitgate calibrate --backend "ollama://localhost:11434" --model "qwen2.5:14b"
277
378
  ```
278
379
 
380
+ Exit codes: `0` passed / ok, `1` verdict failed or bad input, `2` backend unreachable or misconfigured.
381
+
279
382
  ---
280
383
 
281
384
  ## BibTeX Citation
@@ -287,7 +390,7 @@ If you use LogitGate in your research or production systems, please cite it as f
287
390
  author = {Jason Bradley},
288
391
  title = {LogitGate: Calibrated First-Token Logit Evaluation and Cascaded Routing for LLMs},
289
392
  year = {2026},
290
- version = {0.2.2},
393
+ version = {0.3.0},
291
394
  url = {https://github.com/jasonbdly/LogitGate},
292
395
  license = {Apache-2.0}
293
396
  }
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "logitgate"
7
- version = "0.2.2"
7
+ version = "0.3.0"
8
8
  description = "Fast, calibrated first-token logit evaluation and 3-layer cascaded routing for LLMs."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -22,8 +22,6 @@ classifiers = [
22
22
  "Programming Language :: Python :: 3.11",
23
23
  "Programming Language :: Python :: 3.12",
24
24
  "Programming Language :: Python :: 3.13",
25
- "Programming Language :: Python :: 3.14",
26
- "Programming Language :: Python :: 3.15",
27
25
  "Topic :: Scientific/Engineering :: Artificial Intelligence",
28
26
  ]
29
27
  dependencies = [
@@ -62,6 +60,8 @@ all = [
62
60
  [tool.pytest.ini_options]
63
61
  asyncio_mode = "auto"
64
62
  asyncio_default_fixture_loop_scope = "function"
63
+ # Many tests rely on the implicit offline stub on purpose; the warning has dedicated tests.
64
+ filterwarnings = ["ignore::logitgate.exceptions.StubBackendWarning"]
65
65
 
66
66
  [tool.mypy]
67
67
  warn_unused_ignores = true