Signed-off-by: Tianyu Guo <guoty@inferact.ai> Signed-off-by: Isotr0py <Isotr0py@outlook.com> Co-authored-by: Codex <noreply@openai.com> Co-authored-by: Isotr0py <Isotr0py@outlook.com>
223 lines
8.4 KiB
Markdown
223 lines
8.4 KiB
Markdown
# Structured Decisions
|
|
|
|
The `/v1/systemone` endpoint answers a set of typed questions about a state and
|
|
returns a probability for every allowed answer.
|
|
|
|
The endpoint is available by default on generation models. Models without a
|
|
supported read strategy return 501. Supported architectures require
|
|
`--logprobs-mode raw_logprobs` or `processed_logprobs`, since `label_mass` needs
|
|
log probabilities.
|
|
|
|
## How it works
|
|
|
|
1. Each question is one read. The user message is the state, followed by the
|
|
question and its options with the type's labels, and an instruction to
|
|
answer with one label.
|
|
2. The read is the reply's first token. Each label must be one token there,
|
|
or the request returns 400.
|
|
3. The read returns the logprobs of that question's label tokens. Softmax over
|
|
the labels gives the answer's probabilities.
|
|
|
|
Thinking is off unless `chat_template_kwargs` turns it on.
|
|
|
|
Probabilities can differ between identical requests. They depend on which
|
|
requests share a batch and on whether a read hits the prefix cache. Set
|
|
`VLLM_BATCH_INVARIANT=1` for reads that repeat exactly, at a cost in
|
|
throughput.
|
|
|
|
Every read of a request starts with the state, so with
|
|
`--enable-prefix-caching` the state is prefilled once and each further
|
|
question prefills only its own text, then reads one token.
|
|
|
|
## Question types
|
|
|
|
| type | criteria | answer |
|
|
| --- | --- | --- |
|
|
| `choice` | map of option name to a description or `null` | `choice`, `probabilities` by option name, `confidence` |
|
|
| `noul` | `null`, or an object with optional `true` and `false` descriptions | `noul` (probability of yes), `probabilities`, `confidence` |
|
|
| `score` | ordered list of level names | `score` (expected 0-indexed level), `legend`, `probabilities` by level index, `confidence` |
|
|
|
|
A choice has at least one option, a score at least two levels. A question id
|
|
is a non-empty string.
|
|
|
|
## Example
|
|
|
|
```bash
|
|
curl -s http://localhost:8000/v1/systemone \
|
|
-H "Content-Type: application/json" \
|
|
-d '{
|
|
"model": "Qwen/Qwen3-0.6B",
|
|
"state": {"ticket": "My card was charged twice for one order."},
|
|
"questions": {
|
|
"team": {
|
|
"type": "choice",
|
|
"instructions": "Which team should handle this ticket?",
|
|
"criteria": {
|
|
"billing": "payments and refunds",
|
|
"shipping": "deliveries",
|
|
"security": "account access"
|
|
}
|
|
},
|
|
"urgent": {
|
|
"type": "noul",
|
|
"instructions": "Does the customer need a reply within the hour?",
|
|
"criteria": {"true": "the account is locked or money is moving"}
|
|
},
|
|
"tone": {
|
|
"type": "score",
|
|
"instructions": "How upset is the customer?",
|
|
"criteria": ["calm", "annoyed", "furious"]
|
|
}
|
|
},
|
|
"chat_template_kwargs": {"enable_thinking": false}
|
|
}'
|
|
```
|
|
|
|
Response, with probabilities that depend on the model:
|
|
|
|
```json
|
|
{
|
|
"id": "decision-...",
|
|
"object": "structured_decision",
|
|
"created": 1790000000,
|
|
"model": "Qwen/Qwen3-0.6B",
|
|
"answers": {
|
|
"team": {
|
|
"type": "choice",
|
|
"choice": "billing",
|
|
"probabilities": {"billing": 0.97, "shipping": 0.01, "security": 0.02},
|
|
"confidence": 0.90
|
|
},
|
|
"urgent": {
|
|
"type": "noul",
|
|
"noul": 0.02,
|
|
"probabilities": {"yes": 0.02, "no": 0.98},
|
|
"confidence": 0.95
|
|
},
|
|
"tone": {
|
|
"type": "score",
|
|
"score": 0.6,
|
|
"legend": {"0": "calm", "1": "annoyed", "2": "furious"},
|
|
"probabilities": {"0": 0.7, "1": 0.2, "2": 0.1},
|
|
"confidence": 0.65
|
|
}
|
|
},
|
|
"usage": {"input_tokens": 142, "output_tokens": 3},
|
|
"diagnostics": {
|
|
"team": {"label_mass": 0.93, "argmax_is_label": true},
|
|
"urgent": {"label_mass": 0.98, "argmax_is_label": true},
|
|
"tone": {"label_mass": 0.91, "argmax_is_label": true}
|
|
}
|
|
}
|
|
```
|
|
|
|
`diagnostics.label_mass` is the probability the model put on the labels across
|
|
its whole vocabulary. A low value means most of the probability went to tokens
|
|
that are not labels. `confidence` is the chosen option's probability over the
|
|
whole vocabulary: its share of the labels times `label_mass`.
|
|
|
|
## Labels
|
|
|
|
Labels follow the type: a `choice` labels its options `A` to `Z` in the order
|
|
the request lists them, a `noul` labels its options `yes` and `no`, and a
|
|
`score` labels its levels `0` to `9`, so a level's label is its score.
|
|
|
|
## Limits
|
|
|
|
| limit | value |
|
|
| --- | --- |
|
|
| questions per request | 64 |
|
|
| options per `choice` | 26 |
|
|
| levels per `score` | 10 |
|
|
|
|
## Clef-Flash
|
|
|
|
[Clef-Flash](https://huggingface.co/Cloudflare/clef-flash) uses a Qwen3.5-9B
|
|
backbone and a joint schema head to score all questions in one forward pass.
|
|
vLLM detects the head and serves the checkpoint as a pooling model.
|
|
|
|
```bash
|
|
vllm serve Cloudflare/clef-flash --max-model-len 16384
|
|
```
|
|
|
|
Send requests to `/v1/systemone` using `state` and `questions`, as in the
|
|
examples above. `choice`, `noul` and `score` questions are supported.
|
|
|
|
- Text and JSON states use the checkpoint's fixed prompt. JSON is compact
|
|
with sorted keys. `instructions` returns 400; `chat_template_kwargs`
|
|
has no effect.
|
|
- The state is truncated to fit 16,384 tokens, or `max_model_len` if smaller.
|
|
A schema that exceeds this bound returns 400.
|
|
- `images` and `videos` are placed before the state. Videos are sampled at
|
|
2 fps and processed by the Qwen3.5 vision processor. Audio inputs return
|
|
400, and video soundtracks are ignored.
|
|
- Each request performs one pooling read. `usage.input_tokens` counts the
|
|
prompt once, `output_tokens` is 0, and `label_mass` is 1.
|
|
|
|
## Clef-Omni
|
|
|
|
[Clef-Omni](https://huggingface.co/Cloudflare/clef-omni) is a decision model:
|
|
a joint schema head on the Qwen3-Omni thinker scores every option of every
|
|
question in one forward pass. vLLM serves it as a pooling model.
|
|
|
|
```bash
|
|
vllm serve Cloudflare/clef-omni
|
|
```
|
|
|
|
- A request is one read. The prompt is the one the checkpoint's
|
|
`joint_schema_model.py` builds: the state, then every question with its
|
|
options. A JSON state is written as compact JSON with sorted keys.
|
|
- A state too long for the model is truncated to fit, as the reference does.
|
|
A request whose questions alone exceed 64,000 tokens, or `max_model_len`,
|
|
returns 400.
|
|
- `instructions` returns 400, since the prompt is fixed. `chat_template_kwargs`
|
|
has no effect.
|
|
- There are no labels, so `label_mass` is 1 and `confidence` is the chosen
|
|
option's probability. `usage.input_tokens` counts the prompt once, and
|
|
`output_tokens` is 0.
|
|
- `images`, `audio` and `videos` are read as the reference does: placed
|
|
before the state, videos sampled at 2 fps, and a video's soundtrack heard
|
|
with it when every video has one. Audio clips cannot be sent together with
|
|
videos that have soundtracks.
|
|
|
|
## Liquid d1
|
|
|
|
[`LiquidAI/d1-3B`](https://huggingface.co/LiquidAI/d1-3B) is LFM2.5-VL-3B
|
|
post-trained to answer decisions as the first token of its reply. vLLM serves
|
|
it as a generative model.
|
|
|
|
```bash
|
|
vllm serve LiquidAI/d1-3B
|
|
```
|
|
|
|
- The server writes the prompt and reads the label tokens the way the
|
|
`prompt.py` shipped with the checkpoint does, so picks and probabilities
|
|
match `model.system_one()`. `confidence` and `usage` follow the definitions
|
|
on this page instead.
|
|
- A JSON state is written as indented JSON. A `null` state leaves the images,
|
|
or the question alone, as the prompt.
|
|
- `images` are placed before the state and shrunk to at most 1024 x 1024
|
|
pixels, as the reference does.
|
|
- A `choice` whose option names are all single letters is labeled with those
|
|
letters.
|
|
- When each request carries one question over a state that changes from
|
|
request to request, `--no-enable-prefix-caching` lowers latency.
|
|
|
|
## Request fields
|
|
|
|
| field | meaning |
|
|
| --- | --- |
|
|
| `state` | what the questions are about: a string, or JSON that is sent as its JSON text |
|
|
| `questions` | question id to `{type, instructions, criteria}`, asked in this order |
|
|
| `instructions` | optional context placed ahead of the questions |
|
|
| `chat_template_kwargs` | passed to the chat template, for example `{"enable_thinking": false}` |
|
|
| `cache_salt` | optional [prefix-cache salt](../../usage/security.md#cache-salting) applied to every question |
|
|
| `images`, `audio`, `videos` | optional media URLs or data URLs, for models that take them ([Clef-Flash](#clef-flash), images/video; [Clef-Omni](#clef-omni); [Liquid d1](#liquid-d1), images only) |
|
|
|
|
A question with any field other than `type`, `instructions` and `criteria` is
|
|
rejected with a 400.
|
|
|
|
When [tracing](../../../examples/observability/opentelemetry/README.md) is enabled
|
|
with `--otlp-traces-endpoint`, the HTTP `traceparent` and `tracestate` headers are
|
|
forwarded to every question's generation request.
|