fix: remove Option from model listing return types, propagate errors (#7074)

Signed-off-by: Adrian Cole <adrian@tetrate.io>
This commit is contained in:
Adrian Cole
2026-02-09 08:03:10 +08:00
committed by GitHub
parent 4e87011981
commit 08b89ca66b
27 changed files with 177 additions and 218 deletions
+3 -3
View File
@@ -691,15 +691,15 @@ pub async fn configure_provider_dialog() -> anyhow::Result<bool> {
};
spin.stop(style("Model fetch complete").green());
// Select a model: on fetch error show styled error and abort; if Some(models), show list; if None, free-text input
// Select a model: on fetch error show styled error and abort; if models available, show list; otherwise free-text input
let model: String = match models_res {
Err(e) => {
// Provider hook error
cliclack::outro(style(e.to_string()).on_red().white())?;
return Ok(false);
}
Ok(Some(models)) => select_model_from_list(&models, provider_meta)?,
Ok(None) => {
Ok(models) if !models.is_empty() => select_model_from_list(&models, provider_meta)?,
Ok(_) => {
let default_model =
std::env::var("GOOSE_MODEL").unwrap_or(provider_meta.default_model.clone());
cliclack::input("Enter a model from that provider:")
@@ -372,19 +372,6 @@ pub async fn providers() -> Result<Json<Vec<ProviderDetails>>, ErrorResponse> {
pub async fn get_provider_models(
Path(name): Path<String>,
) -> Result<Json<Vec<String>>, ErrorResponse> {
let loaded_provider = goose::config::declarative_providers::load_provider(name.as_str()).ok();
// TODO(Douwe): support a get models url for custom providers
if let Some(loaded_provider) = loaded_provider {
return Ok(Json(
loaded_provider
.config
.models
.into_iter()
.map(|m| m.name)
.collect::<Vec<_>>(),
));
}
let all = get_providers().await.into_iter().collect::<Vec<_>>();
let Some((metadata, provider_type)) = all.into_iter().find(|(m, _)| m.name == name) else {
return Err(ErrorResponse::bad_request(format!(
@@ -405,8 +392,7 @@ pub async fn get_provider_models(
let models_result = provider.fetch_recommended_models().await;
match models_result {
Ok(Some(models)) => Ok(Json(models)),
Ok(None) => Ok(Json(Vec::new())),
Ok(models) => Ok(Json(models)),
Err(provider_error) => Err(provider_error.into()),
}
}
+4 -1
View File
@@ -31,9 +31,12 @@ async fn enhance_model_error(error: ProviderError, provider: &Arc<dyn Provider>)
return error;
}
let Ok(Some(models)) = provider.fetch_recommended_models().await else {
let Ok(models) = provider.fetch_recommended_models().await else {
return error;
};
if models.is_empty() {
return error;
}
ProviderError::RequestFailed(format!(
"{}. Available models for this provider: {}",
+7 -6
View File
@@ -256,7 +256,7 @@ impl Provider for AnthropicProvider {
Ok((message, provider_usage))
}
async fn fetch_supported_models(&self) -> Result<Option<Vec<String>>, ProviderError> {
async fn fetch_supported_models(&self) -> Result<Vec<String>, ProviderError> {
let response = self.api_client.request(None, "v1/models").api_get().await?;
if response.status != StatusCode::OK {
@@ -267,17 +267,18 @@ impl Provider for AnthropicProvider {
}
let json = response.payload.unwrap_or_default();
let arr = match json.get("data").and_then(|v| v.as_array()) {
Some(arr) => arr,
None => return Ok(None),
};
let arr = json.get("data").and_then(|v| v.as_array()).ok_or_else(|| {
ProviderError::RequestFailed(
"Missing 'data' array in Anthropic models response".to_string(),
)
})?;
let mut models: Vec<String> = arr
.iter()
.filter_map(|m| m.get("id").and_then(|v| v.as_str()).map(str::to_string))
.collect();
models.sort();
Ok(Some(models))
Ok(models)
}
async fn stream(
+3 -1
View File
@@ -31,7 +31,9 @@ pub async fn detect_provider_from_api_key(api_key: &str) -> Option<(String, Vec<
})
.await
{
Ok(Some(models)) => Some((provider_name.to_string(), models)),
Ok(models) if !models.is_empty() => {
Some((provider_name.to_string(), models))
}
_ => None,
}
}
+6 -9
View File
@@ -447,16 +447,13 @@ pub trait Provider: Send + Sync {
RetryConfig::default()
}
async fn fetch_supported_models(&self) -> Result<Option<Vec<String>>, ProviderError> {
Ok(None)
async fn fetch_supported_models(&self) -> Result<Vec<String>, ProviderError> {
Ok(vec![])
}
/// Fetch models filtered by canonical registry and usability
async fn fetch_recommended_models(&self) -> Result<Option<Vec<String>>, ProviderError> {
let all_models = match self.fetch_supported_models().await? {
Some(models) => models,
None => return Ok(None),
};
async fn fetch_recommended_models(&self) -> Result<Vec<String>, ProviderError> {
let all_models = self.fetch_supported_models().await?;
let registry = CanonicalModelRegistry::bundled().map_err(|e| {
ProviderError::ExecutionError(format!("Failed to load canonical registry: {}", e))
@@ -501,9 +498,9 @@ pub trait Provider: Send + Sync {
.collect();
if recommended_models.is_empty() {
Ok(Some(all_models))
Ok(all_models)
} else {
Ok(Some(recommended_models))
Ok(recommended_models)
}
}
+4
View File
@@ -301,6 +301,10 @@ impl Provider for BedrockProvider {
self.model.clone()
}
async fn fetch_supported_models(&self) -> Result<Vec<String>, ProviderError> {
Ok(BEDROCK_KNOWN_MODELS.iter().map(|s| s.to_string()).collect())
}
#[tracing::instrument(
skip(self, model_config, system, messages, tools),
fields(model_config, input, output, input_tokens, output_tokens, total_tokens)
@@ -493,14 +493,10 @@ async fn check_provider(
};
let fetched_models = match provider.fetch_supported_models().await {
Ok(Some(models)) => {
Ok(models) => {
println!(" ✓ Fetched {} models", models.len());
models
}
Ok(None) => {
println!(" ⚠ Provider does not support model listing");
Vec::new()
}
Err(e) => {
println!(" ⚠ Failed to fetch models: {}", e);
println!(" This is expected if credentials are not configured.");
@@ -509,11 +505,10 @@ async fn check_provider(
};
let recommended_models = match provider.fetch_recommended_models().await {
Ok(Some(models)) => {
Ok(models) => {
println!(" ✓ Found {} recommended models", models.len());
models
}
Ok(None) => Vec::new(),
Err(e) => {
println!(" ⚠ Failed to fetch recommended models: {}", e);
Vec::new()
+5 -7
View File
@@ -985,13 +985,11 @@ impl Provider for ChatGptCodexProvider {
Ok(())
}
async fn fetch_supported_models(&self) -> Result<Option<Vec<String>>, ProviderError> {
Ok(Some(
CHATGPT_CODEX_KNOWN_MODELS
.iter()
.map(|s| s.to_string())
.collect(),
))
async fn fetch_supported_models(&self) -> Result<Vec<String>, ProviderError> {
Ok(CHATGPT_CODEX_KNOWN_MODELS
.iter()
.map(|s| s.to_string())
.collect())
}
}
@@ -493,6 +493,13 @@ impl Provider for ClaudeCodeProvider {
self.model.clone()
}
async fn fetch_supported_models(&self) -> Result<Vec<String>, ProviderError> {
Ok(CLAUDE_CODE_KNOWN_MODELS
.iter()
.map(|s| s.to_string())
.collect())
}
#[tracing::instrument(
skip(self, model_config, system, messages, tools),
fields(model_config, input, output, input_tokens, output_tokens, total_tokens)
+2 -4
View File
@@ -662,10 +662,8 @@ impl Provider for CodexProvider {
))
}
async fn fetch_supported_models(&self) -> Result<Option<Vec<String>>, ProviderError> {
Ok(Some(
CODEX_KNOWN_MODELS.iter().map(|s| s.to_string()).collect(),
))
async fn fetch_supported_models(&self) -> Result<Vec<String>, ProviderError> {
Ok(CODEX_KNOWN_MODELS.iter().map(|s| s.to_string()).collect())
}
}
@@ -355,6 +355,13 @@ impl Provider for CursorAgentProvider {
self.model.clone()
}
async fn fetch_supported_models(&self) -> Result<Vec<String>, ProviderError> {
Ok(CURSOR_AGENT_KNOWN_MODELS
.iter()
.map(|s| s.to_string())
.collect())
}
#[tracing::instrument(
skip(self, model_config, system, messages, tools),
fields(model_config, input, output, input_tokens, output_tokens, total_tokens)
+22 -39
View File
@@ -391,51 +391,38 @@ impl Provider for DatabricksProvider {
.map_err(|e| ProviderError::ExecutionError(e.to_string()))
}
async fn fetch_supported_models(&self) -> Result<Option<Vec<String>>, ProviderError> {
let response = match self
async fn fetch_supported_models(&self) -> Result<Vec<String>, ProviderError> {
let response = self
.api_client
.request(None, "api/2.0/serving-endpoints")
.response_get()
.await
{
Ok(resp) => resp,
Err(e) => {
tracing::warn!("Failed to fetch Databricks models: {}", e);
return Ok(None);
}
};
.map_err(|e| {
ProviderError::RequestFailed(format!("Failed to fetch Databricks models: {}", e))
})?;
if !response.status().is_success() {
let status = response.status();
if let Ok(error_text) = response.text().await {
tracing::warn!(
"Failed to fetch Databricks models: {} - {}",
status,
error_text
);
} else {
tracing::warn!("Failed to fetch Databricks models: {}", status);
}
return Ok(None);
let detail = response.text().await.unwrap_or_default();
return Err(ProviderError::RequestFailed(format!(
"Failed to fetch Databricks models: {} {}",
status, detail
)));
}
let json: Value = match response.json().await {
Ok(json) => json,
Err(e) => {
tracing::warn!("Failed to parse Databricks API response: {}", e);
return Ok(None);
}
};
let json: Value = response.json().await.map_err(|e| {
ProviderError::RequestFailed(format!("Failed to parse Databricks API response: {}", e))
})?;
let endpoints = match json.get("endpoints").and_then(|v| v.as_array()) {
Some(endpoints) => endpoints,
None => {
tracing::warn!(
let endpoints = json
.get("endpoints")
.and_then(|v| v.as_array())
.ok_or_else(|| {
ProviderError::RequestFailed(
"Unexpected response format from Databricks API: missing 'endpoints' array"
);
return Ok(None);
}
};
.to_string(),
)
})?;
let models: Vec<String> = endpoints
.iter()
@@ -447,11 +434,7 @@ impl Provider for DatabricksProvider {
})
.collect();
if models.is_empty() {
Ok(None)
} else {
Ok(Some(models))
}
Ok(models)
}
}
+2 -2
View File
@@ -695,10 +695,10 @@ impl Provider for GcpVertexAIProvider {
}))
}
async fn fetch_supported_models(&self) -> Result<Option<Vec<String>>, ProviderError> {
async fn fetch_supported_models(&self) -> Result<Vec<String>, ProviderError> {
let models: Vec<String> = KNOWN_MODELS.iter().map(|s| s.to_string()).collect();
let filtered = self.filter_by_org_policy(models).await;
Ok(Some(filtered))
Ok(filtered)
}
}
+7
View File
@@ -267,6 +267,13 @@ impl Provider for GeminiCliProvider {
self.model.clone()
}
async fn fetch_supported_models(&self) -> Result<Vec<String>, ProviderError> {
Ok(GEMINI_CLI_KNOWN_MODELS
.iter()
.map(|s| s.to_string())
.collect())
}
#[tracing::instrument(
skip(self, _model_config, system, messages, tools),
fields(model_config, input, output, input_tokens, output_tokens, total_tokens)
+7 -6
View File
@@ -495,7 +495,7 @@ impl Provider for GithubCopilotProvider {
stream_openai_compat(response, log)
}
async fn fetch_supported_models(&self) -> Result<Option<Vec<String>>, ProviderError> {
async fn fetch_supported_models(&self) -> Result<Vec<String>, ProviderError> {
let (endpoint, token) = self.get_api_info().await?;
let url = format!("{}/models", endpoint);
@@ -515,10 +515,11 @@ impl Provider for GithubCopilotProvider {
let json: serde_json::Value = response.json().await?;
let arr = match json.get("data").and_then(|v| v.as_array()) {
Some(arr) => arr,
None => return Ok(None),
};
let arr = json.get("data").and_then(|v| v.as_array()).ok_or_else(|| {
ProviderError::RequestFailed(
"Missing 'data' array in GitHub Copilot models response".to_string(),
)
})?;
let mut models: Vec<String> = arr
.iter()
.filter_map(|m| {
@@ -532,7 +533,7 @@ impl Provider for GithubCopilotProvider {
})
.collect();
models.sort();
Ok(Some(models))
Ok(models)
}
async fn configure_oauth(&self) -> Result<(), ProviderError> {
+10 -6
View File
@@ -187,24 +187,28 @@ impl Provider for GoogleProvider {
Ok((message, provider_usage))
}
async fn fetch_supported_models(&self) -> Result<Option<Vec<String>>, ProviderError> {
async fn fetch_supported_models(&self) -> Result<Vec<String>, ProviderError> {
let response = self
.api_client
.request(None, "v1beta/models")
.response_get()
.await?;
let json: serde_json::Value = response.json().await?;
let arr = match json.get("models").and_then(|v| v.as_array()) {
Some(arr) => arr,
None => return Ok(None),
};
let arr = json
.get("models")
.and_then(|v| v.as_array())
.ok_or_else(|| {
ProviderError::RequestFailed(
"Missing 'models' array in Google models response".to_string(),
)
})?;
let mut models: Vec<String> = arr
.iter()
.filter_map(|m| m.get("name").and_then(|v| v.as_str()))
.map(|name| name.split('/').next_back().unwrap_or(name).to_string())
.collect();
models.sort();
Ok(Some(models))
Ok(models)
}
fn supports_streaming(&self) -> bool {
+6 -14
View File
@@ -445,22 +445,14 @@ impl Provider for LeadWorkerProvider {
final_result
}
async fn fetch_supported_models(&self) -> Result<Option<Vec<String>>, ProviderError> {
async fn fetch_supported_models(&self) -> Result<Vec<String>, ProviderError> {
// Combine models from both providers
let lead_models = self.lead_provider.fetch_supported_models().await?;
let mut all_models = self.lead_provider.fetch_supported_models().await?;
let worker_models = self.worker_provider.fetch_supported_models().await?;
match (lead_models, worker_models) {
(Some(lead), Some(worker)) => {
let mut all_models = lead;
all_models.extend(worker);
all_models.sort();
all_models.dedup();
Ok(Some(all_models))
}
(Some(models), None) | (None, Some(models)) => Ok(Some(models)),
(None, None) => Ok(None),
}
all_models.extend(worker_models);
all_models.sort();
all_models.dedup();
Ok(all_models)
}
fn supports_embeddings(&self) -> bool {
+5 -11
View File
@@ -223,17 +223,11 @@ impl Provider for LiteLLMProvider {
self.model.model_name.to_lowercase().contains("claude")
}
async fn fetch_supported_models(&self) -> Result<Option<Vec<String>>, ProviderError> {
match self.fetch_models().await {
Ok(models) => {
let model_names: Vec<String> = models.into_iter().map(|m| m.name).collect();
Ok(Some(model_names))
}
Err(e) => {
tracing::warn!("Failed to fetch models from LiteLLM: {}", e);
Ok(None)
}
}
async fn fetch_supported_models(&self) -> Result<Vec<String>, ProviderError> {
let models = self.fetch_models().await.map_err(|e| {
ProviderError::RequestFailed(format!("Failed to fetch models from LiteLLM: {}", e))
})?;
Ok(models.into_iter().map(|m| m.name).collect())
}
}
+2 -2
View File
@@ -307,7 +307,7 @@ impl Provider for OllamaProvider {
stream_ollama(response, log)
}
async fn fetch_supported_models(&self) -> Result<Option<Vec<String>>, ProviderError> {
async fn fetch_supported_models(&self) -> Result<Vec<String>, ProviderError> {
let response = self
.api_client
.request(None, "api/tags")
@@ -340,7 +340,7 @@ impl Provider for OllamaProvider {
model_names.sort();
Ok(Some(model_names))
Ok(model_names)
}
}
+2 -2
View File
@@ -353,7 +353,7 @@ impl Provider for OpenAiProvider {
}
}
async fn fetch_supported_models(&self) -> Result<Option<Vec<String>>, ProviderError> {
async fn fetch_supported_models(&self) -> Result<Vec<String>, ProviderError> {
let models_path = self.base_path.replace("v1/chat/completions", "v1/models");
let response = self
.api_client
@@ -377,7 +377,7 @@ impl Provider for OpenAiProvider {
.filter_map(|m| m.get("id").and_then(|v| v.as_str()).map(str::to_string))
.collect();
models.sort();
Ok(Some(models))
Ok(models)
}
fn supports_embeddings(&self) -> bool {
+10 -13
View File
@@ -112,7 +112,7 @@ impl Provider for OpenAiCompatibleProvider {
Ok((message, ProviderUsage::new(response_model, usage)))
}
async fn fetch_supported_models(&self) -> Result<Option<Vec<String>>, ProviderError> {
async fn fetch_supported_models(&self) -> Result<Vec<String>, ProviderError> {
let response = self
.api_client
.response_get(None, "models")
@@ -128,18 +128,15 @@ impl Provider for OpenAiCompatibleProvider {
return Err(ProviderError::Authentication(msg.to_string()));
}
let data = json.get("data").and_then(|v| v.as_array());
match data {
Some(arr) => {
let mut models: Vec<String> = arr
.iter()
.filter_map(|m| m.get("id").and_then(|v| v.as_str()).map(str::to_string))
.collect();
models.sort();
Ok(Some(models))
}
None => Ok(None),
}
let arr = json.get("data").and_then(|v| v.as_array()).ok_or_else(|| {
ProviderError::RequestFailed("Missing 'data' array in models response".to_string())
})?;
let mut models: Vec<String> = arr
.iter()
.filter_map(|m| m.get("id").and_then(|v| v.as_str()).map(str::to_string))
.collect();
models.sort();
Ok(models)
}
fn supports_streaming(&self) -> bool {
+19 -29
View File
@@ -309,39 +309,35 @@ impl Provider for OpenRouterProvider {
}
/// Fetch supported models from OpenRouter API (only models with tool support)
async fn fetch_supported_models(&self) -> Result<Option<Vec<String>>, ProviderError> {
// Handle request failures gracefully
// If the request fails, fall back to manual entry
let response = match self
async fn fetch_supported_models(&self) -> Result<Vec<String>, ProviderError> {
let response = self
.api_client
.request(None, "api/v1/models")
.response_get()
.await
{
Ok(response) => response,
Err(e) => {
tracing::warn!("Failed to fetch models from OpenRouter API: {}, falling back to manual model entry", e);
return Ok(None);
}
};
.map_err(|e| {
ProviderError::RequestFailed(format!(
"Failed to fetch models from OpenRouter API: {}",
e
))
})?;
// Handle JSON parsing failures gracefully
let json: serde_json::Value = match response.json().await {
Ok(json) => json,
Err(e) => {
tracing::warn!("Failed to parse OpenRouter API response as JSON: {}, falling back to manual model entry", e);
return Ok(None);
}
};
let json: serde_json::Value = response.json().await.map_err(|e| {
ProviderError::RequestFailed(format!(
"Failed to parse OpenRouter API response as JSON: {}",
e
))
})?;
// Check for error in response
if let Some(err_obj) = json.get("error") {
let msg = err_obj
.get("message")
.and_then(|v| v.as_str())
.unwrap_or("unknown error");
tracing::warn!("OpenRouter API returned an error: {}", msg);
return Ok(None);
return Err(ProviderError::RequestFailed(format!(
"OpenRouter API returned an error: {}",
msg
)));
}
let data = json.get("data").and_then(|v| v.as_array()).ok_or_else(|| {
@@ -380,14 +376,8 @@ impl Provider for OpenRouterProvider {
})
.collect();
// If no models with tool support were found, fall back to manual entry
if models.is_empty() {
tracing::warn!("No models with tool support found in OpenRouter API response, falling back to manual model entry");
return Ok(None);
}
models.sort();
Ok(Some(models))
Ok(models)
}
async fn supports_cache_control(&self) -> bool {
+7
View File
@@ -328,6 +328,13 @@ impl Provider for SnowflakeProvider {
self.model.clone()
}
async fn fetch_supported_models(&self) -> Result<Vec<String>, ProviderError> {
Ok(SNOWFLAKE_KNOWN_MODELS
.iter()
.map(|s| s.to_string())
.collect())
}
#[tracing::instrument(
skip(self, model_config, system, messages, tools),
fields(model_config, input, output, input_tokens, output_tokens, total_tokens)
+14 -26
View File
@@ -244,7 +244,7 @@ impl Provider for TetrateProvider {
}
/// Fetch supported models from Tetrate Agent Router Service API (only models with tool support)
async fn fetch_supported_models(&self) -> Result<Option<Vec<String>>, ProviderError> {
async fn fetch_supported_models(&self) -> Result<Vec<String>, ProviderError> {
// Use the existing api_client which already has authentication configured
let response = match self
.api_client
@@ -261,14 +261,12 @@ impl Provider for TetrateProvider {
}
};
// Handle JSON parsing failures gracefully
let json: serde_json::Value = match response.json().await {
Ok(json) => json,
Err(e) => {
tracing::warn!("Failed to parse Tetrate Agent Router Service API response as JSON: {}, falling back to manual model entry", e);
return Ok(None);
}
};
let json: serde_json::Value = response.json().await.map_err(|e| {
ProviderError::ExecutionError(format!(
"Failed to parse Tetrate API response: {}. Please check your API key and account at {}",
e, TETRATE_DOC_URL
))
})?;
// Check for error in response
if let Some(err_obj) = json.get("error") {
@@ -276,10 +274,6 @@ impl Provider for TetrateProvider {
.get("message")
.and_then(|v| v.as_str())
.unwrap_or("unknown error");
tracing::warn!(
"Tetrate Agent Router Service API returned an error: {}",
msg
);
return Err(ProviderError::ExecutionError(format!(
"Tetrate API error: {}. Please check your API key and account at {}",
msg, TETRATE_DOC_URL
@@ -288,13 +282,12 @@ impl Provider for TetrateProvider {
// The response format from /v1/models is expected to be OpenAI-compatible
// It should have a "data" field with an array of model objects
let data = match json.get("data").and_then(|v| v.as_array()) {
Some(data) => data,
None => {
tracing::warn!("Tetrate Agent Router Service API response missing 'data' field, falling back to manual model entry");
return Ok(None);
}
};
let data = json.get("data").and_then(|v| v.as_array()).ok_or_else(|| {
ProviderError::ExecutionError(format!(
"Tetrate API response missing 'data' field. Please check your API key and account at {}",
TETRATE_DOC_URL
))
})?;
let mut models: Vec<String> = data
.iter()
@@ -312,13 +305,8 @@ impl Provider for TetrateProvider {
})
.collect();
if models.is_empty() {
tracing::warn!("No models found in Tetrate Agent Router Service API response, falling back to manual model entry");
return Ok(None);
}
models.sort();
Ok(Some(models))
Ok(models)
}
fn supports_streaming(&self) -> bool {
+2 -2
View File
@@ -239,7 +239,7 @@ impl Provider for VeniceProvider {
self.model.clone()
}
async fn fetch_supported_models(&self) -> Result<Option<Vec<String>>, ProviderError> {
async fn fetch_supported_models(&self) -> Result<Vec<String>, ProviderError> {
let response = self
.api_client
.request(None, &self.models_path)
@@ -264,7 +264,7 @@ impl Provider for VeniceProvider {
})
.collect::<Vec<String>>();
models.sort();
Ok(Some(models))
Ok(models)
}
#[tracing::instrument(
+11 -13
View File
@@ -374,19 +374,17 @@ impl ProviderTester {
dbg!(&models);
println!("===================");
if let Some(models) = models {
assert!(!models.is_empty(), "Expected non-empty model list");
let model_name = &self.provider.get_model_config().model_name;
// Some providers (e.g. Ollama) return names with tags like "qwen3:latest"
// while the configured model name may be just "qwen3".
assert!(
models
.iter()
.any(|m| m == model_name || m.starts_with(&format!("{}:", model_name))),
"Expected model '{}' in supported models",
model_name
);
}
assert!(!models.is_empty(), "Expected non-empty model list");
let model_name = &self.provider.get_model_config().model_name;
// Some providers (e.g. Ollama) return names with tags like "qwen3:latest"
// while the configured model name may be just "qwen3".
assert!(
models
.iter()
.any(|m| m == model_name || m.starts_with(&format!("{}:", model_name))),
"Expected model '{}' in supported models",
model_name
);
Ok(())
}