Fix display name for Ollama models (#45287)

Richard Feldman created

Closes #43646

Release Notes:

- Fixed display name for Ollama models

Change summary

crates/language_models/src/provider/ollama.rs | 137 ++++++++++++++++----
1 file changed, 110 insertions(+), 27 deletions(-)

Detailed changes

crates/language_models/src/provider/ollama.rs 🔗

@@ -249,33 +249,7 @@ impl LanguageModelProvider for OllamaLanguageModelProvider {
         }
 
         // Override with available models from settings
-        for setting_model in &OllamaLanguageModelProvider::settings(cx).available_models {
-            let setting_base = setting_model.name.split(':').next().unwrap();
-            if let Some(model) = models
-                .values_mut()
-                .find(|m| m.name.split(':').next().unwrap() == setting_base)
-            {
-                model.max_tokens = setting_model.max_tokens;
-                model.display_name = setting_model.display_name.clone();
-                model.keep_alive = setting_model.keep_alive.clone();
-                model.supports_tools = setting_model.supports_tools;
-                model.supports_vision = setting_model.supports_images;
-                model.supports_thinking = setting_model.supports_thinking;
-            } else {
-                models.insert(
-                    setting_model.name.clone(),
-                    ollama::Model {
-                        name: setting_model.name.clone(),
-                        display_name: setting_model.display_name.clone(),
-                        max_tokens: setting_model.max_tokens,
-                        keep_alive: setting_model.keep_alive.clone(),
-                        supports_tools: setting_model.supports_tools,
-                        supports_vision: setting_model.supports_images,
-                        supports_thinking: setting_model.supports_thinking,
-                    },
-                );
-            }
-        }
+        merge_settings_into_models(&mut models, &settings.available_models);
 
         let mut models = models
             .into_values()
@@ -921,6 +895,35 @@ impl Render for ConfigurationView {
     }
 }
 
+fn merge_settings_into_models(
+    models: &mut HashMap<String, ollama::Model>,
+    available_models: &[AvailableModel],
+) {
+    for setting_model in available_models {
+        if let Some(model) = models.get_mut(&setting_model.name) {
+            model.max_tokens = setting_model.max_tokens;
+            model.display_name = setting_model.display_name.clone();
+            model.keep_alive = setting_model.keep_alive.clone();
+            model.supports_tools = setting_model.supports_tools;
+            model.supports_vision = setting_model.supports_images;
+            model.supports_thinking = setting_model.supports_thinking;
+        } else {
+            models.insert(
+                setting_model.name.clone(),
+                ollama::Model {
+                    name: setting_model.name.clone(),
+                    display_name: setting_model.display_name.clone(),
+                    max_tokens: setting_model.max_tokens,
+                    keep_alive: setting_model.keep_alive.clone(),
+                    supports_tools: setting_model.supports_tools,
+                    supports_vision: setting_model.supports_images,
+                    supports_thinking: setting_model.supports_thinking,
+                },
+            );
+        }
+    }
+}
+
 fn tool_into_ollama(tool: LanguageModelRequestTool) -> ollama::OllamaTool {
     ollama::OllamaTool::Function {
         function: OllamaFunctionTool {
@@ -930,3 +933,83 @@ fn tool_into_ollama(tool: LanguageModelRequestTool) -> ollama::OllamaTool {
         },
     }
 }
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn test_merge_settings_preserves_display_names_for_similar_models() {
+        // Regression test for https://github.com/zed-industries/zed/issues/43646
+        // When multiple models share the same base name (e.g., qwen2.5-coder:1.5b and qwen2.5-coder:3b),
+        // each model should get its own display_name from settings, not a random one.
+
+        let mut models: HashMap<String, ollama::Model> = HashMap::new();
+        models.insert(
+            "qwen2.5-coder:1.5b".to_string(),
+            ollama::Model {
+                name: "qwen2.5-coder:1.5b".to_string(),
+                display_name: None,
+                max_tokens: 4096,
+                keep_alive: None,
+                supports_tools: None,
+                supports_vision: None,
+                supports_thinking: None,
+            },
+        );
+        models.insert(
+            "qwen2.5-coder:3b".to_string(),
+            ollama::Model {
+                name: "qwen2.5-coder:3b".to_string(),
+                display_name: None,
+                max_tokens: 4096,
+                keep_alive: None,
+                supports_tools: None,
+                supports_vision: None,
+                supports_thinking: None,
+            },
+        );
+
+        let available_models = vec![
+            AvailableModel {
+                name: "qwen2.5-coder:1.5b".to_string(),
+                display_name: Some("QWEN2.5 Coder 1.5B".to_string()),
+                max_tokens: 5000,
+                keep_alive: None,
+                supports_tools: Some(true),
+                supports_images: None,
+                supports_thinking: None,
+            },
+            AvailableModel {
+                name: "qwen2.5-coder:3b".to_string(),
+                display_name: Some("QWEN2.5 Coder 3B".to_string()),
+                max_tokens: 6000,
+                keep_alive: None,
+                supports_tools: Some(true),
+                supports_images: None,
+                supports_thinking: None,
+            },
+        ];
+
+        merge_settings_into_models(&mut models, &available_models);
+
+        let model_1_5b = models
+            .get("qwen2.5-coder:1.5b")
+            .expect("1.5b model missing");
+        let model_3b = models.get("qwen2.5-coder:3b").expect("3b model missing");
+
+        assert_eq!(
+            model_1_5b.display_name,
+            Some("QWEN2.5 Coder 1.5B".to_string()),
+            "1.5b model should have its own display_name"
+        );
+        assert_eq!(model_1_5b.max_tokens, 5000);
+
+        assert_eq!(
+            model_3b.display_name,
+            Some("QWEN2.5 Coder 3B".to_string()),
+            "3b model should have its own display_name"
+        );
+        assert_eq!(model_3b.max_tokens, 6000);
+    }
+}