[BUG] CoercedPredictRequest does not validate quantiles, so POST /predict/bytes accepts invalid values
Assessment
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Newbie friendliness
- 78/100
- Issue type
- Bug
- Clarity
- Clearly specified
- Activity status
- Active
- Tech stack
- python
- Domain
- backend-api-design
Research direction
Start by reading CoercedPredictRequest and the _check_quantiles validator in the request types, then compare how PredictRequest applies it. Trace decode_request to confirm the /predict/bytes path constructs the coerced request directly. Add coverage for empty, out-of-range, and duplicate quantiles on both request types; done means both routes reject them before estimator work.
Written by the indexing model from the issue text.
Description
Describe the bug
CoercedPredictRequest does not validate quantiles, even though #137 ("validate quantiles in PredictRequest and CoercedPredictRequest", commit 74c8278) was meant to cover it and the model's own docstring advertises it. Only PredictRequest got the _check_quantiles validator.
Because POST /predict/bytes builds CoercedPredictRequest directly from the multipart metadata field via decode_request, it never constructs a PredictRequest — so invalid quantiles are accepted on that route and carried all the way into the estimator.
Two user-visible consequences:
quantiles: []returns HTTP 200 with no quantile table, silently.SktimeExecutor.predictguards withif quantiles:, so an empty list is falsy and the quantile forecast is skipped without a word. The same body onPOST /predictis a 422.- Out-of-range and duplicate quantiles are rejected only after a full
fit+ pointpredict. The request fails atpredict_quantileswith HTTP 400, after the server has already paid for an inference pass. This is the same wasted-work problem #134 fixed for unsupported-quantile capability, but quantile values still slip past on the bytes path.
Identical requests therefore behave differently depending on the route — 422 before any work on POST /predict, 400 after a full inference pass on POST /predict/bytes (the route the Python Client uses).
Reproduce the bug
Unit level — CoercedPredictRequest accepts every value _check_quantiles is supposed to reject:
import narwhals as nw
from tserve.types import CoercedPredictRequest
from tserve.types.converters import _to_bytes, decode_request
past = nw.from_dict(
{
"timestamp": ["2024-01-01", "2024-01-02", "2024-01-03", "2024-01-04"],
"sales": [120.0, 135.0, 128.0, 142.0],
},
backend="pyarrow",
)
for bad in ([], [0.0], [1.0], [1.5], [-0.5], [0.5, 0.5]):
req = CoercedPredictRequest(
past=past, time="timestamp", target=["sales"], fh=2, quantiles=bad
)
print("accepted", req.quantiles)
# the POST /predict/bytes path, via decode_request
req = decode_request(
{"time": "timestamp", "target": ["sales"], "fh": 2,
"model": "naive", "quantiles": [1.5, 1.5]},
{"past": _to_bytes(past)},
)
print("decode_request accepted", req.quantiles)
accepted []
accepted [0.0]
accepted [1.0]
accepted [1.5]
accepted [-0.5]
accepted [0.5, 0.5]
decode_request accepted [1.5, 1.5]
Route level — the same five bodies against both predict routes:
quantiles |
POST /predict |
POST /predict/bytes |
|---|---|---|
[] |
422 | 200, quantiles: null |
[0.5, 0.5] |
422 | 400 after fit + predict |
[1.5] |
422 | 400 after fit + predict |
[0.0] |
422 | 400 after fit + predict |
[-0.5] |
422 | 400 after fit + predict |
The 400s carry the estimator's message (values in alpha must lie in the open interval (0, 1), but found value: 1.5) wrapped in the "returned its point forecast but failed on the requested quantiles" text — i.e. the point forecast really was computed first.
Expected behavior
CoercedPredictRequest runs _check_quantiles, like PredictRequest does, so both predict routes reject invalid quantiles before any model work and quantiles: [] is an error rather than a silent no-op.
Versions
main at 74c8278, Python 3.12, macOS.
- Dominant language
- Python
- Stars
- 21
- Forks
- 5
- Avg merge
- 3d 10h
- Merged PRs (30d)
- 2
Getting set up
- Ships a Dockerfile or Docker Compose file
- Has a pull request template
- No contributing guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from sktime/tserve
-
[BUG] target accepts duplicates and the time column, failing later with raw pandas errorsPossibly taken @sun-9545sunoj claimed this today. Open
Difficulty 2/5 1-3 hours Newbie friendliness 84/100
-
[BUG] Multi-row static tables are accepted without validation and silently drop all rows after the firstPossibly taken @abhinav-phi claimed this 11 days ago. Openbug
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
Similar issues
-
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
Maintainers usually reply within 1 day
-
bug z-test-failure
Difficulty 2/5 1-3 hours Newbie friendliness 84/100
torch-spyre/spyre-inference#1598 ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
zilliztech/memsearch#853 ·
Maintainers usually reply within 1 day
-
IPA charactersOpen
Difficulty 2/5 1-3 hours Newbie friendliness 66/100
tywr/Nordwand-Mono#55 ·
Maintainers usually reply within 1 day
-
physical
Difficulty 2/5 1-3 hours Newbie friendliness 74/100
antonyrag/ragleap-core#1339 ·