Metadata-Version: 2.4
Name: llms2jev
Version: 0.3.4
Summary: Inspired by Jev. Turn LLMs into decision engines—score choices, skip the chat.
Project-URL: Homepage, https://github.com/Qingbolan/llms2jev-releases
Project-URL: Repository, https://github.com/Qingbolan/llms2jev-releases
Project-URL: Documentation, https://github.com/Qingbolan/llms2jev-releases/tree/v0.3.4/docs
Project-URL: Benchmarks, https://github.com/Qingbolan/llms2jev-releases/tree/v0.3.4/benchmarks
Author-email: "Silan.Hu" <silan.hu@u.nus.edu>
License-Expression: MIT
License-File: LICENSE
Keywords: candidate-scoring,decision-runtime,semantic-operators
Classifier: Development Status :: 3 - Alpha
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Typing :: Typed
Requires-Python: >=3.8
Provides-Extra: client
Requires-Dist: httpx>=0.27; extra == 'client'
Provides-Extra: mcp
Requires-Dist: httpx>=0.27; extra == 'mcp'
Requires-Dist: mcp<3,>=2.2; (python_version >= '3.10') and extra == 'mcp'
Provides-Extra: ollama
Requires-Dist: httpx>=0.27; extra == 'ollama'
Provides-Extra: server
Requires-Dist: fastapi>=0.115; extra == 'server'
Requires-Dist: httpx>=0.27; extra == 'server'
Requires-Dist: uvicorn>=0.30; extra == 'server'
Provides-Extra: transformers
Requires-Dist: torch<2.5,>=2.0; (python_version < '3.9') and extra == 'transformers'
Requires-Dist: torch>=2.0; (python_version >= '3.9') and extra == 'transformers'
Requires-Dist: transformers<4.47,>=4.46.3; (python_version < '3.9') and extra == 'transformers'
Requires-Dist: transformers>=4.51; (python_version >= '3.9') and extra == 'transformers'
Description-Content-Type: text/markdown

![LLM2Jev — Less generation. Faster decisions. A direct token-scoring path bypasses verbose explanations and answer parsing for bounded semantic workloads.](https://raw.githubusercontent.com/Qingbolan/llms2jev-releases/v0.2.0/docs/assets/readme-hero.png)

# LLM2Jev — Less generation. Faster decisions.

**Classify. Filter. Score. Route.** Jev-style decisions from local language models.

An email pipeline often needs a category, a document pipeline needs a relevance decision, and an agent router needs a destination. Generating an explanation and a JSON object can add work that these applications never use. LLM2Jev tests a narrower execution path: define the possible answers, read binary model scores, and construct the result in Python.

The goal is to reduce decoding and parsing overhead in repeated semantic decisions. **Local measurements show a modest speedup in one configured workload; quality and application-level savings remain workload-dependent.** Candidate scoring repeats input processing, so the approach can also be slower than generating a short label. See the measured results below, including incorrect decisions.

## The idea from Jev

LLM2Jev’s public API design is inspired by [TypeSafe’s Jev](https://typesafe.ai/blog/introducing-system-one-models-and-jev). The `state` plus typed `questions` interface and the `Choice`, `Score`, and `Noul` vocabulary follow [TypeSafe’s published API primitives](https://docs.typesafe.ai/introduction). Credit for these interface concepts belongs to TypeSafe; they are not new primitives introduced by LLM2Jev.

[TypeSafe's Jev](https://typesafe.ai/blog/introducing-system-one-models-and-jev) makes typed decisions over supplied state. It motivates a useful application question: how much work can software avoid when it needs a bounded judgment rather than generated text?

LLM2Jev explores that question using existing language models. It implements Jev-style `Choice`, `Score`, and `Noul` responses through next-token binary scoring. It does not reproduce Jev's architecture, training, calibrated probabilities, or parallel sampler, and external API conformance has not been established. This is an independent project.

TypeSafe publishes large speed and cost improvements for its own workloads and service. Those measurements do not transfer to this adapter. Here, Transformers performs a forward pass with **zero generated tokens**; Ollama requests **one generated token per candidate** to obtain logprobs and reports actual usage. Neither path parses generated text into an answer.

## Concrete workloads

The strongest starting hypothesis is short text, a small fixed answer space, repeated decisions, and an application that consumes the result directly.

| Application | Unstructured input and decision | How the application uses it | Work to account for |
| --- | --- | --- | --- |
| Email or support triage | Message → `Choice` among billing, delivery, technical, and other | Assign a queue; count categories in ordinary code | 500 messages × 4 categories = 2,000 candidate evaluations; no email ingestion or aggregation is bundled |
| Semantic filtering | Passage → `Noul`: does it describe a customer requesting a refund? | Keep matching passages before an expensive downstream analysis | One candidate per passage with instructions-only Noul; choose a threshold against labeled data |
| Model routing | Request → `Choice` among application-defined nano, balanced, and frontier tiers | Application dispatches to the selected model | Three candidates plus the downstream call; routing errors and retries can erase savings |
| Rubric scoring | Ticket → `Score` over explicit urgency levels | Prioritize a review queue | One candidate per level; output is an expected level, not a verified fact |
| Browser action selection | Textual page state plus a short list of known actions → `Choice` | Browser controller validates and executes an action | Each action is a candidate; DOM extraction, planning, execution, and success checks remain outside this library |

These are integration patterns; the refund predicate has a small measured smoke experiment below, while the other workloads remain unmeasured. There is no bundled reproduction of a 500-email speed benchmark or a 7.1-second flight-search agent. For routing, describe operational task requirements in the criteria; model names alone do not teach the scorer which downstream model will succeed.

In a data pipeline, `Noul` can supply a **semantic filter**, `Choice` a **bounded semantic map**, and `Score` a **rubric-based ranking signal**. Applications retain record IDs, apply thresholds, sort, group, and count. LLM2Jev currently provides the per-record decision primitive; it has no dataframe/SQL integration, relational optimizer, or dataset-level operator API. Comparing document pairs is possible by supplying both in `state`, but a naive semantic join still needs one evaluation per pair.

## Install from PyPI

The distribution is now named **`llms2jev`**. Python imports remain `llm2jev`, and
the commands remain `llm2jev`, `llm2jev-serve`, and `llm2jev-mcp`. When migrating an
existing environment, uninstall `llm2jev` before installing `llms2jev`: both
distributions provide the same import package and should not be installed together.

```bash
python -m pip uninstall llm2jev
python -m pip install 'llms2jev[server,mcp]==0.3.4'
```

LLM2Jev is available on [PyPI](https://pypi.org/project/llms2jev/). Install the core Python package directly:

```bash
python -m pip install llms2jev
```

To install the published version used by this README with the dependencies for your runtime:

| Use case | Installation command |
| --- | --- |
| Core Python API and custom runtimes | `python -m pip install 'llms2jev==0.3.4'` |
| Local Transformers inference | `python -m pip install 'llms2jev[transformers]==0.3.4'` |
| Connect to Ollama | `python -m pip install 'llms2jev[ollama]==0.3.4'` |
| Run the HTTP server | `python -m pip install 'llms2jev[server]==0.3.4'` |

Wheel and source archives are also available from the [PyPI release files](https://pypi.org/project/llms2jev/0.3.4/#files).

## Try a decision

The core package has no mandatory third-party runtime dependencies. Install the Transformers extra for local inference; model weights are supplied separately. See the [runtime compatibility details](https://github.com/Qingbolan/llms2jev-releases/blob/v0.3.4/docs/architecture.md#python-38-compatibility) when choosing an interpreter and model family:

```bash
pip install 'llms2jev[transformers]==0.3.4'
```

Supply downloaded model weights; the runtime loads local files only. On Apple Silicon, select `device="mps"` explicitly; the example below uses CPU for portability. The [measured refund example](https://github.com/Qingbolan/llms2jev-releases/blob/v0.3.4/benchmarks/README.md) includes the pinned model download, explicit encoder, and `Yes`/`No` label configuration used in the benchmark.

From a source checkout, start with the deterministic, model-free example:

```bash
uv sync --group dev
uv run python examples/bound_evaluation.py
```

That example checks the application path with synthetic scores. For source development, install `uv sync --extra transformers --group dev`. The following is a general API walkthrough with a compatible local model, not a validated model/encoder configuration:

```python
from llm2jev import Choice, LLM2Jev, Noul, TransformersRuntime

with TransformersRuntime("/path/to/local/model", device="cpu") as runtime:
    triage = LLM2Jev(runtime=runtime, model_identity=runtime.identity).bind(
        model=runtime.identity.name,
        questions={
            "department": Choice(
                instructions="Which queue should handle this customer message?",
                criteria={
                    "billing": "Payments, invoices, or refunds",
                    "delivery": "Shipping, tracking, or missing packages",
                    "other": "Requests outside billing and delivery",
                },
            ),
            "refund_requested": Noul(
                instructions="Does the customer explicitly request a refund?",
            ),
        },
    )
    response = triage.evaluate(
        state="My package never arrived. Please refund my payment.",
    )
    print(response.choices["department"].choice)
    print(response.nouls["refund_requested"].noul)
    print(response.to_json(indent=2))
```

This executes four binary candidates. Actual values depend on the model and prompt; the message intentionally contains both delivery and billing evidence. Binding snapshots reusable rules once. Each evaluation supplies one record, and `iter_evaluate()` processes records lazily and sequentially. Passing a whole list of emails as one `state` asks questions about that list; it does not classify each email separately. Question IDs are result keys: put the predicate in `instructions`, rather than relying on a name such as `refund_requested`.

| Result | Meaning |
| --- | --- |
| `Choice` | Selected option and a normalized candidate distribution |
| `Score` | Expected rubric level, using equally spaced indices from 0, plus a distribution |
| `Noul` | Binary probability conditioned on the selected label pair; explicit true/false criteria use two candidates |

`Choice` and `Score` report `confidence` as distribution concentration, **not calibrated correctness**. For example, raw candidate support `[0.009, 0.001]` and `[0.9, 0.1]` both become `[0.9, 0.1]` with confidence `0.8`. The API does not automatically abstain. Select an application policy on held-out data; an `other` option can help represent scope but does not guarantee rejection. [Probability semantics](https://github.com/Qingbolan/llms2jev-releases/blob/v0.3.4/docs/concepts.md) · [Python workflow](https://github.com/Qingbolan/llms2jev-releases/blob/v0.3.4/docs/guides/python.md).

## Does it actually make local inference faster?

**In the measured configuration, yes—by a modest amount against one-token generation. It is not a general acceleration switch, and the tested small model is not accurate enough for unattended refund decisions.**

Historical measurement of wheel `llms2jev==0.1.0`; these timings are not a fresh benchmark of 0.2.0. Version 0.2.0 has separately passed installed-wheel inference with Qwen2.5 on Python 3.8. See the [component validation record](https://github.com/Qingbolan/llms2jev-releases/blob/v0.3.4/docs/decisions/component-contracts.md). Benchmark hardware: Apple M4, 24 GB RAM; Qwen2.5-0.5B-Instruct; MPS, float32; 12 hand-authored English records × 3 repetitions. The configured predicate uses `Yes`/`No` and the explicit example renderer. The one-token baseline uses the same prompt. Timings include tokenization, model execution, synchronization, and output handling; model loading is excluded.

| Method | Median / p95 per record | Correct unique records | Generated tokens / record |
| --- | ---: | ---: | ---: |
| LLM2Jev, native tail logits | **89.3 / 126.6 ms** | 8/12 | **0** |
| LLM2Jev, original full logits | 109.1 / 220.4 ms | 8/12 | 0 |
| Greedy one-token label | 103.4 / 134.9 ms | 8/12 | 1 |
| Generated JSON, fence-aware parser | 890.1 / 1,438.4 ms | 6/12 | 13 |

Tail projection reduced median scoring time by **18.1%** versus full logits (1.22×), and this scoring path took **13.7% less time** than one-token generation (1.16×). JSON was slower **and less accurate** here; that comparison does not establish equal-quality application savings. It is prompted JSON, not grammar-constrained decoding.

![Measured latency and correctness, including the shortest generated-label baseline](https://raw.githubusercontent.com/Qingbolan/llms2jev-releases/v0.2.0/docs/assets/benchmark-local.png)

These are smoke measurements, not a held-out quality study. The configured binary path missed two of six refund requests and incorrectly accepted two of six negative records. Default rendering with lowercase labels scored every record positive with both tested small Qwen models. No calibration, downstream savings, energy reduction, or universal speedup is claimed. [Raw observations, configuration failures, and reproduction commands](https://github.com/Qingbolan/llms2jev-releases/tree/v0.3.4/benchmarks).

## Watch the actual run

[![Actual installed-wheel run, including an incorrect refund decision](https://raw.githubusercontent.com/Qingbolan/llms2jev-releases/v0.2.0/docs/assets/local-inference-poster.png)](https://github.com/Qingbolan/llms2jev-releases/raw/refs/tags/v0.2.0/docs/assets/local-inference.mp4)

[Play/download the 13-second MP4](https://github.com/Qingbolan/llms2jev-releases/raw/refs/tags/v0.2.0/docs/assets/local-inference.mp4). This historical 0.1.x recording shows a real subprocess at wall-clock speed, including model loading, real probabilities, measured per-call times, and expected versus predicted decisions. Its three demo timings are a separate run from the repeated benchmark. [Timestamped output](https://github.com/Qingbolan/llms2jev-releases/blob/v0.3.4/docs/assets/local-inference.json).

## Where the latency and cost can go

```text
rules ── bind once ──> candidate plan
                            + one record
                            ↓
                     C binary prompts
                            ↓
                 next-token yes/no scores
                            ↓
                   typed decision results
                            ↓
                 application filter / route / count
```

For one record, `C` is the sum of Choice options, Score levels, and Noul candidates. Each prompt contains the state. Transformers batches candidate prompts, but recomputes their input representations; binding is not a KV or prefix cache. Ollama sends candidates sequentially, including through its async adapter.

A useful break-even comparison is **C candidate prefills and their serving overhead** versus **one prefill plus the baseline's output decoding**. Batching can reduce wall-clock time without eliminating input work. Compare against a minimal generated label or constrained structured output, not only a verbose reasoning response. Input length, candidate count, label availability, hardware, and downstream mistakes all matter. [Evaluation protocol](https://github.com/Qingbolan/llms2jev-releases/blob/v0.3.4/docs/evaluation.md).

## Failure workloads and limits

| Workload | Why it can fail or lose its advantage | Application or evaluation response |
| --- | --- | --- |
| Long documents × many categories | Repeated prefills dominate; models without native logit selection still materialize sequence-wide vocabulary logits | Measure input-length/candidate sweeps and peak memory; check runtime context limits |
| Short inputs with a one-token baseline | There is little decoding to remove, while multiple binary prompts add work | Include this baseline; a speedup is not assumed |
| Large semantic joins or hundreds of browser actions | Candidate/pair counts grow quickly; no retrieval or query planner reduces them | Retrieve a shortlist and measure shortlist recall as well as final quality |
| Ambiguous, overlapping, or out-of-scope categories | Independent scores are renormalized into a forced choice, even if all candidates have weak support | Define useful criteria, test out-of-scope records, and evaluate rejection policies |
| Decisions that depend on each other | Questions and candidates are evaluated independently; constraints are not jointly solved | Enforce workflow dependencies and action preconditions in application code |
| Arithmetic, multi-step planning, open-ended extraction, or explanation | Removing decoding does not preserve every reasoning capability; outputs are restricted to supplied alternatives | Evaluate a reasoning/generative baseline or use deterministic code where appropriate |
| Ollama missing either exact label, or overlong context | Missing labels cause an explicit error; upstream context truncation is not currently detected by this adapter | Measure capability failures and enforce a deployment-specific input budget |
| Adversarial text or domain shift | Typed output ensures shape, not correct interpretation or immunity to prompt injection | Include these records in the held-out workload; validate actions separately |

The adapters accept text/JSON state; they do not provide OCR, audio understanding, browser control, or evidence retrieval. Deployment and correctness findings are recorded in the [architecture review](https://github.com/Qingbolan/llms2jev-releases/blob/v0.3.4/docs/decisions/review-resolution.md).

## Existing techniques and this project's contribution

Binary next-token scoring is established: [Qwen3-Reranker](https://huggingface.co/Qwen/Qwen3-Reranker-0.6B) uses yes/no logits for relevance scoring. Semantic data operators are also established in [LOTUS](https://github.com/lotus-data/lotus). TypeSafe publishes a [System One Adapter](https://github.com/typesafe-ai/system-one-adapter-python) for obtaining its decision interface from language models. LLM2Jev does not claim to invent these ideas.

The current contribution is their integration into a small decision runtime: immutable rule compilation, per-execution state snapshots, typed probability assembly, shared Python/HTTP execution, and explicit runtime ownership and capability failures. Unlike the generated-answer adapter, this runtime reads binary token scores and assembles answers without parsing generated probabilities. Its practical value must be established by showing useful decisions at lower total cost or latency at a fixed error budget. No new model or training method is claimed. The measured execution advantage below is limited to its stated configuration and does not establish general application savings. See the [workload evaluation protocol](https://github.com/Qingbolan/llms2jev-releases/blob/v0.3.4/docs/evaluation.md).

## Execution and development

| Adapter | Execution contract | Guide |
| --- | --- | --- |
| `TransformersRuntime` | Local next-token logits; prefill-only; candidate batches | [Python](https://github.com/Qingbolan/llms2jev-releases/blob/v0.3.4/docs/guides/python.md) |
| `OllamaRuntime` | One-token requests; both exact label logprobs required | [Ollama](https://github.com/Qingbolan/llms2jev-releases/blob/v0.3.4/docs/guides/ollama.md) |
| `AsyncOllamaRuntime` | Same sequential scoring policy with async I/O | [Ollama](https://github.com/Qingbolan/llms2jev-releases/blob/v0.3.4/docs/guides/ollama.md) |
| `llm2jev-serve` | Ollama-backed `POST /v1/systemone`, model listing, liveness, optional bearer authentication | [HTTP](https://github.com/Qingbolan/llms2jev-releases/blob/v0.3.4/docs/guides/http.md) |

The root `llm2jev` exports are the public Python API. `application/` orchestrates evaluation through scoring contracts; `runtime/` owns model execution and resources; `inference/` owns the scoring-independent algorithms. `transport/` parses and routes HTTP, while `serving/` composes these parts and owns process lifespan. Runtimes do not import application or inference implementations. [Architecture](https://github.com/Qingbolan/llms2jev-releases/blob/v0.3.4/docs/architecture.md) · [API](https://github.com/Qingbolan/llms2jev-releases/blob/v0.3.4/docs/api.md) · [Privacy](https://github.com/Qingbolan/llms2jev-releases/blob/v0.3.4/docs/privacy.md) · [Documentation index](https://github.com/Qingbolan/llms2jev-releases/blob/v0.3.4/docs/index.md).

```text
src/llm2jev/
├── core/                    Domain values and wire serialization
├── contracts.py             Runtime protocols, labels, identities, BinaryScores
├── application/             Services, bound evaluators, per-call preparation
├── inference/               Compilation, rendering, normalization, assembly
├── runtime/
│   ├── ollama/              HTTP execution, wire policy, configuration
│   ├── transformers/        Tensor execution and provider tokenization
│   └── lifecycle.py         Shared resource state machines
├── transport/               Framework-free parsing and HTTP routes
├── serving/                 App composition, authentication, lifespan, CLI
└── utils/                   Internal JSON and probability primitives
```

Customize prompts with `ProbeEncoder.encode(CandidateProbe)` and `encoder=`, and probability policies with `distribution=`. See the [API and packaging decision](docs/decisions/evidence-api.md).

Use `LLM2Jev(runtime=...)` to inject model execution. Custom runtimes return `BinaryScores`; question/answer JSON contracts are defined in the API reference.

```bash
uv run python -m unittest discover -s tests -v
uv run python -m compileall -q src tests
uv run mypy src/llm2jev
uv build
```

Tests cover deterministic scoring, state isolation, lifecycle, HTTP, and diagnostic privacy. CPU tensor tests need the optional PyTorch dependency and otherwise skip. These checks establish software contracts; the small local-model experiment establishes bounded execution evidence; held-out accuracy, calibration, scaling, and downstream cost remain open evaluation work in the [implementation plan](https://github.com/Qingbolan/llms2jev-releases/blob/v0.3.4/docs/implementation-plan.md).

## CLI and MCP

Version 0.3.4 provides `llm2jev health`, `llm2jev models`, `llm2jev evaluate`, and
`llm2jev-mcp` (stdio). Install them with `pip install 'llms2jev[server,mcp]==0.3.4'`. They share
one HTTP client and call the existing Ollama-backed `llm2jev-serve`; MCP needs
Python 3.10+ and the optional SDK 2.x dependency. See the
[complete CLI/MCP tutorial](https://github.com/Qingbolan/llms2jev-releases/blob/v0.3.4/docs/guides/cli-mcp.md)
for source installation, JSON examples, client configuration, and real-run evidence.

## Contributions — PRs welcome

PRs are welcome: bug fixes, clearer documentation, reproducible evaluations, and runtime improvements tied to a concrete use case. For architectural or public API changes, open an issue first to discuss the behavior and tradeoffs.

Describe the problem, the resulting behavior, and the checks you ran. Keep changes focused, preserve documented contracts, and include regression tests for behavior changes. Performance claims need reproducible measurements with hardware, dependency versions, baselines, and correctness results. Retain source attribution when adapting ideas or code.

See the [contribution guide](https://github.com/Qingbolan/llms2jev-releases/blob/v0.3.4/CONTRIBUTING.md) for setup, architecture rules, and the PR checklist. Submit PRs to [llms2jev-releases](https://github.com/Qingbolan/llms2jev-releases/pulls).

Licensed under [MIT](https://github.com/Qingbolan/llms2jev-releases/blob/v0.3.4/LICENSE).
