logitgate 0.2.1__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.1/src/logitgate.egg-info → logitgate-0.3.0}/PKG-INFO +139 -32
  2. {logitgate-0.2.1 → logitgate-0.3.0}/README.md +138 -29
  3. {logitgate-0.2.1 → logitgate-0.3.0}/pyproject.toml +3 -3
  4. {logitgate-0.2.1 → logitgate-0.3.0}/src/logitgate/__init__.py +83 -8
  5. {logitgate-0.2.1 → logitgate-0.3.0}/src/logitgate/backends/__init__.py +8 -0
  6. {logitgate-0.2.1 → logitgate-0.3.0}/src/logitgate/backends/base.py +123 -29
  7. {logitgate-0.2.1 → 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.1 → logitgate-0.3.0}/src/logitgate/backends/llamacpp.py +8 -0
  10. {logitgate-0.2.1 → logitgate-0.3.0}/src/logitgate/backends/ollama.py +71 -85
  11. {logitgate-0.2.1 → 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.1 → 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.1 → logitgate-0.3.0}/src/logitgate/config.py +39 -1
  20. {logitgate-0.2.1 → logitgate-0.3.0}/src/logitgate/debiasing/__init__.py +10 -0
  21. {logitgate-0.2.1 → logitgate-0.3.0}/src/logitgate/debiasing/inversion.py +29 -4
  22. {logitgate-0.2.1 → 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.1 → 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.1 → 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.1 → logitgate-0.3.0}/src/logitgate/models.py +23 -0
  35. {logitgate-0.2.1 → logitgate-0.3.0}/src/logitgate/snapshot.py +107 -0
  36. {logitgate-0.2.1 → logitgate-0.3.0/src/logitgate.egg-info}/PKG-INFO +139 -32
  37. {logitgate-0.2.1 → logitgate-0.3.0}/src/logitgate.egg-info/SOURCES.txt +12 -0
  38. {logitgate-0.2.1 → logitgate-0.3.0}/tests/test_ast_gate.py +59 -1
  39. {logitgate-0.2.1 → 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.1 → 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.1 → 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.1 → logitgate-0.3.0}/tests/test_usability_and_validation.py +147 -0
  48. logitgate-0.2.1/src/logitgate/cascade.py +0 -579
  49. logitgate-0.2.1/src/logitgate/cli.py +0 -264
  50. logitgate-0.2.1/src/logitgate/exceptions.py +0 -29
  51. logitgate-0.2.1/src/logitgate/gates/ast_gate.py +0 -560
  52. logitgate-0.2.1/src/logitgate/gates/judge_gate.py +0 -320
  53. logitgate-0.2.1/src/logitgate/gates/logit_gate.py +0 -985
  54. logitgate-0.2.1/src/logitgate/integrations/__init__.py +0 -10
  55. logitgate-0.2.1/src/logitgate/integrations/langgraph.py +0 -30
  56. logitgate-0.2.1/src/logitgate/integrations/pytest_plugin.py +0 -68
  57. {logitgate-0.2.1 → logitgate-0.3.0}/LICENSE +0 -0
  58. {logitgate-0.2.1 → logitgate-0.3.0}/setup.cfg +0 -0
  59. {logitgate-0.2.1 → logitgate-0.3.0}/src/logitgate/debiasing/softmax.py +0 -0
  60. {logitgate-0.2.1 → logitgate-0.3.0}/src/logitgate/integrations/deepeval.py +0 -0
  61. {logitgate-0.2.1 → logitgate-0.3.0}/src/logitgate/py.typed +0 -0
  62. {logitgate-0.2.1 → logitgate-0.3.0}/src/logitgate.egg-info/dependency_links.txt +0 -0
  63. {logitgate-0.2.1 → logitgate-0.3.0}/src/logitgate.egg-info/entry_points.txt +0 -0
  64. {logitgate-0.2.1 → logitgate-0.3.0}/src/logitgate.egg-info/requires.txt +0 -0
  65. {logitgate-0.2.1 → logitgate-0.3.0}/src/logitgate.egg-info/top_level.txt +0 -0
  66. {logitgate-0.2.1 → logitgate-0.3.0}/tests/test_cascade.py +0 -0
  67. {logitgate-0.2.1 → 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.1
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,28 +63,36 @@ 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
 
78
- ```mermaid
79
- flowchart TD
80
- artifact["Candidate Artifact"] --> L0["Layer 0: ASTGate / ContractGate"]
81
-
82
- L0 -->|"Syntax / Schema Failure"| F0["FAIL: 0 LLM calls (sub-0.1ms)"]
83
- L0 -->|"Passed"| L1["Layer 1: LogitGate"]
84
-
85
- L1 -->|"P >= pass_threshold (>= 0.85)"| P1["PASS: 0 CoT tokens (~40–110ms)"]
86
- L1 -->|"P <= fail_threshold (<= 0.20)"| F1["FAIL: 0 CoT tokens (~40–110ms)"]
87
- L1 -->|"0.20 < P < 0.85 (Ambiguous)"| L2["Layer 2: JudgeGate"]
88
-
89
- L2 -->|"Escalated Critique"| R2["Actionable Diagnostic Report"]
78
+ ```
79
+ Candidate Artifact
80
+ │
81
+ ▼
82
+ ┌──────────────────────────────────────┐
83
+ │ Layer 0: ASTGate / ContractGate ├──► [FAIL: syntax/schema, <0.1ms]
84
+ └──────────────────┬───────────────────┘
85
+ │ Passed
86
+ ▼
87
+ ┌──────────────────────────────────────┐
88
+ │ Layer 1: LogitGate ├──► [PASS: P >= 0.85, ~40–110ms]
89
+ └──────────────────┬───────────────────┤
90
+ │ Ambiguous └──► [FAIL: P <= 0.20, ~40–110ms]
91
+ │ (0.20 < P < 0.85)
92
+ ▼
93
+ ┌──────────────────────────────────────┐
94
+ │ Layer 2: JudgeGate ├──► [Actionable Diagnostic Report]
95
+ └──────────────────────────────────────┘
90
96
  ```
91
97
 
92
98
  1. **Layer 0 (`ASTGate` / `ContractGate`)**: Deterministic checks in **<0.1 ms** on CPU (Python AST syntax, JSON/Pydantic schemas, regex patterns, acyclic DAG topology). Failures reject immediately with zero LLM overhead.
@@ -98,7 +104,7 @@ flowchart TD
98
104
  ## Installation
99
105
 
100
106
  ```bash
101
- # Core package (zero dependencies except pydantic & httpx)
107
+ # Core package: only pydantic + httpx
102
108
  pip install logitgate
103
109
 
104
110
  # With optional framework integrations:
@@ -112,6 +118,28 @@ pip install "logitgate[all]" # All integrations
112
118
 
113
119
  ---
114
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
+
115
143
  ## 30-Second Quickstart
116
144
 
117
145
  ### 1. Single Atomic Rubric (3-Layer Cascaded Fast Path)
@@ -153,26 +181,28 @@ print(f"Tokens Generated: {verdict.total_tokens_generated}") # 0 tokens!
153
181
 
154
182
  ### 2. Multi-Rubric Batching (Prefix-Preserved KV-Cache Reuse)
155
183
 
156
- 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`.
157
185
 
158
- 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:
159
187
 
160
188
  ```python
161
- from logitgate import LogitGate
189
+ from logitgate import MultiRubricGate
162
190
 
163
- # Best Practice: Keep rubrics atomic — evaluate multiple criteria via evaluate_batch
191
+ # Best Practice: Keep rubrics atomic — evaluate multiple criteria via MultiRubricGate
164
192
  rubrics = [
165
193
  "Does the function calculate Fibonacci numbers recursively?",
166
194
  "Does the function explicitly handle non-positive inputs (n <= 0)?",
167
195
  "Does the function include parameter and return type annotations?",
168
196
  ]
169
197
 
170
- gate = LogitGate("Base rubric")
198
+ gate = MultiRubricGate(rubrics)
171
199
 
172
200
  # Evaluates all 3 rubrics in a single batch reusing the shared KV-cache prefix
173
- verdicts = gate.evaluate_batch(candidate_code, rubrics)
201
+ verdict = gate(candidate_code)
202
+ print(f"Passed: {verdict.passed} | {verdict.reason}")
174
203
 
175
- 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)):
176
206
  status = "PASS" if v.passed else "FAIL"
177
207
  print(f"[{status}] P={v.probability:.4f} | {rubric}")
178
208
  ```
@@ -186,6 +216,7 @@ for rubric, v in zip(rubrics, verdicts):
186
216
  | **`ASTGate`** | Layer 0 | **< 0.1 ms** | 0 | Sub-millisecond deterministic checks: Python syntax, forbidden imports, regex, acyclic DAGs, custom callables. |
187
217
  | **`ContractGate`** | Layer 0 | **< 0.1 ms** | 0 | General-domain contract validation: Pydantic models, JSON Schemas, length bounds, required sections. |
188
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). |
189
220
  | **`ChoiceGate`** | Layer 1 | **~40–120 ms** | 0 | Discrete K-ary routing and intent classification across arbitrary string options with permutation averaging. |
190
221
  | **`ScoreGate`** | Layer 1 | **~50–160 ms** | 0 | Continuous probability-weighted rubric scoring (𝔼[S] = Σ s · P(s)) with inverted scale debiasing. |
191
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. |
@@ -195,6 +226,73 @@ for rubric, v in zip(rubrics, verdicts):
195
226
 
196
227
  ---
197
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
+
198
296
  ## Benchmark Highlights
199
297
 
200
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:
@@ -247,13 +345,11 @@ For complete research data, mathematical derivations, and production recipes, ex
247
345
 
248
346
  ## Common Recipes
249
347
 
250
- ### Multi-Rubric Batch Evaluation (`evaluate_batch`)
348
+ ### Multi-Rubric Batch Evaluation (`MultiRubricGate`)
251
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:
252
350
 
253
351
  ```python
254
- from logitgate import LogitGate
255
-
256
- gate = LogitGate("Base rubric")
352
+ from logitgate import MultiRubricGate
257
353
 
258
354
  rubrics = [
259
355
  "Does the code include full type annotations?",
@@ -261,8 +357,9 @@ rubrics = [
261
357
  "Does the code implement proper error handling?",
262
358
  ]
263
359
 
360
+ gate = MultiRubricGate(rubrics)
264
361
  candidate_code = "def fetch(url: str, timeout: int = 10) -> str: pass"
265
- verdicts = gate.evaluate_batch(candidate_code, rubrics)
362
+ verdicts = gate.evaluate_each(candidate_code)
266
363
 
267
364
  for rubric, v in zip(rubrics, verdicts):
268
365
  print(f"[{'PASS' if v.passed else 'FAIL'}] P={v.probability:.2f} | {rubric}")
@@ -296,7 +393,9 @@ from logitgate import ScoreGate
296
393
 
297
394
  scorer = ScoreGate(
298
395
  rubric="Rate the architectural code quality from 1 (disastrous) to 5 (flawless):",
299
- scale=(1, 5),
396
+ min_score=1,
397
+ max_score=5,
398
+ threshold=3.5,
300
399
  symmetric_debiasing=True, # Evaluates 1->5 and 5->1 to eliminate scale anchoring
301
400
  )
302
401
 
@@ -321,10 +420,18 @@ cat schema.json | logitgate eval --rubric "Must be valid JSON schema"
321
420
  # Replay and explain a frozen failure snapshot from CI
322
421
  logitgate explain .logitgate/failures/latest.json
323
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
+
324
429
  # Auto-calibrate model temperature on micro-dataset
325
430
  logitgate calibrate --backend "ollama://localhost:11434" --model "qwen2.5:14b"
326
431
  ```
327
432
 
433
+ Exit codes: `0` passed / ok, `1` verdict failed or bad input, `2` backend unreachable or misconfigured.
434
+
328
435
  ---
329
436
 
330
437
  ## BibTeX Citation
@@ -336,7 +443,7 @@ If you use LogitGate in your research or production systems, please cite it as f
336
443
  author = {Jason Bradley},
337
444
  title = {LogitGate: Calibrated First-Token Logit Evaluation and Cascaded Routing for LLMs},
338
445
  year = {2026},
339
- version = {0.2.1},
446
+ version = {0.3.0},
340
447
  url = {https://github.com/jasonbdly/LogitGate},
341
448
  license = {Apache-2.0}
342
449
  }
@@ -10,28 +10,36 @@
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
 
23
- ```mermaid
24
- flowchart TD
25
- artifact["Candidate Artifact"] --> L0["Layer 0: ASTGate / ContractGate"]
26
-
27
- L0 -->|"Syntax / Schema Failure"| F0["FAIL: 0 LLM calls (sub-0.1ms)"]
28
- L0 -->|"Passed"| L1["Layer 1: LogitGate"]
29
-
30
- L1 -->|"P >= pass_threshold (>= 0.85)"| P1["PASS: 0 CoT tokens (~40–110ms)"]
31
- L1 -->|"P <= fail_threshold (<= 0.20)"| F1["FAIL: 0 CoT tokens (~40–110ms)"]
32
- L1 -->|"0.20 < P < 0.85 (Ambiguous)"| L2["Layer 2: JudgeGate"]
33
-
34
- L2 -->|"Escalated Critique"| R2["Actionable Diagnostic Report"]
25
+ ```
26
+ Candidate Artifact
27
+ │
28
+ ▼
29
+ ┌──────────────────────────────────────┐
30
+ │ Layer 0: ASTGate / ContractGate ├──► [FAIL: syntax/schema, <0.1ms]
31
+ └──────────────────┬───────────────────┘
32
+ │ Passed
33
+ ▼
34
+ ┌──────────────────────────────────────┐
35
+ │ Layer 1: LogitGate ├──► [PASS: P >= 0.85, ~40–110ms]
36
+ └──────────────────┬───────────────────┤
37
+ │ Ambiguous └──► [FAIL: P <= 0.20, ~40–110ms]
38
+ │ (0.20 < P < 0.85)
39
+ ▼
40
+ ┌──────────────────────────────────────┐
41
+ │ Layer 2: JudgeGate ├──► [Actionable Diagnostic Report]
42
+ └──────────────────────────────────────┘
35
43
  ```
36
44
 
37
45
  1. **Layer 0 (`ASTGate` / `ContractGate`)**: Deterministic checks in **<0.1 ms** on CPU (Python AST syntax, JSON/Pydantic schemas, regex patterns, acyclic DAG topology). Failures reject immediately with zero LLM overhead.
@@ -43,7 +51,7 @@ flowchart TD
43
51
  ## Installation
44
52
 
45
53
  ```bash
46
- # Core package (zero dependencies except pydantic & httpx)
54
+ # Core package: only pydantic + httpx
47
55
  pip install logitgate
48
56
 
49
57
  # With optional framework integrations:
@@ -57,6 +65,28 @@ pip install "logitgate[all]" # All integrations
57
65
 
58
66
  ---
59
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
+
60
90
  ## 30-Second Quickstart
61
91
 
62
92
  ### 1. Single Atomic Rubric (3-Layer Cascaded Fast Path)
@@ -98,26 +128,28 @@ print(f"Tokens Generated: {verdict.total_tokens_generated}") # 0 tokens!
98
128
 
99
129
  ### 2. Multi-Rubric Batching (Prefix-Preserved KV-Cache Reuse)
100
130
 
101
- 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`.
102
132
 
103
- 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:
104
134
 
105
135
  ```python
106
- from logitgate import LogitGate
136
+ from logitgate import MultiRubricGate
107
137
 
108
- # Best Practice: Keep rubrics atomic — evaluate multiple criteria via evaluate_batch
138
+ # Best Practice: Keep rubrics atomic — evaluate multiple criteria via MultiRubricGate
109
139
  rubrics = [
110
140
  "Does the function calculate Fibonacci numbers recursively?",
111
141
  "Does the function explicitly handle non-positive inputs (n <= 0)?",
112
142
  "Does the function include parameter and return type annotations?",
113
143
  ]
114
144
 
115
- gate = LogitGate("Base rubric")
145
+ gate = MultiRubricGate(rubrics)
116
146
 
117
147
  # Evaluates all 3 rubrics in a single batch reusing the shared KV-cache prefix
118
- verdicts = gate.evaluate_batch(candidate_code, rubrics)
148
+ verdict = gate(candidate_code)
149
+ print(f"Passed: {verdict.passed} | {verdict.reason}")
119
150
 
120
- 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)):
121
153
  status = "PASS" if v.passed else "FAIL"
122
154
  print(f"[{status}] P={v.probability:.4f} | {rubric}")
123
155
  ```
@@ -131,6 +163,7 @@ for rubric, v in zip(rubrics, verdicts):
131
163
  | **`ASTGate`** | Layer 0 | **< 0.1 ms** | 0 | Sub-millisecond deterministic checks: Python syntax, forbidden imports, regex, acyclic DAGs, custom callables. |
132
164
  | **`ContractGate`** | Layer 0 | **< 0.1 ms** | 0 | General-domain contract validation: Pydantic models, JSON Schemas, length bounds, required sections. |
133
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). |
134
167
  | **`ChoiceGate`** | Layer 1 | **~40–120 ms** | 0 | Discrete K-ary routing and intent classification across arbitrary string options with permutation averaging. |
135
168
  | **`ScoreGate`** | Layer 1 | **~50–160 ms** | 0 | Continuous probability-weighted rubric scoring (𝔼[S] = Σ s · P(s)) with inverted scale debiasing. |
136
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. |
@@ -140,6 +173,73 @@ for rubric, v in zip(rubrics, verdicts):
140
173
 
141
174
  ---
142
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
+
143
243
  ## Benchmark Highlights
144
244
 
145
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:
@@ -192,13 +292,11 @@ For complete research data, mathematical derivations, and production recipes, ex
192
292
 
193
293
  ## Common Recipes
194
294
 
195
- ### Multi-Rubric Batch Evaluation (`evaluate_batch`)
295
+ ### Multi-Rubric Batch Evaluation (`MultiRubricGate`)
196
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:
197
297
 
198
298
  ```python
199
- from logitgate import LogitGate
200
-
201
- gate = LogitGate("Base rubric")
299
+ from logitgate import MultiRubricGate
202
300
 
203
301
  rubrics = [
204
302
  "Does the code include full type annotations?",
@@ -206,8 +304,9 @@ rubrics = [
206
304
  "Does the code implement proper error handling?",
207
305
  ]
208
306
 
307
+ gate = MultiRubricGate(rubrics)
209
308
  candidate_code = "def fetch(url: str, timeout: int = 10) -> str: pass"
210
- verdicts = gate.evaluate_batch(candidate_code, rubrics)
309
+ verdicts = gate.evaluate_each(candidate_code)
211
310
 
212
311
  for rubric, v in zip(rubrics, verdicts):
213
312
  print(f"[{'PASS' if v.passed else 'FAIL'}] P={v.probability:.2f} | {rubric}")
@@ -241,7 +340,9 @@ from logitgate import ScoreGate
241
340
 
242
341
  scorer = ScoreGate(
243
342
  rubric="Rate the architectural code quality from 1 (disastrous) to 5 (flawless):",
244
- scale=(1, 5),
343
+ min_score=1,
344
+ max_score=5,
345
+ threshold=3.5,
245
346
  symmetric_debiasing=True, # Evaluates 1->5 and 5->1 to eliminate scale anchoring
246
347
  )
247
348
 
@@ -266,10 +367,18 @@ cat schema.json | logitgate eval --rubric "Must be valid JSON schema"
266
367
  # Replay and explain a frozen failure snapshot from CI
267
368
  logitgate explain .logitgate/failures/latest.json
268
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
+
269
376
  # Auto-calibrate model temperature on micro-dataset
270
377
  logitgate calibrate --backend "ollama://localhost:11434" --model "qwen2.5:14b"
271
378
  ```
272
379
 
380
+ Exit codes: `0` passed / ok, `1` verdict failed or bad input, `2` backend unreachable or misconfigured.
381
+
273
382
  ---
274
383
 
275
384
  ## BibTeX Citation
@@ -281,7 +390,7 @@ If you use LogitGate in your research or production systems, please cite it as f
281
390
  author = {Jason Bradley},
282
391
  title = {LogitGate: Calibrated First-Token Logit Evaluation and Cascaded Routing for LLMs},
283
392
  year = {2026},
284
- version = {0.2.1},
393
+ version = {0.3.0},
285
394
  url = {https://github.com/jasonbdly/LogitGate},
286
395
  license = {Apache-2.0}
287
396
  }
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "logitgate"
7
- version = "0.2.1"
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