refactor(alerts): unify model deprecation helper routes

This commit is contained in:
moe-berri 2026-11-08 16:35:27 +08:00
parent 6267dd7bc8
commit 1747cdbddb
8 changed files with 155 additions and 155 deletions

View file

@ -39,7 +39,7 @@ def _require_admin(user: UserAPIKeyAuth, *, write: bool) -> None:
raise HTTPException(status_code=403, detail="Only gateway admins can manage model deprecation alerts")
@router.get("/alerting/model-deprecations/state", tags=["alerting"], response_model=DeprecationAlertState)
@router.get("/alerts/model-deprecations/state", tags=["alerts"], response_model=DeprecationAlertState)
async def get_deprecation_alert_state(
model: Annotated[str, Query(min_length=1, max_length=1024)],
deprecation_date: date,
@ -53,7 +53,7 @@ async def get_deprecation_alert_state(
return DeprecationAlertState.model_validate(record.param_value)
@router.put("/alerting/model-deprecations/state", tags=["alerting"], response_model=DeprecationAlertState)
@router.put("/alerts/model-deprecations/state", tags=["alerts"], response_model=DeprecationAlertState)
async def update_deprecation_alert_state(
data: DeprecationAlertUpdate,
user: Annotated[UserAPIKeyAuth, Depends(user_api_key_auth)],
@ -69,7 +69,7 @@ async def update_deprecation_alert_state(
return state
@router.get("/alerting/model-deprecations/tags", tags=["alerting"], response_model=tuple[DeprecationTagUsage, ...])
@router.get("/alerts/model-deprecations/tags", tags=["alerts"], response_model=tuple[DeprecationTagUsage, ...])
async def get_deprecation_usage_tags(
model: Annotated[str, Query(min_length=1, max_length=1024)],
api_keys: Annotated[tuple[str, ...], Query(min_length=1, max_length=100)],

View file

@ -123,9 +123,9 @@ class AlertInput(BaseModel, frozen=True):
if self.range_start > self.range_end:
raise ValueError("range_start must not follow range_end")
if (self.kind == "model_deprecation") != (self.deprecation is not None):
raise ValueError("Model deprecation issues require deprecation details")
raise ValueError("Model deprecation alerts require deprecation details")
if (self.kind == "model_deprecation") != (self.family == "retirement"):
raise ValueError("Only model deprecation issues use the retirement family")
raise ValueError("Only model deprecation alerts use the retirement family")
if self.observed_at.tzinfo is None:
raise ValueError("observed_at must include a timezone")
return self
@ -139,7 +139,7 @@ class Alert(AlertInput, frozen=True):
scope_label: str | None = None
active: bool = True
can_update_status: bool | None = Field(
default=None, description="Current reader's status permission, evaluated by the issue-detail endpoint"
default=None, description="Current reader's status permission, evaluated by the alert detail endpoint"
)
can_view_activity: bool | None = Field(
default=None, description="Current reader's permission to inspect Activity, keys, users and impact"

View file

@ -38,7 +38,7 @@ def client() -> Iterator[tuple[TestClient, UserAPIKeyAuth]]:
IDENTITY = {"model": "anthropic/claude-sonnet-4-5", "deprecation_date": "2026-11-30"}
UPDATE = {**IDENTITY, "status": "resolved", "last_seen_date": "2026-11-04", "last_seen_requests": 123}
PATH = "/alerting/model-deprecations/state"
PATH = "/alerts/model-deprecations/state"
def test_new_alert_is_open(client: tuple[TestClient, UserAPIKeyAuth]) -> None:
@ -109,7 +109,7 @@ def test_tags_are_scoped_to_model_dates_and_the_visible_keys(client: tuple[TestC
table=SimpleNamespace(group_by=grouped)
)
response = http.get(
"/alerting/model-deprecations/tags",
"/alerts/model-deprecations/tags",
params={
"model": "openai/gpt-4-turbo",
"api_keys": ["reporting-one", "reporting-two"],
@ -146,7 +146,7 @@ def test_tag_access_matches_alert_visibility(
table=SimpleNamespace(group_by=grouped)
)
response = http.get(
"/alerting/model-deprecations/tags",
"/alerts/model-deprecations/tags",
params={"model": "test-model", "api_keys": ["one"], "start_date": "2026-10-06", "end_date": "2026-11-04"},
)
assert response.status_code == expected
@ -168,7 +168,7 @@ def test_tag_query_rejects_more_than_100_keys_before_database_access(client: tup
table=SimpleNamespace(group_by=grouped)
)
response = http.get(
"/alerting/model-deprecations/tags",
"/alerts/model-deprecations/tags",
params={
"model": "test-model",
"api_keys": [f"reporting-{index}" for index in range(101)],

View file

@ -100,7 +100,7 @@ export default function MigrationDetail({
queryKey: ["deprecationUsageTags", scope, model, range, keys],
enabled: keys.length > 0,
queryFn: async ({ signal }: { signal: AbortSignal }) => {
const { data } = await backgroundFetchClient.GET("/alerting/model-deprecations/tags", {
const { data } = await backgroundFetchClient.GET("/alerts/model-deprecations/tags", {
params: { query: { ...range, model, api_keys: keys } },
signal,
});
@ -141,7 +141,7 @@ export default function MigrationDetail({
last_seen_date: selected.lastUsedDate,
last_seen_requests: selected.lastDayRequests,
};
const { data } = await backgroundFetchClient.PUT("/alerting/model-deprecations/state", { body });
const { data } = await backgroundFetchClient.PUT("/alerts/model-deprecations/state", { body });
if (!data) throw new Error("Could not save alert status");
return data;
},

View file

@ -50,8 +50,8 @@ describe("model migration impact", () => {
body: request.method === "PUT" ? await request.clone().json() : null,
};
calls.push(call);
if (url.pathname === "/alerting/model-deprecations/tags") return apiResponse([]);
if (request.method === "PUT" && url.pathname === "/alerting/model-deprecations/state")
if (url.pathname === "/alerts/model-deprecations/tags") return apiResponse([]);
if (request.method === "PUT" && url.pathname === "/alerts/model-deprecations/state")
return apiResponse({ status: "resolved" });
if (url.pathname.endsWith("/aggregated/keys")) {
const offset = Number(url.searchParams.get("offset") || 0);
@ -111,7 +111,7 @@ describe("model migration impact", () => {
};
await waitFor(() =>
expect(
calls.find((call) => call.method === "PUT" && call.path === "/alerting/model-deprecations/state")?.body,
calls.find((call) => call.method === "PUT" && call.path === "/alerts/model-deprecations/state")?.body,
).toEqual(expectedAcknowledgement),
);

View file

@ -65,7 +65,7 @@ describe("deprecation alert API wiring", () => {
renderWithProviders(<AlertsHost />);
expect(await screen.findByText("1 alerts")).toBeInTheDocument();
expect(backgroundFetchClient.GET).toHaveBeenLastCalledWith(
"/alerting/model-deprecations/state",
"/alerts/model-deprecations/state",
expect.objectContaining({
params: { query: { model: model.litellm_model, deprecation_date: model.deprecation_date } },
}),

View file

@ -32,7 +32,7 @@ export function useDeprecationAlerts(accessToken: string | null) {
const alerts = usedDeprecationAlerts(models, activity.results ?? []);
return Promise.all(
alerts.map(async (alert) => {
const { data: state } = await backgroundFetchClient.GET("/alerting/model-deprecations/state", {
const { data: state } = await backgroundFetchClient.GET("/alerts/model-deprecations/state", {
params: { query: { model: alert.usageModel, deprecation_date: alert.model.deprecation_date } },
signal,
});

View file

@ -944,44 +944,6 @@ export interface paths {
patch?: never;
trace?: never;
};
"/alerting/model-deprecations/state": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
/** Get Deprecation Alert State */
get: operations["get_deprecation_alert_state_alerting_model_deprecations_state_get"];
/** Update Deprecation Alert State */
put: operations["update_deprecation_alert_state_alerting_model_deprecations_state_put"];
post?: never;
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/alerting/model-deprecations/tags": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
/**
* Get Deprecation Usage Tags
* @description Distinct tags observed with this model for a page of affected reporting key IDs.
*/
get: operations["get_deprecation_usage_tags_alerting_model_deprecations_tags_get"];
put?: never;
post?: never;
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/alerting/settings": {
parameters: {
query?: never;
@ -1053,6 +1015,44 @@ export interface paths {
patch: operations["update_alert_default_alerts_defaults__family__patch"];
trace?: never;
};
"/alerts/model-deprecations/state": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
/** Get Deprecation Alert State */
get: operations["get_deprecation_alert_state_alerts_model_deprecations_state_get"];
/** Update Deprecation Alert State */
put: operations["update_deprecation_alert_state_alerts_model_deprecations_state_put"];
post?: never;
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/alerts/model-deprecations/tags": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
/**
* Get Deprecation Usage Tags
* @description Distinct tags observed with this model for a page of affected reporting key IDs.
*/
get: operations["get_deprecation_usage_tags_alerts_model_deprecations_tags_get"];
put?: never;
post?: never;
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/alerts/models": {
parameters: {
query?: never;
@ -27280,7 +27280,7 @@ export interface components {
active: boolean;
/**
* Can Update Status
* @description Current reader's status permission, evaluated by the issue-detail endpoint
* @description Current reader's status permission, evaluated by the alert detail endpoint
*/
can_update_status?: boolean | null;
/**
@ -55147,105 +55147,6 @@ export interface operations {
};
};
};
get_deprecation_alert_state_alerting_model_deprecations_state_get: {
parameters: {
query: {
model: string;
deprecation_date: string;
};
header?: never;
path?: never;
cookie?: never;
};
requestBody?: never;
responses: {
/** @description Successful Response */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["DeprecationAlertState"];
};
};
/** @description Validation Error */
422: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["HTTPValidationError"];
};
};
};
};
update_deprecation_alert_state_alerting_model_deprecations_state_put: {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
requestBody: {
content: {
"application/json": components["schemas"]["DeprecationAlertUpdate"];
};
};
responses: {
/** @description Successful Response */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["DeprecationAlertState"];
};
};
/** @description Validation Error */
422: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["HTTPValidationError"];
};
};
};
};
get_deprecation_usage_tags_alerting_model_deprecations_tags_get: {
parameters: {
query: {
model: string;
api_keys: string[];
start_date: string;
end_date: string;
};
header?: never;
path?: never;
cookie?: never;
};
requestBody?: never;
responses: {
/** @description Successful Response */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["DeprecationTagUsage"][];
};
};
/** @description Validation Error */
422: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["HTTPValidationError"];
};
};
};
};
alerting_settings_alerting_settings_get: {
parameters: {
query?: never;
@ -55358,6 +55259,105 @@ export interface operations {
};
};
};
get_deprecation_alert_state_alerts_model_deprecations_state_get: {
parameters: {
query: {
model: string;
deprecation_date: string;
};
header?: never;
path?: never;
cookie?: never;
};
requestBody?: never;
responses: {
/** @description Successful Response */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["DeprecationAlertState"];
};
};
/** @description Validation Error */
422: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["HTTPValidationError"];
};
};
};
};
update_deprecation_alert_state_alerts_model_deprecations_state_put: {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
requestBody: {
content: {
"application/json": components["schemas"]["DeprecationAlertUpdate"];
};
};
responses: {
/** @description Successful Response */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["DeprecationAlertState"];
};
};
/** @description Validation Error */
422: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["HTTPValidationError"];
};
};
};
};
get_deprecation_usage_tags_alerts_model_deprecations_tags_get: {
parameters: {
query: {
model: string;
api_keys: string[];
start_date: string;
end_date: string;
};
header?: never;
path?: never;
cookie?: never;
};
requestBody?: never;
responses: {
/** @description Successful Response */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["DeprecationTagUsage"][];
};
};
/** @description Validation Error */
422: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["HTTPValidationError"];
};
};
};
};
list_alert_models_alerts_models_get: {
parameters: {
query?: {