feat: add provider and model routing tables, admin UI, and audit category for gateway
Implement the multi-provider routing system for the OpenAI gateway, including two new database tables (`gateway_providers`, `gateway_models`) ensured at init, a new admin page at `/admin/gateway` with full CRUD for providers and model routes, and a `"gateway"` audit category mapped to `"ai"`. The routing layer sits transparently on top of the existing single-provider default path: unmatched model names fall through unchanged, while matched routes forward to the configured provider with their own pricing economy, vision model, and context window. Cross-worker cache invalidation uses a shared `_ROUTING_CACHE` bumped via `"gateway_routing"` cache version.
This commit is contained in:
@@ -1696,6 +1696,102 @@ ACTIONS: tuple[Action, ...] = (
|
||||
),
|
||||
requires_admin=True,
|
||||
),
|
||||
Action(
|
||||
name="gateway_providers",
|
||||
method="GET",
|
||||
path="/admin/gateway/providers",
|
||||
summary="List OpenAI gateway providers (admin only)",
|
||||
description=(
|
||||
"Returns JSON: the named upstream providers used by model routes, plus the implicit "
|
||||
"'default' provider read from the gateway service config."
|
||||
),
|
||||
handler="http",
|
||||
requires_admin=True,
|
||||
read_only=True,
|
||||
),
|
||||
Action(
|
||||
name="gateway_provider_set",
|
||||
method="POST",
|
||||
path="/admin/gateway/providers",
|
||||
summary="Create or update a gateway provider (admin only)",
|
||||
description=(
|
||||
"Adds or updates a named upstream provider. base_url is the OpenAI-compatible "
|
||||
"chat-completions endpoint; the embeddings endpoint is derived from it."
|
||||
),
|
||||
handler="http",
|
||||
requires_admin=True,
|
||||
params=(
|
||||
body("name", "Provider name (letters, numbers, hyphen, underscore).", required=True),
|
||||
body("base_url", "Chat-completions endpoint URL."),
|
||||
body("api_key", "Upstream API key."),
|
||||
Param(
|
||||
name="is_active",
|
||||
location="body",
|
||||
description="Whether the provider is active ('1' or '0').",
|
||||
required=False,
|
||||
type="boolean",
|
||||
),
|
||||
),
|
||||
),
|
||||
Action(
|
||||
name="gateway_provider_delete",
|
||||
method="DELETE",
|
||||
path="/admin/gateway/providers/{name}",
|
||||
summary="Delete a gateway provider (admin only, confirmation required)",
|
||||
handler="http",
|
||||
requires_admin=True,
|
||||
params=(path("name", "Provider name."), confirm()),
|
||||
),
|
||||
Action(
|
||||
name="gateway_models",
|
||||
method="GET",
|
||||
path="/admin/gateway/models",
|
||||
summary="List OpenAI gateway model routes (admin only)",
|
||||
description=(
|
||||
"Returns JSON: every source-model route with its provider, target model, kind "
|
||||
"(chat/embed), optional vision model, context window, and per-model pricing economy."
|
||||
),
|
||||
handler="http",
|
||||
requires_admin=True,
|
||||
read_only=True,
|
||||
),
|
||||
Action(
|
||||
name="gateway_model_set",
|
||||
method="POST",
|
||||
path="/admin/gateway/models",
|
||||
summary="Create or update a gateway model route (admin only)",
|
||||
description=(
|
||||
"Maps a requested source_model onto a provider + target_model, each with its own "
|
||||
"pricing. kind is 'chat' or 'embed'. A vision_model adds image-to-text augmentation "
|
||||
"for chat routes. Prices are USD per 1,000,000 tokens; chat uses cache-hit/cache-miss/"
|
||||
"output, embeddings use input, vision uses input/output."
|
||||
),
|
||||
handler="http",
|
||||
requires_admin=True,
|
||||
params=(
|
||||
body("source_model", "Model name clients request.", required=True),
|
||||
body("provider", "Provider name, or blank for the default upstream."),
|
||||
body("target_model", "Model name sent upstream.", required=True),
|
||||
body("kind", "Route kind: 'chat' or 'embed'."),
|
||||
body("vision_provider", "Provider for image description, or blank for the route provider."),
|
||||
body("vision_model", "Vision model name (blank disables the merge)."),
|
||||
Param(name="context_window", location="body", description="Max context tokens (0 = unknown).", required=False, type="integer"),
|
||||
Param(name="price_cache_hit_per_m", location="body", description="USD per 1M cache-hit input tokens.", required=False, type="number"),
|
||||
Param(name="price_cache_miss_per_m", location="body", description="USD per 1M cache-miss input tokens.", required=False, type="number"),
|
||||
Param(name="price_output_per_m", location="body", description="USD per 1M output tokens.", required=False, type="number"),
|
||||
Param(name="price_input_per_m", location="body", description="USD per 1M input tokens (embed/vision).", required=False, type="number"),
|
||||
Param(name="is_active", location="body", description="Whether the route is active ('1' or '0').", required=False, type="boolean"),
|
||||
),
|
||||
),
|
||||
Action(
|
||||
name="gateway_model_delete",
|
||||
method="DELETE",
|
||||
path="/admin/gateway/models/{source_model}",
|
||||
summary="Delete a gateway model route (admin only, confirmation required)",
|
||||
handler="http",
|
||||
requires_admin=True,
|
||||
params=(path("source_model", "Source model name."), confirm()),
|
||||
),
|
||||
)
|
||||
|
||||
PLATFORM_CATALOG = Catalog(actions=ACTIONS)
|
||||
|
||||
@@ -56,6 +56,8 @@ CONFIRM_REQUIRED = {
|
||||
"db_insert_row",
|
||||
"db_update_row",
|
||||
"db_delete_row",
|
||||
"gateway_provider_delete",
|
||||
"gateway_model_delete",
|
||||
}
|
||||
|
||||
CONDITIONAL_CONFIRM = {
|
||||
|
||||
Reference in New Issue
Block a user