Skip to content

Commit cb367dd

Browse files
committed
fix: use ServiceUnavailableResponse for config errors in rlsapi v1
Replace raw dict HTTPExceptions in _get_default_model_id() with ServiceUnavailableResponse so all 503 responses from /v1/infer share the same JSON shape ({"response": ..., "cause": ...}). Add test verifying configuration error and LLM error 503 responses produce identical detail key sets. Signed-off-by: Major Hayden <major@redhat.com>
1 parent 7a4d6d1 commit cb367dd

2 files changed

Lines changed: 46 additions & 10 deletions

File tree

src/app/endpoints/rlsapi_v1.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -133,10 +133,11 @@ def _get_default_model_id() -> str:
133133
if configuration.inference is None:
134134
msg = "No inference configuration available"
135135
logger.error(msg)
136-
raise HTTPException(
137-
status_code=503,
138-
detail={"response": "Service configuration error", "cause": msg},
136+
error_response = ServiceUnavailableResponse(
137+
backend_name="inference service",
138+
cause=msg,
139139
)
140+
raise HTTPException(**error_response.model_dump())
140141

141142
model_id = configuration.inference.default_model
142143
provider_id = configuration.inference.default_provider
@@ -146,10 +147,11 @@ def _get_default_model_id() -> str:
146147

147148
msg = "No default model configured for rlsapi v1 inference"
148149
logger.error(msg)
149-
raise HTTPException(
150-
status_code=503,
151-
detail={"response": "Service configuration error", "cause": msg},
150+
error_response = ServiceUnavailableResponse(
151+
backend_name="inference service",
152+
cause=msg,
152153
)
154+
raise HTTPException(**error_response.model_dump())
153155

154156

155157
async def retrieve_simple_response(

tests/unit/app/endpoints/test_rlsapi_v1.py

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
RlsapiV1SystemInfo,
3131
RlsapiV1Terminal,
3232
)
33+
from models.responses import ServiceUnavailableResponse
3334
from models.rlsapi.responses import RlsapiV1InferResponse
3435
from tests.unit.utils.auth_helpers import mock_authorization_resolvers
3536
from utils.suid import check_suid
@@ -235,7 +236,7 @@ def test_get_default_model_id_success(mock_configuration: AppConfig) -> None:
235236

236237

237238
@pytest.mark.parametrize(
238-
("config_setup", "expected_message"),
239+
("config_setup", "expected_cause"),
239240
[
240241
pytest.param(
241242
"missing_model",
@@ -253,9 +254,9 @@ def test_get_default_model_id_errors(
253254
mocker: MockerFixture,
254255
minimal_config: AppConfig,
255256
config_setup: str,
256-
expected_message: str,
257+
expected_cause: str,
257258
) -> None:
258-
"""Test _get_default_model_id raises HTTPException for invalid configs."""
259+
"""Test _get_default_model_id raises HTTPException with ServiceUnavailableResponse shape."""
259260
if config_setup == "missing_model":
260261
# Config exists but no model/provider defaults
261262
mocker.patch("app.endpoints.rlsapi_v1.configuration", minimal_config)
@@ -269,7 +270,40 @@ def test_get_default_model_id_errors(
269270
_get_default_model_id()
270271

271272
assert exc_info.value.status_code == 503
272-
assert expected_message in str(exc_info.value.detail)
273+
assert expected_cause in str(exc_info.value.detail)
274+
# Verify ServiceUnavailableResponse produces dict with response+cause keys
275+
detail: dict[str, str] = exc_info.value.detail # type: ignore[assignment]
276+
assert set(detail.keys()) == {"response", "cause"}
277+
278+
279+
def test_config_error_503_matches_llm_error_503_shape(
280+
mocker: MockerFixture,
281+
) -> None:
282+
"""Test that configuration error 503s have the same shape as LLM error 503s.
283+
284+
Both _get_default_model_id() configuration errors and APIConnectionError
285+
handlers use ServiceUnavailableResponse, producing identical detail shapes
286+
with 'response' and 'cause' keys.
287+
"""
288+
# Trigger a configuration error 503
289+
mock_config = mocker.Mock()
290+
mock_config.inference = None
291+
mocker.patch("app.endpoints.rlsapi_v1.configuration", mock_config)
292+
293+
with pytest.raises(HTTPException) as config_exc:
294+
_get_default_model_id()
295+
296+
# Build an LLM connection error 503 using the same response model
297+
llm_response = ServiceUnavailableResponse(
298+
backend_name="Llama Stack",
299+
cause="Unable to connect to the inference backend",
300+
)
301+
llm_detail = llm_response.model_dump()["detail"]
302+
303+
config_detail: dict[str, str] = config_exc.value.detail # type: ignore[assignment]
304+
305+
# Both must have identical key sets: {"response", "cause"}
306+
assert set(config_detail.keys()) == set(llm_detail.keys()) == {"response", "cause"}
273307

274308

275309
# --- Test retrieve_simple_response ---

0 commit comments

Comments
 (0)