feat(server): add /v1/models endpoint for OpenAI-compatible model discovery
Open WebUI and other OpenAI-compatible clients call GET /v1/models to discover available models. Lists all models from enabled providers via the model registry, respects disabled models, and handles Ollama models from TOML config.
This commit is contained in:
@@ -3,7 +3,7 @@ use axum::{
|
|||||||
extract::State,
|
extract::State,
|
||||||
response::IntoResponse,
|
response::IntoResponse,
|
||||||
response::sse::{Event, Sse},
|
response::sse::{Event, Sse},
|
||||||
routing::post,
|
routing::{get, post},
|
||||||
};
|
};
|
||||||
use futures::stream::StreamExt;
|
use futures::stream::StreamExt;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
@@ -24,6 +24,7 @@ use crate::{
|
|||||||
pub fn router(state: AppState) -> Router {
|
pub fn router(state: AppState) -> Router {
|
||||||
Router::new()
|
Router::new()
|
||||||
.route("/v1/chat/completions", post(chat_completions))
|
.route("/v1/chat/completions", post(chat_completions))
|
||||||
|
.route("/v1/models", get(list_models))
|
||||||
.layer(axum::middleware::from_fn_with_state(
|
.layer(axum::middleware::from_fn_with_state(
|
||||||
state.clone(),
|
state.clone(),
|
||||||
rate_limiting::middleware::rate_limit_middleware,
|
rate_limiting::middleware::rate_limit_middleware,
|
||||||
@@ -31,6 +32,60 @@ pub fn router(state: AppState) -> Router {
|
|||||||
.with_state(state)
|
.with_state(state)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// GET /v1/models — OpenAI-compatible model listing.
|
||||||
|
/// Returns all models from enabled providers so clients like Open WebUI can
|
||||||
|
/// discover which models are available through the proxy.
|
||||||
|
async fn list_models(
|
||||||
|
State(state): State<AppState>,
|
||||||
|
_auth: AuthenticatedClient,
|
||||||
|
) -> Result<Json<serde_json::Value>, AppError> {
|
||||||
|
let registry = &state.model_registry;
|
||||||
|
let providers = state.provider_manager.get_all_providers().await;
|
||||||
|
|
||||||
|
let mut models = Vec::new();
|
||||||
|
|
||||||
|
for provider in &providers {
|
||||||
|
let provider_name = provider.name();
|
||||||
|
|
||||||
|
// Find this provider's models in the registry
|
||||||
|
if let Some(provider_info) = registry.providers.get(provider_name) {
|
||||||
|
for (model_id, meta) in &provider_info.models {
|
||||||
|
// Skip disabled models via the config cache
|
||||||
|
if let Some(cfg) = state.model_config_cache.get(model_id).await {
|
||||||
|
if !cfg.enabled {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
models.push(serde_json::json!({
|
||||||
|
"id": model_id,
|
||||||
|
"object": "model",
|
||||||
|
"created": 0,
|
||||||
|
"owned_by": provider_name,
|
||||||
|
"name": meta.name,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// For Ollama, models are configured in the TOML, not the registry
|
||||||
|
if provider_name == "ollama" {
|
||||||
|
for model_id in &state.config.providers.ollama.models {
|
||||||
|
models.push(serde_json::json!({
|
||||||
|
"id": model_id,
|
||||||
|
"object": "model",
|
||||||
|
"created": 0,
|
||||||
|
"owned_by": "ollama",
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Json(serde_json::json!({
|
||||||
|
"object": "list",
|
||||||
|
"data": models
|
||||||
|
})))
|
||||||
|
}
|
||||||
|
|
||||||
async fn get_model_cost(
|
async fn get_model_cost(
|
||||||
model: &str,
|
model: &str,
|
||||||
prompt_tokens: u32,
|
prompt_tokens: u32,
|
||||||
|
|||||||
Reference in New Issue
Block a user