fix(inference): map abstract tier models to provider-native defaults for custom cloud slugs (#2146)

Co-authored-by: Steven Enamakel <enamakel@tinyhumans.ai>
This commit is contained in:
Еmir В.
2026-05-19 21:57:30 -07:00
committed by GitHub
co-authored by Steven Enamakel
parent 81dc8d7ebe
commit 6ace4abf3b
4 changed files with 87 additions and 3 deletions
+1 -1
View File
@@ -1 +1 @@
e22ec719034fdac3994c42a3c040fafa10672219
c90c8a330056286e7c0d05439ae3d4527fa4fafe
+43 -1
View File
@@ -36,6 +36,21 @@ pub const PROVIDER_OPENHUMAN: &str = "openhuman";
/// Prefix for Ollama-local providers: `"ollama:<model>"`.
pub const OLLAMA_PROVIDER_PREFIX: &str = "ollama:";
fn is_abstract_tier_model(model: &str) -> bool {
use crate::openhuman::config::{
MODEL_AGENTIC_V1, MODEL_CODING_V1, MODEL_REASONING_QUICK_V1, MODEL_REASONING_V1,
};
// No dedicated constant for the summarization tier yet; keep the literal
// in sync with the tier name used by the summarizer sub-agent.
const MODEL_SUMMARIZATION_V1: &str = "summarization-v1";
let trimmed = model.trim();
trimmed == MODEL_REASONING_V1
|| trimmed == MODEL_REASONING_QUICK_V1
|| trimmed == MODEL_AGENTIC_V1
|| trimmed == MODEL_CODING_V1
|| trimmed == MODEL_SUMMARIZATION_V1
}
/// Auth-profile storage key for a slug-keyed provider.
///
/// New writes use `"provider:<slug>"`. Lookups also try the bare `<slug>`
@@ -426,12 +441,39 @@ fn make_cloud_provider_by_slug(
// Resolve effective model: use provided model if non-empty, else fall back
// to the entry's legacy default_model (if any), else empty → error.
let effective_model = if model.trim().is_empty() {
let mut effective_model = if model.trim().is_empty() {
entry.default_model.clone().unwrap_or_default()
} else {
model.to_string()
};
if entry.auth_style != AuthStyle::OpenhumanJwt && is_abstract_tier_model(&effective_model) {
if let Some(default_model) = entry
.default_model
.as_deref()
.map(str::trim)
.filter(|m| !m.is_empty() && !is_abstract_tier_model(m))
{
log::info!(
"[providers][chat-factory] role={} slug={} remapping abstract model {} -> {}",
role,
slug,
effective_model,
default_model
);
effective_model = default_model.to_string();
} else {
anyhow::bail!(
"[chat-factory] model '{}' is an abstract tier for role '{}', \
but cloud provider slug '{}' has no concrete default_model configured. \
Set cloud_providers[].default_model to a provider-native model id (e.g. deepseek-v4-pro).",
effective_model,
role,
slug
);
}
}
log::info!(
"[providers][chat-factory] role={} slug={} model={} endpoint_host={}",
role,
@@ -124,6 +124,48 @@ fn openrouter_slug_model() {
assert_eq!(model, "meta-llama/llama-3.1-8b");
}
#[test]
fn custom_provider_remaps_abstract_tier_to_concrete_default_model() {
let mut config = Config::default();
config.cloud_providers.push(CloudProviderCreds {
id: "p_ds".to_string(),
slug: "deepseek".to_string(),
label: "DeepSeek".to_string(),
endpoint: "https://api.deepseek.com/v1".to_string(),
auth_style: AuthStyle::Bearer,
default_model: Some("deepseek-v4-pro".to_string()),
..Default::default()
});
let (_, model) =
create_chat_provider_from_string("reasoning", "deepseek:reasoning-v1", &config)
.expect("abstract tier should remap to concrete default model");
assert_eq!(model, "deepseek-v4-pro");
}
#[test]
fn custom_provider_rejects_abstract_tier_without_concrete_default_model() {
let mut config = Config::default();
config.cloud_providers.push(CloudProviderCreds {
id: "p_ds".to_string(),
slug: "deepseek".to_string(),
label: "DeepSeek".to_string(),
endpoint: "https://api.deepseek.com/v1".to_string(),
auth_style: AuthStyle::Bearer,
default_model: None,
..Default::default()
});
// Can't use `.expect_err(..)` here because `Box<dyn Provider>` doesn't
// implement `Debug`, so the success arm has no Debug to print.
let err = match create_chat_provider_from_string("reasoning", "deepseek:reasoning-v1", &config)
{
Ok(_) => panic!("abstract tier without concrete provider default should fail"),
Err(e) => e,
};
assert!(err.to_string().contains("abstract tier"));
}
#[test]
fn orcarouter_slug_model() {
let mut config = Config::default();