← Back
logan-markewich

logan-markewich/jeff

A self-hosted drop-in replacement for TypeSafe's jev, powered by GliFormer.

View on GitHub ↗
classificationencoderglinerjevtypesafe
Stars
280
Forks
21
Watchers
280
Open issues
3
Contributors
1
Language
Python
License
MIT License
Default branch
main
Created Sep 19, 2026Updated Sep 20, 2026

Star growth

Today—
This week—
This month—

Star history will appear here once this repo has been tracked for a couple of days.

README

jeff

A self-hosted implementation of TypeSafe's jev System One API, powered by GLiFormer (400M parameters). Use the official typesafe-sdk by pointing TYPESAFE_BASE_URL at jeff.

Supports choice (pick an option), score (rate on ordered levels), and noul (probability of yes). Cheaper to self-host, but less accurate than jev on reasoning-heavy tasks. See benchmarks.

Quickstart · Deploy · Configuration · API · Development

Quickstart

Requires uv and Python 3.12. Run from the repository root:

uv sync --extra dev
uv run hf download knowledgator/gliformer-large-v1 --local-dir models/gliformer-large-v1
JEFF_API_KEYS=devkey uv run jeff

Serves at http://localhost:8000. Device selection: CUDA → MPS → CPU.

The sync above also installs typesafe-sdk. Save this as example.py:

from typesafe_sdk import Choice, Noul, Score, TypeSafeClient

client = TypeSafeClient(api_key="devkey", base_url="http://localhost:8000")
result = client.system_one(
    "I was charged twice. Please help ASAP.",
    {
        "billing": Noul(instructions="Is this about billing?"),
        "tone": Choice(
            instructions="What is the tone?",
            criteria={"calm": None, "angry": "hostile"},
        ),
        "urgency": Score(
            instructions="How urgent is this?", criteria=["low", "medium", "high"]
        ),
    },
)
print(result.nouls["billing"].noul)
print(result.choices["tone"].choice)
print(result.scores["urgency"].score)

In another terminal:

uv run python example.py

For an existing SDK app, set TYPESAFE_API_KEY=devkey and TYPESAFE_BASE_URL=http://localhost:8000 instead of passing client arguments.

curl example
curl http://localhost:8000/v1/systemone \
  -H 'Authorization: Bearer devkey' \
  -H 'Content-Type: application/json' \
  -d '{
    "state": "The export button crashes in Safari.",
    "model": "jev-latest",
    "questions": {
      "severity": {
        "type": "score",
        "instructions": "How severe?",
        "criteria": ["cosmetic", "degraded", "blocking"]
      }
    }
  }'

Deploy on Modal

Use L4 for the HTTP API. These commands download weights once and deploy with one warm container:

uv run modal setup
uv run modal run deploy/modal_gpu.py::download
JEFF_GPU=L4 JEFF_API_KEYS=k1 uv run modal deploy deploy/modal_gpu.py

For an ephemeral URL with no warm container:

JEFF_API_KEYS=devkey uv run modal serve deploy/modal_gpu.py

Deploy settings: JEFF_GPU=L4, JEFF_MIN_CONTAINERS=1, JEFF_MAX_CONTAINERS=8, JEFF_MAX_INPUTS=64, JEFF_TARGET_INPUTS=16. Server JEFF_* variables are forwarded. The GPU image defaults to batch size 32, batch wait 10 ms, and warmup enabled.

Measured HTTP throughput caps at ~50 requests/s per container; scale containers for more. A10G performs better for direct backend calls and long requests. See results.

CPU / ONNX deployment

ONNX Runtime runs the encoder; the RNN and classification head stay in PyTorch. CPU is a fallback: the measured 8-core Modal deployment was slower and more expensive than jev. On Mac, prefer MPS.

uv sync --extra onnx
uv run python scripts/export_onnx.py models/gliformer-large-v1 --int8
JEFF_BACKEND=onnx JEFF_QUANT=int8 JEFF_THREADS=8 JEFF_API_KEYS=devkey uv run jeff

Or deploy to Modal:

uv run modal run deploy/modal_cpu.py::export
JEFF_CPU=8 JEFF_QUANT=int8 JEFF_API_KEYS=k1 uv run modal deploy deploy/modal_cpu.py

Configuration

Set environment variables before starting the server.

Variable Default Purpose
JEFF_API_KEYS empty (auth off) Comma-separated bearer keys
JEFF_MODEL models/gliformer-large-v1 Local checkpoint path
JEFF_DEVICE auto cuda, mps, or cpu
JEFF_HOST / JEFF_PORT 0.0.0.0 / 8000 Listen address
JEFF_MAX_BATCH / JEFF_MAX_WAIT_MS 16 / 5 Batch size / wait in ms
JEFF_MAX_QUEUE 256 Queued requests before HTTP 529
JEFF_RATE_LIMIT_RPS / JEFF_RATE_LIMIT_BURST 0 (off) / 20 Per-key rate limit
JEFF_MAX_QUESTIONS / JEFF_MAX_LABELS / JEFF_MAX_STATE_CHARS 64 / 64 / 20000 Request limits; exceeded limits return 422
Model and backend settings
Variable Default Purpose
JEFF_MODEL_NAME gliformer-large-v1 Name in responses and model listing
JEFF_MODEL_ALIASES jev-latest,jev Accepted request model aliases
JEFF_BACKEND torch torch or onnx
JEFF_DTYPE bf16 on CUDA, fp32 elsewhere Model precision
JEFF_TEMPERATURE 3.2 Probability calibration; 1 disables scaling
JEFF_ISOLATE nouls Separate encoder passes: none, nouls, all
JEFF_NOUL_MODE yes_no yes_no, single, single_named
JEFF_STATE_FORMAT kv Object/array rendering: kv, json, values
JEFF_ATTN auto auto, flash (CUDA), or eager
JEFF_COMPILE / JEFF_COMPILE_MODE / JEFF_PAD_MULTIPLE 0 / unset / 0 Compilation and padding options
JEFF_WARMUP 0 Warmup at startup
JEFF_QUANT / JEFF_THREADS / JEFF_ONNX_PATH fp32 / auto / auto ONNX precision, thread count, encoder path

For faster local iteration, download knowledgator/gliformer-base-v1, set JEFF_MODEL to its path, and use JEFF_NOUL_MODE=single for usable noul results.

API and compatibility

Endpoint Purpose
POST /v1/systemone Answer classification questions
GET /v1/models List models and aliases
GET /healthz Health check
GET /stats Batcher counters and active configuration

Errors: 401 invalid key, 422 validation or request limit, 429 rate limit (retry-after-ms), 529 full queue. Responses include x-typesafe-request-id, x-jeff-server-ms, and x-jeff-batcher-ms.

The wire format works with the official SDK; model behavior differs:

  • Probabilities: normalized sigmoids, temperature-scaled at 3.2. score uses the raw distribution, so it only matches the weighted average of displayed probabilities at JEFF_TEMPERATURE=1. Confidence uses (p_max - 1/n) / (1 - 1/n).
  • Question independence: nouls get separate encoder passes; choice and score questions share a pass and can affect each other. Set JEFF_ISOLATE=all for independence at extra cost.
  • Tokens: usage.input_tokens counts DeBERTa prompt + text tokens; output_tokens is nominal. Counts are not comparable to jev billing.

Benchmarks

Measured on 1,600 labeled items across eight datasets:

Comparison jeff jev
Sequential p50 latency from a laptop 151 ms (L4 / Modal HTTP) 129 ms
Cost per 1M single-question requests ~$2.6 (L4 / Modal HTTP) ~$15.6
AG News topic accuracy 75.5% 90.5%
JevBench v1.2.2 score (official, rank of 18) 66.9 (#9) 75.3 (#2)
JevBench Intelligence (easy / standard / judge / hard accuracy) 63.9 (100 / 76 / 62 / 38%) 90.4 (100 / 99 / 95 / 74%)

jeff is close on binary sentiment, tied on emotion classification, and substantially behind on irony, reading comprehension, and JevBench's judge and hard tiers. On JevBench its rank comes from cost; it is #14 of 18 on intelligence. Costs depend on workload and utilization. Full results, methodology, and reproduction commands → · JevBench leaderboard →

Development

uv sync --extra dev
uv run pytest -q

Model integration tests need models/gliformer-base-v1; they skip if it is absent. The SDK tests use a live server with a fake backend.

Code: core · backends · server · deploy · bench

License

MIT. See LICENSE.