feat: return retryable 503 from models list during startup warm-up

This commit is contained in:
9qeklajc
2026-10-02 09:57:32 +02:00
parent 0bdfc1b811
commit cc9aedb8f0
3 changed files with 51 additions and 17 deletions
+13 -7
View File
@@ -3,7 +3,7 @@ import json
import random
import httpx
from fastapi import APIRouter, Depends, HTTPException, Request
from fastapi import APIRouter, Depends, HTTPException, Request, Response
from pydantic import BaseModel as V2BaseModel
from pydantic.v1 import BaseModel, validator
from sqlmodel.ext.asyncio.session import AsyncSession
@@ -767,13 +767,19 @@ async def model_paths_for_model(model_id: str) -> dict:
return result
@models_router.get("/v1/models")
@models_router.get("/v1/models/", include_in_schema=False)
@models_router.get("/models")
@models_router.get("/models/", include_in_schema=False)
async def models(session: AsyncSession = Depends(get_session)) -> dict:
@models_router.get("/v1/models", response_model=None)
@models_router.get("/v1/models/", include_in_schema=False, response_model=None)
@models_router.get("/models", response_model=None)
@models_router.get("/models/", include_in_schema=False, response_model=None)
async def models(
request: Request, session: AsyncSession = Depends(get_session)
) -> dict | Response:
"""Get all available models from all providers with database overrides applied."""
from ..proxy import get_unique_models
from ..proxy import get_unique_models, is_warming_up, models_warming_up_response
# An empty list here reads as "this node serves nothing"; tell clients to retry.
if is_warming_up():
return models_warming_up_response(request)
items = get_unique_models()
data = []
+14 -10
View File
@@ -106,6 +106,19 @@ def is_warming_up() -> bool:
return _warming_up
def models_warming_up_response(request: Request) -> Response:
response = create_error_response(
"service_unavailable",
"Models are still loading, retry shortly",
503,
request=request,
code=MODELS_WARMING_UP,
error_scope=ERROR_SCOPE_NODE,
)
response.headers["Retry-After"] = str(WARMING_UP_RETRY_AFTER_SECONDS)
return response
async def reinitialize_upstreams() -> None:
"""Re-initialize upstream providers from database (called after admin changes)."""
global _upstreams
@@ -671,16 +684,7 @@ async def _proxy(
if not candidates:
if is_warming_up():
response = create_error_response(
"service_unavailable",
"Models are still loading, retry shortly",
503,
request=request,
code=MODELS_WARMING_UP,
error_scope=ERROR_SCOPE_NODE,
)
response.headers["Retry-After"] = str(WARMING_UP_RETRY_AFTER_SECONDS)
return response
return models_warming_up_response(request)
return create_error_response(
"invalid_model", f"Model '{model_id}' not found", 400, request=request
)
+24
View File
@@ -4,6 +4,7 @@ import json
from unittest.mock import AsyncMock, MagicMock, patch
import pytest
from fastapi import Response
from routstr import proxy as proxy_module
from routstr.core.error_scope import ERROR_SCOPE_HEADER, ERROR_SCOPE_NODE
@@ -56,3 +57,26 @@ async def test_failed_initialization_still_ends_warm_up() -> None:
await proxy_module.initialize_upstreams()
assert not proxy_module.is_warming_up()
@pytest.mark.asyncio
async def test_models_list_while_warming_up_is_retryable_503() -> None:
from routstr.payment.models import models
proxy_module.mark_warming_up()
response = await models(_chat_request(), MagicMock())
assert isinstance(response, Response)
assert response.status_code == 503
assert response.headers["Retry-After"] == "2"
@pytest.mark.asyncio
async def test_models_list_after_warm_up_returns_data() -> None:
from routstr.payment.models import models
with patch.object(proxy_module, "get_unique_models", return_value=[]):
response = await models(_chat_request(), MagicMock())
assert response == {"data": []}