[Engine] Chat and Messages silently coerce boolean and fractional token limits

Aperta Adatta ai principianti
#4,037 0 commenti 0 reazioni 0 assegnatari Vedi su GitHub

I maintainer di solito rispondono entro 3 giorni

@XiaoBinGan ci sta già lavorando.

Dal 27/10/2026.

  • #4314 di @XiaoBinGan — aperta

Valutazione

Difficoltà
2/5
Tempo stimato
1-3 ore
Idoneità per principianti
72/100
Tipo di issue
Bug
Chiarezza
Specificata chiaramente
Stato di attività
Attiva
Stack tecnologico
python
Ambito
api, backend

Direzione di ricerca

Inizia in serve/server.py, nelle conversioni del limite di token intorno alle righe 7147 e 7395, poi leggi la convalida Responses correlata e il comportamento di Service.prepare intorno alle righe 4889-4898. Riproduci le richieste Chat e Messages elencate e confronta la loro convalida con quella di /v1/responses. Il lavoro è completato quando i limiti booleani e frazionari restituiscono HTTP 400, mentre i valori sentinella documentati -1 e 0 continuano a essere accettati.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Descrizione

Area

[Engine] (speed, kernels, models, the server and API)

Desktop app, Terminal or source?

Source (START-HERE.bat, setup.sh or a build)

What happened

/v1/chat/completions accepts true and 2.5 in either max_tokens or max_completion_tokens; my requests produced 1 and 2 completion tokens respectively. /v1/messages does the same for max_tokens. Chat max_tokens: false also succeeds and allows the 81-token answer to finish.

/v1/responses rejects the tested max_output_tokens: true and 2.5 requests with HTTP 400 and "max_output_tokens must be a positive whole number".

Expected: reject boolean and fractional token limits with HTTP 400 instead of silently converting them to a different generation budget. Preserve the documented integer -1 and 0 sentinel values.

Chat and Messages convert their selected token limit with int(...) at serve/server.py:7147 and serve/server.py:7395. This converts True to 1 and 2.5 to 2. With no other cap field supplied, False falls through the or expression to 0; Service.prepare treats that as the remaining context allowance (serve/server.py:4889-4898).

Smallest steps or command that reproduce it

The same build and config as in #4035 (commit 7dd5cf18). Server:

cd /content/Strata
python3 serve/server.py --engine strata --config /content/area3_config.json --host 127.0.0.1 --port 18091

Client:

import json
import urllib.error
import urllib.request

base = "http://127.0.0.1:18091"
prompt = "Count from 1 to 30, separated by single spaces, and nothing else."


def post(path, body):
    req = urllib.request.Request(
        base + path,
        data=json.dumps(body).encode(),
        headers={"Content-Type": "application/json"}
    )
    try:
        with urllib.request.urlopen(req, timeout=300) as response:
            return response.status, json.load(response)
    except urllib.error.HTTPError as e:
        return e.code, json.loads(e.read() or b"{}")


def chat(field, value):
    body = {"model": "strata", "messages": [{"role": "user", "content": prompt}],
            "temperature": 0, "reasoning_effort": "none", field: value}
    status, r = post("/v1/chat/completions", body)
    if status != 200:
        return {"status": status, "error": r.get("error", r)}
    c = r["choices"][0]
    return {"status": status, "finish_reason": c["finish_reason"],
            "completion_tokens": r["usage"]["completion_tokens"], "content": c["message"]["content"]}


def messages(value):
    body = {"model": "strata", "messages": [{"role": "user", "content": prompt}],
            "temperature": 0, "thinking": {"type": "disabled"}, "max_tokens": value}
    status, r = post("/v1/messages", body)
    if status != 200:
        return {"status": status, "error": r.get("error", r)}
    return {"status": status, "stop_reason": r["stop_reason"], "output_tokens": r["usage"]["output_tokens"],
            "content": "".join(b.get("text", "") for b in r["content"] if b.get("type") == "text")}


def responses(value):
    body = {"model": "strata", "input": prompt, "temperature": 0, "reasoning": {"effort": "none"},
            "max_output_tokens": value}
    status, r = post("/v1/responses", body)
    if status != 200:
        return {"status": status, "error": r.get("error", r)}
    return {"status": status, "response_status": r.get("status"), "incomplete": r.get("incomplete_details"),
            "output_tokens": (r.get("usage") or {}).get("output_tokens")}


cases = [
    ("chat max_tokens", lambda v: chat("max_tokens", v), [2, 1, True, False, 2.5]),
    ("chat max_completion_tokens", lambda v: chat("max_completion_tokens", v), [2, True, 2.5]),
    ("messages max_tokens", messages, [2, True, 2.5]),
    ("responses max_output_tokens", responses, [2, True, 2.5]),
]
for name, fn, values in cases:
    for v in values:
        print(json.dumps({"case": name, "value": v, **fn(v)}))

These cases are a subset of a larger client I ran; the log lines below are excerpts from that run, unchanged.

Reproduction status

Reproduced locally

Strata version

Commit 7dd5cf18e68c639a8773e096c5c79f971ea2a9f5 (main), engine 0.1.49.2

Graphics card, memory and system

NVIDIA RTX PRO 6000 Blackwell Server Edition 97,887 MiB · 176 GiB RAM · Ubuntu 24.04.4 LTS (Linux 6.6.122+, Google Colab), driver 580.82.07, CUDA 13.0.88, GCC 13.3.0, Python 3.13.16, AMD EPYC 9B45 (48 logical CPUs)

Model, size and settings

Qwen3.8-Flash-Next GSQ-RCO Q2_0 · 4096 context · INT8 KV · the config in #4035

Logs

Excerpts from my run, 2026-10-25:

{"case": "chat max_tokens", "value": 2, "status": 200, "finish_reason": "length", "completion_tokens": 2, "content": "1 "}
{"case": "chat max_tokens", "value": 1, "status": 200, "finish_reason": "length", "completion_tokens": 1, "content": "1"}
{"case": "chat max_tokens", "value": true, "status": 200, "finish_reason": "length", "completion_tokens": 1, "content": "1"}
{"case": "chat max_tokens", "value": false, "status": 200, "finish_reason": "stop", "completion_tokens": 81, "content": "1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30"}
{"case": "chat max_tokens", "value": 2.5, "status": 200, "finish_reason": "length", "completion_tokens": 2, "content": "1 "}
{"case": "chat max_completion_tokens", "value": 2, "status": 200, "finish_reason": "length", "completion_tokens": 2, "content": "1 "}
{"case": "chat max_completion_tokens", "value": true, "status": 200, "finish_reason": "length", "completion_tokens": 1, "content": "1"}
{"case": "chat max_completion_tokens", "value": 2.5, "status": 200, "finish_reason": "length", "completion_tokens": 2, "content": "1 "}
{"case": "messages max_tokens", "value": 2, "status": 200, "stop_reason": "max_tokens", "output_tokens": 2, "content": "1 "}
{"case": "messages max_tokens", "value": true, "status": 200, "stop_reason": "max_tokens", "output_tokens": 1, "content": "1"}
{"case": "messages max_tokens", "value": 2.5, "status": 200, "stop_reason": "max_tokens", "output_tokens": 2, "content": "1 "}
{"case": "responses max_output_tokens", "value": 2, "status": 200, "response_status": "incomplete", "incomplete": {"reason": "max_output_tokens"}, "output_tokens": 2}
{"case": "responses max_output_tokens", "value": true, "status": 400, "error": {"message": "max_output_tokens must be a positive whole number", "type": "invalid_request_error", "param": "max_output_tokens", "code": null}}
{"case": "responses max_output_tokens", "value": 2.5, "status": 400, "error": {"message": "max_output_tokens must be a positive whole number", "type": "invalid_request_error", "param": "max_output_tokens", "code": null}}

Not checked in this run: streaming requests, numeric-string caps, false in chat max_completion_tokens, Messages max_tokens, or Responses max_output_tokens, or requests supplying both chat cap fields.

Lingua principale
C++
Stelle
11.6k
Fork
1k
Metriche di merge delle PR
Nessuna PR unita negli ultimi 30g

Preparare l'ambiente

Questo progetto non fornisce container di sviluppo, Dockerfile né guida per i contributori, quindi l'ambiente è a tuo carico: parti dal suo README e consulta la nostra guida al primo contributo per i passaggi generali.

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Altre issue di Niko1221/Strata

Tutte le issue di Niko1221/Strata

Issue simili

Altre issue su C++

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.