test(ci): serve /moderations from the canned OpenAI mock (#37739)

* test(ci): serve /moderations from the canned OpenAI mock

The otel proxy E2E job points its `openai/*` wildcard deployment at the
canned mock, and #37492 made `get_model_list` agree with
`get_available_deployment` on bare model names. /moderations now resolves
`omni-moderation-latest` to that wildcard deployment the way
/chat/completions already did, so the request lands on the mock, which
never implemented the route and answers a bare 404.

Add /moderations and /v1/moderations to the mock, returning an
OpenAI-shaped response with one result per input item.

* style(ci): annotate the new moderations locals as Final
This commit is contained in:
yuneng-jiang 2026-08-20 16:58:38 -07:00 committed by GitHub
parent 2a863f8bdd
commit 0c2e404be3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 60 additions and 5 deletions

View file

@ -8,11 +8,11 @@ those jobs failed with ``404 Application not found`` even though nothing in the
PR was broken.
This process is the local stand-in. A model points its ``api_base`` here and
gets back a well-formed chat/text/embedding response with realistic ``usage`` so
cost tracking and spend accounting still exercise their real code paths. The one
behavioral special case mirrors the old hosted mock: a request whose ``model``
is ``429`` returns HTTP 429 so rate-limit and cooldown tests still have
something to trip on.
gets back a well-formed chat/text/embedding/moderation response with realistic
``usage`` so cost tracking and spend accounting still exercise their real code
paths. The one behavioral special case mirrors the old hosted mock: a request
whose ``model`` is ``429`` returns HTTP 429 so rate-limit and cooldown tests
still have something to trip on.
"""
from __future__ import annotations
@ -35,6 +35,21 @@ _SLOW_MODEL: Final = "slow-endpoint"
_SLOW_RESPONSE_SECONDS: Final = 3.0
_PROMPT_TOKENS: Final = 20
_COMPLETION_TOKENS: Final = 20
_MODERATION_CATEGORIES: Final = (
"harassment",
"harassment/threatening",
"hate",
"hate/threatening",
"illicit",
"illicit/violent",
"self-harm",
"self-harm/instructions",
"self-harm/intent",
"sexual",
"sexual/minors",
"violence",
"violence/graphic",
)
def _usage() -> dict[str, int]:
@ -220,6 +235,28 @@ async def triton_embeddings(_request: Request) -> Response:
)
def _moderation_result() -> dict[str, object]:
return {
"flagged": False,
"categories": {category: False for category in _MODERATION_CATEGORIES},
"category_scores": {category: 0.0 for category in _MODERATION_CATEGORIES},
"category_applied_input_types": {category: ["text"] for category in _MODERATION_CATEGORIES},
}
async def moderations(request: Request) -> Response:
body: Final = await _parse_body(request)
raw_input: Final = body.get("input", "")
count: Final = len(raw_input) if isinstance(raw_input, list) else 1
return JSONResponse(
{
"id": f"modr-{uuid.uuid4().hex[:24]}",
"model": _requested_model(body),
"results": [_moderation_result() for _ in range(max(count, 1))],
}
)
async def list_models(_request: Request) -> Response:
return JSONResponse(
{
@ -247,6 +284,8 @@ app = Starlette(
Route("/embeddings", embeddings, methods=["POST"]),
Route("/v1/embeddings", embeddings, methods=["POST"]),
Route("/triton/embeddings", triton_embeddings, methods=["POST"]),
Route("/moderations", moderations, methods=["POST"]),
Route("/v1/moderations", moderations, methods=["POST"]),
Route("/models", list_models, methods=["GET"]),
Route("/v1/models", list_models, methods=["GET"]),
]

View file

@ -13,9 +13,11 @@ from __future__ import annotations
import re
from pathlib import Path
from typing import Final
import httpx
import pytest
from openai.types import ModerationCreateResponse
from tests.fake_openai_endpoint import (
_LOCAL_DEFAULT,
@ -56,6 +58,20 @@ def test_chat_completion_shape():
assert body["usage"]["total_tokens"] == 40
def test_moderations_route_parses_as_an_openai_response():
base: Final = ensure_fake_openai_endpoint()
response: Final = httpx.post(
f"{base}/v1/moderations",
json={"input": ["I want to harm someone", "hello"], "model": "omni-moderation-latest"},
timeout=10,
)
assert response.status_code == 200
parsed: Final = ModerationCreateResponse.model_validate(response.json())
assert parsed.model == "omni-moderation-latest"
assert len(parsed.results) == 2
assert parsed.results[0].categories.violence is False
def test_triton_embeddings_route():
base = ensure_fake_openai_endpoint()
response = httpx.post(f"{base}/triton/embeddings", json={"inputs": []}, timeout=10)