mirror of
https://github.com/tinyhumansai/openhuman.git
synced 2026-07-27 21:08:00 +00:00
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:
co-authored by
Steven Enamakel
parent
81dc8d7ebe
commit
6ace4abf3b
@@ -1 +1 @@
|
||||
e22ec719034fdac3994c42a3c040fafa10672219
|
||||
c90c8a330056286e7c0d05439ae3d4527fa4fafe
|
||||
Vendored
+1
-1
Submodule app/src-tauri/vendor/tauri-cef updated: 4cabccfa82...c90c8a3300
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user