feat: apply provider quirks to models created from a name
Unknown chat/reranker models resolved through the create-from-name fallback now inherit their provider's family rules (require_max_tokens, reasoning defaults, etc.) from the quirks overlay, and the model completion table marks reasoning-capable models with a brain glyph.
This commit is contained in:
2 files changed
+61
-3
No files matched your search
+50
-1
@@ -1,5 +1,7 @@
|
|||||||
use super::{
|
use super::{
|
||||||
ApiPatch, MessageContentToolCalls, RequestPatch, list_all_models, list_client_names,
|
ApiPatch, MessageContentToolCalls, RequestPatch,
|
||||||
|
catalog::{ALL_QUIRKS, apply_rules, quirks_for},
|
||||||
|
list_all_models, list_client_names,
|
||||||
message::{Message, MessageContent, MessageContentPart},
|
message::{Message, MessageContent, MessageContentPart},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -77,6 +79,9 @@ impl Model {
|
|||||||
{
|
{
|
||||||
let mut new_model = Self::new(client_name, model_name);
|
let mut new_model = Self::new(client_name, model_name);
|
||||||
new_model.data.model_type = model_type.to_string();
|
new_model.data.model_type = model_type.to_string();
|
||||||
|
if let Some(quirks) = quirks_for(&ALL_QUIRKS, client_name) {
|
||||||
|
apply_rules(quirks, &mut new_model.data);
|
||||||
|
}
|
||||||
return Ok(new_model);
|
return Ok(new_model);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -132,6 +137,7 @@ impl Model {
|
|||||||
output_price,
|
output_price,
|
||||||
supports_vision,
|
supports_vision,
|
||||||
supports_function_calling,
|
supports_function_calling,
|
||||||
|
reasoning_levels,
|
||||||
..
|
..
|
||||||
} = &self.data;
|
} = &self.data;
|
||||||
let max_input_tokens = stringify_option_value(max_input_tokens);
|
let max_input_tokens = stringify_option_value(max_input_tokens);
|
||||||
@@ -145,6 +151,9 @@ impl Model {
|
|||||||
if *supports_function_calling {
|
if *supports_function_calling {
|
||||||
capabilities.push('⚒');
|
capabilities.push('⚒');
|
||||||
};
|
};
|
||||||
|
if !reasoning_levels.is_empty() {
|
||||||
|
capabilities.push('🧠');
|
||||||
|
};
|
||||||
let capabilities: String = capabilities
|
let capabilities: String = capabilities
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|v| format!("{v} "))
|
.map(|v| format!("{v} "))
|
||||||
@@ -419,3 +428,43 @@ where
|
|||||||
None => "-".to_string(),
|
None => "-".to_string(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::client::ClientConfig;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn created_from_name_inherits_family_rules() {
|
||||||
|
// The model registries are process-wide caches seeded by a ctor (see
|
||||||
|
// the request_context tests) that registers a "claude" client; the
|
||||||
|
// config below documents the shape a real lookup would use.
|
||||||
|
let claude: ClientConfig = serde_yaml::from_str(
|
||||||
|
"type: claude\nmodels:\n - name: test-claude-embedder\n type: embedding\n",
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
let config = AppConfig {
|
||||||
|
clients: vec![claude],
|
||||||
|
..AppConfig::default()
|
||||||
|
};
|
||||||
|
let model = Model::retrieve_model(
|
||||||
|
&config,
|
||||||
|
"claude:claude-definitely-not-a-real-model",
|
||||||
|
ModelType::Chat,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(model.name(), "claude-definitely-not-a-real-model");
|
||||||
|
assert_eq!(model.model_type(), ModelType::Chat);
|
||||||
|
assert!(model.data.require_max_tokens);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn description_shows_brain_glyph() {
|
||||||
|
let mut model = Model::new("claude", "some-model");
|
||||||
|
model.data.reasoning_levels = vec!["low".to_string(), "high".to_string()];
|
||||||
|
assert!(model.description().contains('🧠'));
|
||||||
|
|
||||||
|
model.data.reasoning_levels.clear();
|
||||||
|
assert!(!model.description().contains('🧠'));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5232,7 +5232,7 @@ mod tests {
|
|||||||
// with an empty client list, which would permanently pin every later
|
// with an empty client list, which would permanently pin every later
|
||||||
// model lookup in this process to "unknown model" and make any test that
|
// model lookup in this process to "unknown model" and make any test that
|
||||||
// needs a resolvable model dependent on test ordering. Seed the caches
|
// needs a resolvable model dependent on test ordering. Seed the caches
|
||||||
// before any test runs with one client, "test-seeded", exposing a single
|
// before any test runs with a client, "test-seeded", exposing a single
|
||||||
// embedding model. Deliberately NO chat model: some tests assert that no
|
// embedding model. Deliberately NO chat model: some tests assert that no
|
||||||
// chat model is available, and chat lookups don't need one — a
|
// chat model is available, and chat lookups don't need one — a
|
||||||
// "test-seeded:<anything>" chat id resolves through the create-from-name
|
// "test-seeded:<anything>" chat id resolves through the create-from-name
|
||||||
@@ -5252,8 +5252,17 @@ mod tests {
|
|||||||
embedder.model_type = "embedding".to_string();
|
embedder.model_type = "embedding".to_string();
|
||||||
config.models = vec![embedder];
|
config.models = vec![embedder];
|
||||||
}
|
}
|
||||||
|
// A claude client is seeded alongside it so tests can exercise
|
||||||
|
// provider-quirk inheritance through the create-from-name fallback.
|
||||||
|
// It deliberately carries one embedding model: an empty `models` list
|
||||||
|
// would fall back to the full embedded claude catalog and register
|
||||||
|
// real chat models, breaking the no-chat-model invariant above.
|
||||||
|
let claude: ClientConfig = serde_yaml::from_str(
|
||||||
|
"type: claude\nmodels:\n - name: test-claude-embedder\n type: embedding\n",
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
let config = AppConfig {
|
let config = AppConfig {
|
||||||
clients: vec![client],
|
clients: vec![client, claude],
|
||||||
..AppConfig::default()
|
..AppConfig::default()
|
||||||
};
|
};
|
||||||
let _ = list_client_names(&config);
|
let _ = list_client_names(&config);
|
||||||
|
|||||||
Reference in new issue
Block a user