fix(llm): normalize Ollama model tags to lowercase

Ollama's /api/tags returns whatever casing was used at pull time
(e.g. 'gemma3:12B' if the user ran 'ollama pull gemma3:12B'), but
/api/chat rejects mixed-case tags with a 400. The two code paths
are inconsistent, which surfaces the capitalized tag in the model
dropdown and then silently kills every chat request against it.

Lowercase on read (get_installed_models), on settings write
(update_settings_route), and on ensure_model() input so a legacy
mixed-case user setting can't trigger a spurious re-pull at
startup. The dropdown and stored settings are now always in the
form Ollama will actually accept.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-12 18:02:52 -04:00
parent 95135a665b
commit 9a851de624
2 changed files with 21 additions and 3 deletions
+9 -1
View File
@@ -64,6 +64,15 @@ async def update_settings_route():
if not isinstance(data, dict):
return jsonify({"error": "Expected a JSON object"}), 400
# Normalize model names to lowercase before validation. Ollama's /api/tags
# preserves whatever casing was used at pull time, but /api/chat rejects
# mixed-case tags — lowercasing here keeps the two paths consistent and
# means the stored setting is always in a form Ollama will actually accept.
_MODEL_KEYS = frozenset({"default_model", "background_model"})
for _key in _MODEL_KEYS:
if _key in data and data[_key]:
data[_key] = str(data[_key]).lower()
if "default_model" in data:
installed = await get_installed_models()
if data["default_model"]:
@@ -74,7 +83,6 @@ async def update_settings_route():
# Empty string for model keys means "reset to system default".
# Delete the DB row so get_setting() falls back to Config defaults
# rather than returning "" and breaking model resolution everywhere.
_MODEL_KEYS = frozenset({"default_model", "background_model"})
to_save = {}
for k, v in data.items():
str_v = str(v)