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.
- {logitgate-0.2.2/src/logitgate.egg-info → logitgate-0.3.0}/PKG-INFO +121 -20
- {logitgate-0.2.2 → logitgate-0.3.0}/README.md +120 -17
- {logitgate-0.2.2 → logitgate-0.3.0}/pyproject.toml +3 -3
- {logitgate-0.2.2 → logitgate-0.3.0}/src/logitgate/__init__.py +83 -8
- {logitgate-0.2.2 → logitgate-0.3.0}/src/logitgate/backends/__init__.py +8 -0
- {logitgate-0.2.2 → logitgate-0.3.0}/src/logitgate/backends/base.py +123 -29
- {logitgate-0.2.2 → logitgate-0.3.0}/src/logitgate/backends/dory_pytorch.py +4 -3
- logitgate-0.3.0/src/logitgate/backends/dory_recurrent.py +113 -0
- {logitgate-0.2.2 → logitgate-0.3.0}/src/logitgate/backends/llamacpp.py +8 -0
- {logitgate-0.2.2 → logitgate-0.3.0}/src/logitgate/backends/ollama.py +71 -85
- {logitgate-0.2.2 → logitgate-0.3.0}/src/logitgate/backends/openai_compat.py +17 -0
- logitgate-0.3.0/src/logitgate/backends/pytorch_native.py +198 -0
- {logitgate-0.2.2 → logitgate-0.3.0}/src/logitgate/backends/recurrent.py +8 -2
- logitgate-0.3.0/src/logitgate/benchmark.py +376 -0
- logitgate-0.3.0/src/logitgate/calibration/__init__.py +11 -0
- logitgate-0.3.0/src/logitgate/calibration/conformal.py +334 -0
- logitgate-0.3.0/src/logitgate/cascade.py +232 -0
- logitgate-0.3.0/src/logitgate/cli.py +681 -0
- {logitgate-0.2.2 → logitgate-0.3.0}/src/logitgate/config.py +39 -1
- {logitgate-0.2.2 → logitgate-0.3.0}/src/logitgate/debiasing/__init__.py +10 -0
- {logitgate-0.2.2 → logitgate-0.3.0}/src/logitgate/debiasing/inversion.py +29 -4
- {logitgate-0.2.2 → logitgate-0.3.0}/src/logitgate/debiasing/permutation.py +178 -0
- logitgate-0.3.0/src/logitgate/exceptions.py +54 -0
- {logitgate-0.2.2 → logitgate-0.3.0}/src/logitgate/gates/__init__.py +4 -2
- logitgate-0.3.0/src/logitgate/gates/ast_gate.py +451 -0
- {logitgate-0.2.2 → logitgate-0.3.0}/src/logitgate/gates/base.py +48 -1
- logitgate-0.3.0/src/logitgate/gates/judge_gate.py +324 -0
- logitgate-0.3.0/src/logitgate/gates/logit_gate.py +804 -0
- logitgate-0.3.0/src/logitgate/hardware/__init__.py +9 -0
- logitgate-0.3.0/src/logitgate/hardware/memory_guard.py +82 -0
- logitgate-0.3.0/src/logitgate/integrations/__init__.py +49 -0
- logitgate-0.3.0/src/logitgate/integrations/langgraph.py +86 -0
- logitgate-0.3.0/src/logitgate/integrations/pytest_plugin.py +143 -0
- {logitgate-0.2.2 → logitgate-0.3.0}/src/logitgate/models.py +23 -0
- {logitgate-0.2.2 → logitgate-0.3.0}/src/logitgate/snapshot.py +107 -0
- {logitgate-0.2.2 → logitgate-0.3.0/src/logitgate.egg-info}/PKG-INFO +121 -20
- {logitgate-0.2.2 → logitgate-0.3.0}/src/logitgate.egg-info/SOURCES.txt +12 -0
- {logitgate-0.2.2 → logitgate-0.3.0}/tests/test_ast_gate.py +59 -1
- {logitgate-0.2.2 → logitgate-0.3.0}/tests/test_backends_and_cli.py +357 -1
- logitgate-0.3.0/tests/test_calibration.py +151 -0
- logitgate-0.3.0/tests/test_cascade_routing.py +303 -0
- logitgate-0.3.0/tests/test_conformal_guarantees.py +64 -0
- {logitgate-0.2.2 → logitgate-0.3.0}/tests/test_debiasing_math.py +85 -1
- logitgate-0.3.0/tests/test_dx_and_tooling.py +407 -0
- {logitgate-0.2.2 → logitgate-0.3.0}/tests/test_logit_gate.py +21 -0
- logitgate-0.3.0/tests/test_multi_rubric_gate.py +282 -0
- {logitgate-0.2.2 → logitgate-0.3.0}/tests/test_usability_and_validation.py +147 -0
- logitgate-0.2.2/src/logitgate/cascade.py +0 -579
- logitgate-0.2.2/src/logitgate/cli.py +0 -264
- logitgate-0.2.2/src/logitgate/exceptions.py +0 -29
- logitgate-0.2.2/src/logitgate/gates/ast_gate.py +0 -560
- logitgate-0.2.2/src/logitgate/gates/judge_gate.py +0 -320
- logitgate-0.2.2/src/logitgate/gates/logit_gate.py +0 -985
- logitgate-0.2.2/src/logitgate/integrations/__init__.py +0 -10
- logitgate-0.2.2/src/logitgate/integrations/langgraph.py +0 -30
- logitgate-0.2.2/src/logitgate/integrations/pytest_plugin.py +0 -68
- {logitgate-0.2.2 → logitgate-0.3.0}/LICENSE +0 -0
- {logitgate-0.2.2 → logitgate-0.3.0}/setup.cfg +0 -0
- {logitgate-0.2.2 → logitgate-0.3.0}/src/logitgate/debiasing/softmax.py +0 -0
- {logitgate-0.2.2 → logitgate-0.3.0}/src/logitgate/integrations/deepeval.py +0 -0
- {logitgate-0.2.2 → logitgate-0.3.0}/src/logitgate/py.typed +0 -0
- {logitgate-0.2.2 → logitgate-0.3.0}/src/logitgate.egg-info/dependency_links.txt +0 -0
- {logitgate-0.2.2 → logitgate-0.3.0}/src/logitgate.egg-info/entry_points.txt +0 -0
- {logitgate-0.2.2 → logitgate-0.3.0}/src/logitgate.egg-info/requires.txt +0 -0
- {logitgate-0.2.2 → logitgate-0.3.0}/src/logitgate.egg-info/top_level.txt +0 -0
- {logitgate-0.2.2 → logitgate-0.3.0}/tests/test_cascade.py +0 -0
- {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.
|
|
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
|
-
|
|
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`.
|
|
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
|
|
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
|
|
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
|
|
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
|
|
189
|
+
from logitgate import MultiRubricGate
|
|
168
190
|
|
|
169
|
-
# Best Practice: Keep rubrics atomic — evaluate multiple criteria via
|
|
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 =
|
|
198
|
+
gate = MultiRubricGate(rubrics)
|
|
177
199
|
|
|
178
200
|
# Evaluates all 3 rubrics in a single batch reusing the shared KV-cache prefix
|
|
179
|
-
|
|
201
|
+
verdict = gate(candidate_code)
|
|
202
|
+
print(f"Passed: {verdict.passed} | {verdict.reason}")
|
|
180
203
|
|
|
181
|
-
|
|
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 (`
|
|
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
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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`.
|
|
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
|
|
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
|
|
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
|
|
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
|
|
136
|
+
from logitgate import MultiRubricGate
|
|
113
137
|
|
|
114
|
-
# Best Practice: Keep rubrics atomic — evaluate multiple criteria via
|
|
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 =
|
|
145
|
+
gate = MultiRubricGate(rubrics)
|
|
122
146
|
|
|
123
147
|
# Evaluates all 3 rubrics in a single batch reusing the shared KV-cache prefix
|
|
124
|
-
|
|
148
|
+
verdict = gate(candidate_code)
|
|
149
|
+
print(f"Passed: {verdict.passed} | {verdict.reason}")
|
|
125
150
|
|
|
126
|
-
|
|
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 (`
|
|
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
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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
|