fix: preserve requested model ID casing in registry resolution (#733)

Previously, ModelRegistry::resolve() lowercased the requested model name
before looking it up in the alias map, and always returned the registry's
canonical (lowercase) model ID.  This broke third-party API providers
that enforce case-sensitive model name matching.

Now when the resolved model ID differs from the requested name only in
case (eq_ignore_ascii_case), the requested casing is preserved.

Closes #729

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
AGSaturn
2026-05-06 17:52:10 +08:00
committed by GitHub
parent b62af3e047
commit 8e987a4702
+46 -2
View File
@@ -227,7 +227,7 @@ impl ModelRegistry {
{
return ModelResolution {
requested: Some(name.to_string()),
resolved: model,
resolved: preserve_requested_model_id_case(model, name),
used_fallback: false,
fallback_chain,
};
@@ -235,7 +235,7 @@ impl ModelRegistry {
if let Some(idx) = self.alias_map.get(&normalize(name)) {
return ModelResolution {
requested: Some(name.to_string()),
resolved: self.models[*idx].clone(),
resolved: preserve_requested_model_id_case(self.models[*idx].clone(), name),
used_fallback: false,
fallback_chain,
};
@@ -283,6 +283,14 @@ fn model_matches(model: &ModelInfo, requested: &str) -> bool {
.any(|alias| normalize(alias) == requested)
}
fn preserve_requested_model_id_case(mut model: ModelInfo, requested: &str) -> ModelInfo {
let requested = requested.trim();
if model.id.eq_ignore_ascii_case(requested) {
model.id = requested.to_string();
}
model
}
#[cfg(test)]
mod tests {
use super::*;
@@ -406,4 +414,40 @@ mod tests {
assert_eq!(resolved.resolved.provider, ProviderKind::Vllm);
assert_eq!(resolved.resolved.id, "deepseek-ai/DeepSeek-V4-Flash");
}
#[test]
fn preserves_requested_model_casing_for_third_party_providers() {
let registry = ModelRegistry::default();
let resolved = registry.resolve(Some("DeepSeek-V4-Pro"), None);
assert_eq!(resolved.resolved.provider, ProviderKind::Deepseek);
assert_eq!(resolved.resolved.id, "DeepSeek-V4-Pro");
}
#[test]
fn preserves_requested_model_casing_with_provider_hint() {
let registry = ModelRegistry::default();
let resolved = registry.resolve(Some("DeepSeek-V4-Pro"), Some(ProviderKind::Deepseek));
assert_eq!(resolved.resolved.provider, ProviderKind::Deepseek);
assert_eq!(resolved.resolved.id, "DeepSeek-V4-Pro");
}
#[test]
fn preserves_requested_model_casing_without_surrounding_whitespace() {
let registry = ModelRegistry::default();
let resolved = registry.resolve(Some(" DeepSeek-V4-Pro "), None);
assert_eq!(resolved.resolved.provider, ProviderKind::Deepseek);
assert_eq!(resolved.resolved.id, "DeepSeek-V4-Pro");
}
#[test]
fn alias_match_does_not_override_requested_casing() {
let registry = ModelRegistry::default();
let resolved = registry.resolve(Some("deepseek-reasoner"), None);
assert_eq!(resolved.resolved.provider, ProviderKind::Deepseek);
assert_eq!(resolved.resolved.id, "deepseek-v4-flash");
}
}