fix: reject unknown models when model router is active (#13)

* fix: reject unknown models when model router is active

* fix: use existing invalid request error for model router
This commit is contained in:
whit3rabbit
2026-05-23 15:20:29 -05:00
committed by GitHub
parent 98bc1c833a
commit c29b523554
4 changed files with 58 additions and 0 deletions
+3
View File
@@ -270,6 +270,9 @@ impl ChatCompletionRuntime {
"all deployments for this model are at their RPM limit".to_string(),
));
}
return Err(ChatCompletionError::InvalidRequest(format!(
"model '{model}' is not configured in model_list"
)));
}
let state = self
@@ -96,6 +96,14 @@ pub(crate) async fn bedrock_passthrough(
);
return (StatusCode::TOO_MANY_REQUESTS, Json(err)).into_response();
}
super::state::ResolvedModel::UnknownModel => {
let err = anyllm_translate::mapping::errors_map::create_anthropic_error(
anyllm_translate::anthropic::ErrorType::InvalidRequestError,
format!("model '{}' is not configured in model_list", model_id),
None,
);
return (StatusCode::BAD_REQUEST, Json(err)).into_response();
}
super::state::ResolvedModel::Legacy(m) => m,
};
+11
View File
@@ -52,6 +52,8 @@ pub(crate) enum ResolvedModel {
},
/// Model is known but all deployments are at their RPM limit.
AllAtLimit,
/// Model router is active but the model alias is not configured.
UnknownModel,
/// No model router, or model not in router. Used legacy ModelMapping.
Legacy(String),
}
@@ -140,6 +142,7 @@ impl AppState {
if router.has_model(model) {
return ResolvedModel::AllAtLimit;
}
return ResolvedModel::UnknownModel;
}
ResolvedModel::Legacy(self.map_model(model))
}
@@ -200,6 +203,14 @@ impl AppState {
);
Err((StatusCode::TOO_MANY_REQUESTS, Json(err)).into_response())
}
ResolvedModel::UnknownModel => {
let err = mapping::errors_map::create_anthropic_error(
anthropic::ErrorType::InvalidRequestError,
format!("model '{model}' is not configured in model_list"),
None,
);
Err((StatusCode::BAD_REQUEST, Json(err)).into_response())
}
ResolvedModel::Legacy(mapped) => Ok((mapped, self.clone(), None)),
}
}
+36
View File
@@ -198,6 +198,42 @@ async fn runtime_model_router_selects_backend_and_mapped_model() {
);
}
#[tokio::test]
async fn runtime_model_router_rejects_unconfigured_model() {
let captured_a = Arc::new(Mutex::new(None));
let base_a = spawn_json_backend(captured_a.clone(), chat_response("unused")).await;
let mut backends = IndexMap::new();
backends.insert("a".to_string(), backend_config(BackendKind::OpenAI, base_a));
let deployment = Arc::new(anyllm_proxy::config::model_router::Deployment::new(
"a".to_string(),
"actual-model".to_string(),
None,
None,
));
let mut routes = HashMap::new();
routes.insert("virtual-model".to_string(), vec![deployment]);
let router = Arc::new(RwLock::new(
anyllm_proxy::config::model_router::ModelRouter::new(routes),
));
let runtime = ChatCompletionRuntime::from_multi_config_with_model_router(
multi_config(backends, "a"),
Some(router),
);
let req = serde_json::from_value(json!({
"model": "unconfigured-model",
"messages": [{"role": "user", "content": "hello"}],
"max_tokens": 32
}))
.unwrap();
let err = runtime.complete(req).await.unwrap_err();
assert!(matches!(err, ChatCompletionError::InvalidRequest(_)));
assert!(captured_a.lock().unwrap().is_none());
}
#[tokio::test]
async fn runtime_returns_typed_error_for_unsupported_backend() {
let mut backends = IndexMap::new();