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::{
|
||||
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},
|
||||
};
|
||||
|
||||
@@ -77,6 +79,9 @@ impl Model {
|
||||
{
|
||||
let mut new_model = Self::new(client_name, model_name);
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -132,6 +137,7 @@ impl Model {
|
||||
output_price,
|
||||
supports_vision,
|
||||
supports_function_calling,
|
||||
reasoning_levels,
|
||||
..
|
||||
} = &self.data;
|
||||
let max_input_tokens = stringify_option_value(max_input_tokens);
|
||||
@@ -145,6 +151,9 @@ impl Model {
|
||||
if *supports_function_calling {
|
||||
capabilities.push('⚒');
|
||||
};
|
||||
if !reasoning_levels.is_empty() {
|
||||
capabilities.push('🧠');
|
||||
};
|
||||
let capabilities: String = capabilities
|
||||
.into_iter()
|
||||
.map(|v| format!("{v} "))
|
||||
@@ -419,3 +428,43 @@ where
|
||||
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('🧠'));
|
||||
}
|
||||
}
|
||||
@@ -5219,7 +5219,7 @@ mod tests {
|
||||
// with an empty client list, which would permanently pin every later
|
||||
// model lookup in this process to "unknown model" and make any test that
|
||||
// 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
|
||||
// chat model is available, and chat lookups don't need one — a
|
||||
// "test-seeded:<anything>" chat id resolves through the create-from-name
|
||||
@@ -5239,8 +5239,17 @@ mod tests {
|
||||
embedder.model_type = "embedding".to_string();
|
||||
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 {
|
||||
clients: vec![client],
|
||||
clients: vec![client, claude],
|
||||
..AppConfig::default()
|
||||
};
|
||||
let _ = list_client_names(&config);
|
||||
|
||||
Reference in new issue
Block a user