Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion api/core/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,11 @@


class SentinelModel(nn.Module):
"""Two-layer MLP that accepts arbitrary-length feature vectors."""
"""Two-layer MLP with a fixed-width input contract per model instance.

The default model expects 16 features. Deployments using a different
input dimension must update the API contract and model artifact together.
"""

def __init__(self, input_dim: int = 16, hidden_dim: int = 32, output_dim: int = 1) -> None:
super().__init__()
Expand Down
12 changes: 10 additions & 2 deletions api/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from slowapi.util import get_remote_address
from slowapi.errors import RateLimitExceeded
from api.inference import run_inference
from pydantic import BaseModel
from pydantic import BaseModel, Field
from typing import List, Optional

limiter = Limiter(key_func=get_remote_address)
Expand All @@ -16,8 +16,16 @@
app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler)


FEATURE_DIMENSIONS = 16

class RequestModel(BaseModel):
features: List[float]
"""Fixed-width feature vector required by the shipped SentinelModel."""

features: List[float] = Field(
min_length=FEATURE_DIMENSIONS,
max_length=FEATURE_DIMENSIONS,
description="Exactly 16 numeric features in the model's expected order.",
)


class PromptRequest(BaseModel):
Expand Down
17 changes: 17 additions & 0 deletions tests/test_predict.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from api import main


def test_predict_accepts_exact_model_feature_dimension(client, monkeypatch):
monkeypatch.setattr(main, "run_inference", lambda features: [0.42])

response = client.post("/predict", json={"features": [0.0] * 16})

assert response.status_code == 200
assert response.json() == {"prediction": [0.42]}


def test_predict_rejects_feature_vectors_with_the_wrong_dimension(client):
response = client.post("/predict", json={"features": [0.0] * 15})

assert response.status_code == 422
assert any(error["loc"] == ["body", "features"] for error in response.json()["detail"])
Loading