vllm-jev-decison

September 18, 2026 · View on GitHub

Classification-only typed decisions for compatible vLLM language models.

中文 · Installation & usage · Examples · Validation

Supply text, a question and a finite JSON Schema. The plugin scores candidate labels, selects typed values, and constructs the result in code. No free-form answer generation, mixed inference, or generative fallback is available. Unsupported schemas are rejected before model inference.

Classification architecture

This is an independent Jev-like interface, not a reproduction of Jev's model or a promise of equivalent accuracy or speed. It requires no model-specific head, weight training or vLLM source patch.

Install

Target API: vLLM 0.29.0, Python 3.11+. Install in the server environment:

git clone https://github.com/siliconkernel/vllm-jev-decison.git
cd vllm-jev-decison
pip install '.[vllm]'
vllm-jev-decison doctor --model YOUR_MODEL
export VLLM_PLUGINS="${VLLM_PLUGINS:+$VLLM_PLUGINS,}jev-decison"
vllm serve YOUR_MODEL --logprobs-mode raw_logprobs

If that vLLM version is already installed, use pip install .. The plugin needs max_logprobs at 16 or higher; vLLM's default of 20 already satisfies this, so raise it only if your deployment lowered it. raw_logprobs is also the vLLM default and is passed explicitly so the requirement survives a default change. doctor --model additionally checks that the model encodes the candidate labels A-P as distinct single tokens, which the backend requires; it exits non-zero when they do not, so a mismatch surfaces before deployment rather than as a disabled endpoint. Preserve other required plugins in the allowlist. Configure VLLM_API_KEY or vLLM --api-key for authentication. The package is installed from this repository, not a claimed PyPI release. Full installation and troubleshooting guide.

Use

curl http://localhost:8000/plugins/jev-decison/infer \
  -H 'Content-Type: application/json' \
  --data-binary @examples/request.json

Add -H "Authorization: Bearer $VLLM_API_KEY" when authentication is configured. The example request classifies a customer message into an enum category and a boolean urgency flag:

{
  "state": "My card was charged twice. Please refund me urgently.",
  "question": "Categorize this request and identify urgency.",
  "schema": {
    "type": "object",
    "properties": {
      "category": {"enum": ["billing", "technical", "other"]},
      "urgent": {"type": "boolean"}
    },
    "required": ["category", "urgent"],
    "additionalProperties": false
  },
  "mode": "classify",
  "min_confidence": 0.8
}

mode is optional and only accepts classify. auto, generate, output-token budgets and nonfinite fields are rejected before any model call. Nonfinite schemas return HTTP 422; a rejected mode value is a request-field error, returned as HTTP 400 by the native plugin and HTTP 422 by the bridge's plain FastAPI stack. No fallback is run.

The response contains accepted, value, per-field decisions, and usage. If any classified field misses the threshold, accepted=false and value=null. Selected values in decisions are diagnostic proposals, not accepted results. GET /plugins/jev-decison/capabilities reports the active backend and limits. The Python client uses JEV_DECISON_URL and optional JEV_DECISON_API_KEY.

Supported output schemas

SchemaBehavior
Enum with at most 16 valuesScore candidate labels and select a value
BooleanClassify false / true
Bounded integer interval, at most 16 valuesClassify the numeric value
Constant or nullConstruct directly, without a model call
Closed, fully required object of supported fieldsClassify fields independently; supports nesting
Free text, open numbers, optional fields, arbitrary arrays or unsupported joint constraintsReject before inference

Explicit finite enum/const values may themselves be objects or arrays; the plugin selects the entire declared value. It does not generate their contents. Every completed result is validated against the root schema.

Limits: 32 fields, 16 candidates per field, 64,000 input characters, 32 KB schema and 20 JSON nesting levels. That depth counts every JSON level, not just objects: each nested object costs two levels, so roughly 9 levels of nested objects fit. Repeated enum values are collapsed, since two labels sharing one value would split its probability between them. Draft 2020-12 is used; references are rejected in v0.1. format is an annotation, not an enforced format checker.

How classification works

Candidate values map to tokenizer-checked single-token labels A–P. The engine computes next-token logprobs, the adapter reads the requested candidate scores, and argmax selects a value. The model never serializes the output object's keys or selected values; Python builds them from the declared schema and candidate table.

The portable backend still uses the ordinary vLLM sampler with max_tokens=1. Its one output token is counted as a classification transport token, even though its text is ignored. generated_tokens is always zero for this API; that does not mean zero output tokens or a model with a new classification head. Multiple fields create multiple engine requests, and input cost grows faster than the field count: every nonroot field repeats the whole output schema for context, and that schema itself grows with the field count. Measured on one model, going from 1 to 32 boolean fields over the same input multiplied total input tokens by 108, not by 32. Prefix caching recovers part of it but shrinks as fields are added (43% at four fields, 19% at 32), because the shared state becomes a smaller share of each prompt. Budget for wide schemas accordingly. Measurement, script. At most eight requests are active per API process by default. JEV_DECISON_CONCURRENCY (1-256) and JEV_DECISON_TIMEOUT (1-3600 seconds) change that; the gate is per API process, so one wide request can delay others. capabilities reports the values actually in effect. vLLM may batch them. This is not one forward for all questions, not a sampler-bypass patch, and not retained-session KV fusion.

Confidence and validity

Conditional candidate probabilities are not calibrated correctness estimates. candidate_mass separately reports raw vocabulary probability assigned to the labels, and it is worth reading: it catches failures confidence cannot see. Asking a question the labels cannot answer still returns confidence 0.9985, while mass drops to 0.000004. Treat state as untrusted. Text inside it can steer the decision — three of four attempts did, every one at confidence 1.0000 — and the system prompt is not a defence. Successful steering pushed mass below 0.26 while normal decisions sat at 1.0, so low mass is a useful warning sign, though not a calibrated detector and not a security control. Measurement, script. Label order, prompts, model training and distribution shift affect results. Constants have no model confidence score. Schema-valid does not mean task-correct.

Candidate order is measured, not just disclaimed. Across 9 cases and 46 orderings on Qwen3-4B, 8 cases held the same value under every permutation, and one flipped to a wrong value at confidence 0.997. Selected positions were evenly spread (16/15/15), so this is not a simple first-label preference: reordering only moves decisions the model was already close on. min_confidence does not defend against it — the flipped answer clears any usable threshold. If a decision matters, test your own labels under more than one order. Measurement, script.

Integration and evidence

Deployment paths

The native plugin uses vLLM's official EndpointPlugin interface and existing EngineClient. It does not load another model. The optional bridge provides the same classification-only API over an existing vLLM server:

pip install '.[bridge]'
vllm-jev-decison bridge --upstream http://127.0.0.1:8000 --model /model --port 18186

The bridge backend is explicitly http_bridge, not native plugin deployment. It requires /tokenize and requested raw token logprobs, not structured-generation support. See the guide for separate bridge/upstream authentication. Both paths are verified against a live model, and return identical decisions and token accounting for the same cases; the bridge runs were served by an upstream with no plugin loaded.

Intended models are vLLM-supported autoregressive text models with compatible single-token labels and raw requested logprobs. Instruct models are the primary target; pooling-only models, multimodal input, LoRA routing and arbitrary tokenizer combinations are not validated. “Compatible models” does not mean “any model.”

Local tests cover finite planning, rejected generation modes, authentication, abstention, accounting, cancellation and a fake-engine adapter contract. Native GPU plugin startup is verified: the plugin loads into a real vLLM API server on NVIDIA GB10, serves both routes, reconciles its token accounting against the engine's own /metrics counters, and releases engine work when a client disconnects. Those runs were recorded on a vLLM build newer than 0.29.0 that carries the same EndpointPlugin interface. Historical bridge smoke records include the earlier mixed prototype, which is no longer supported; see validation records. No universal speedup, production readiness or semantic-correctness guarantee is claimed.

Development

python3 -m venv .venv
source .venv/bin/activate
pip install -e '.[test,bridge]'
pytest -q
python -m build
python docs/render_diagrams.py

Native smoke runs against a loaded plugin and refuses to record evidence for the bridge; bridge smoke covers the other path; label-order bias measures how much candidate order moves a decision; candidate-mass signal measures what mass detects that confidence does not. All write to a new output directory. Preserve failed records. MIT licensed; vLLM retains its own license.