diff --git a/changedetectionio/blueprint/settings/llm.py b/changedetectionio/blueprint/settings/llm.py index 92513a26..a027d751 100644 --- a/changedetectionio/blueprint/settings/llm.py +++ b/changedetectionio/blueprint/settings/llm.py @@ -1,4 +1,7 @@ +import json +import logging import os +import re from flask import Blueprint, jsonify, redirect, url_for, flash from flask_babel import gettext @@ -8,6 +11,44 @@ from changedetectionio.store import ChangeDetectionStore from changedetectionio.auth_decorator import login_optionally_required +class _LiteLLMWarningCapture(logging.Handler): + """Capture warnings emitted on the 'LiteLLM' stdlib logger during a single call. + + litellm.get_valid_models() catches HTTP/auth errors internally, logs a warning, + and returns []. Without capturing that warning we can't tell the user *why* + no models came back (bad key vs. offline vs. genuinely empty model list). + """ + def __init__(self): + super().__init__(level=logging.WARNING) + self.messages = [] + + def emit(self, record): + try: + self.messages.append(record.getMessage()) + except Exception: + pass + + +def _humanize_litellm_error(raw: str) -> str: + # litellm warnings typically look like: + # "Error getting valid models: Failed to get models: { 'error': { 'message': '...' } }" + # Pull the inner provider message when present; otherwise trim the boilerplate. + if not raw: + return raw + m = re.search(r'\{.*\}', raw, re.DOTALL) + if m: + try: + body = json.loads(m.group(0)) + inner = (body.get('error') or {}).get('message') or body.get('message') + if inner: + return inner + except Exception: + pass + cleaned = re.sub(r'^Error getting valid models:\s*', '', raw) + cleaned = re.sub(r'^Failed to get models:\s*', '', cleaned).strip() + return cleaned[:500] + + def construct_llm_blueprint(datastore: ChangeDetectionStore): llm_blueprint = Blueprint('llm', __name__) @@ -41,13 +82,27 @@ def construct_llm_blueprint(datastore: ChangeDetectionStore): try: import litellm logger.debug(f"LLM model list: calling litellm.get_valid_models provider={provider!r} (litellm={litellm_provider!r}) api_base={api_base!r}") - raw = litellm.get_valid_models( - check_provider_endpoint=True, - custom_llm_provider=litellm_provider, - api_key=api_key or None, - api_base=api_base or None, - ) or [] + + capture = _LiteLLMWarningCapture() + litellm_logger = logging.getLogger('LiteLLM') + litellm_logger.addHandler(capture) + try: + raw = litellm.get_valid_models( + check_provider_endpoint=True, + custom_llm_provider=litellm_provider, + api_key=api_key or None, + api_base=api_base or None, + ) or [] + finally: + litellm_logger.removeHandler(capture) + models = sorted({(m if m.startswith(prefix) else prefix + m) for m in raw}) + + if not models and capture.messages: + err = _humanize_litellm_error(capture.messages[-1]) + logger.debug(f"LLM model list: 0 models, surfacing captured litellm warning: {err!r}") + return jsonify({'models': [], 'error': err}), 400 + logger.debug(f"LLM model list: got {len(models)} models for provider={provider!r}") return jsonify({'models': models, 'error': None}) except Exception as e: diff --git a/changedetectionio/blueprint/settings/templates/settings_llm_tab.html b/changedetectionio/blueprint/settings/templates/settings_llm_tab.html index 18233e7f..22a74843 100644 --- a/changedetectionio/blueprint/settings/templates/settings_llm_tab.html +++ b/changedetectionio/blueprint/settings/templates/settings_llm_tab.html @@ -469,7 +469,7 @@ if (!data.models || data.models.length === 0) { statusEl.style.color = '#e67e22'; - statusEl.textContent = '{{ _("No models returned — check your API key.") }}'; + statusEl.textContent = '{{ _("No models returned by the provider.") }}'; selGroup.style.display = 'none'; return; } diff --git a/changedetectionio/forms.py b/changedetectionio/forms.py index 852ed773..9ea93157 100644 --- a/changedetectionio/forms.py +++ b/changedetectionio/forms.py @@ -1106,7 +1106,6 @@ class globalSettingsLLMForm(Form): _l('API Key'), validators=[validators.Optional()], render_kw={ - "placeholder": _l('Leave blank to use LITELLM_API_KEY env var'), "autocomplete": "off", "style": "width: 24em;", }, diff --git a/changedetectionio/translations/cs/LC_MESSAGES/messages.po b/changedetectionio/translations/cs/LC_MESSAGES/messages.po index 33be85c0..3cfadde0 100644 --- a/changedetectionio/translations/cs/LC_MESSAGES/messages.po +++ b/changedetectionio/translations/cs/LC_MESSAGES/messages.po @@ -1121,7 +1121,7 @@ msgid "Loading…" msgstr "" #: changedetectionio/blueprint/settings/templates/settings_llm_tab.html -msgid "No models returned — check your API key." +msgid "No models returned by the provider." msgstr "" #: changedetectionio/blueprint/settings/templates/settings_llm_tab.html @@ -3134,10 +3134,6 @@ msgstr "" msgid "API Key" msgstr "API klíč" -#: changedetectionio/forms.py -msgid "Leave blank to use LITELLM_API_KEY env var" -msgstr "" - #: changedetectionio/forms.py msgid "API Base URL" msgstr "" diff --git a/changedetectionio/translations/de/LC_MESSAGES/messages.po b/changedetectionio/translations/de/LC_MESSAGES/messages.po index dd6b5a7e..e29f3f19 100644 --- a/changedetectionio/translations/de/LC_MESSAGES/messages.po +++ b/changedetectionio/translations/de/LC_MESSAGES/messages.po @@ -1137,7 +1137,7 @@ msgid "Loading…" msgstr "" #: changedetectionio/blueprint/settings/templates/settings_llm_tab.html -msgid "No models returned — check your API key." +msgid "No models returned by the provider." msgstr "" #: changedetectionio/blueprint/settings/templates/settings_llm_tab.html @@ -3186,10 +3186,6 @@ msgstr "" msgid "API Key" msgstr "API-Schlüssel" -#: changedetectionio/forms.py -msgid "Leave blank to use LITELLM_API_KEY env var" -msgstr "" - #: changedetectionio/forms.py msgid "API Base URL" msgstr "" diff --git a/changedetectionio/translations/en_GB/LC_MESSAGES/messages.po b/changedetectionio/translations/en_GB/LC_MESSAGES/messages.po index 5b538c7b..7de77a5b 100644 --- a/changedetectionio/translations/en_GB/LC_MESSAGES/messages.po +++ b/changedetectionio/translations/en_GB/LC_MESSAGES/messages.po @@ -1119,7 +1119,7 @@ msgid "Loading…" msgstr "" #: changedetectionio/blueprint/settings/templates/settings_llm_tab.html -msgid "No models returned — check your API key." +msgid "No models returned by the provider." msgstr "" #: changedetectionio/blueprint/settings/templates/settings_llm_tab.html @@ -3128,10 +3128,6 @@ msgstr "" msgid "API Key" msgstr "" -#: changedetectionio/forms.py -msgid "Leave blank to use LITELLM_API_KEY env var" -msgstr "" - #: changedetectionio/forms.py msgid "API Base URL" msgstr "" diff --git a/changedetectionio/translations/en_US/LC_MESSAGES/messages.po b/changedetectionio/translations/en_US/LC_MESSAGES/messages.po index a3f43c35..8e479203 100644 --- a/changedetectionio/translations/en_US/LC_MESSAGES/messages.po +++ b/changedetectionio/translations/en_US/LC_MESSAGES/messages.po @@ -1119,7 +1119,7 @@ msgid "Loading…" msgstr "" #: changedetectionio/blueprint/settings/templates/settings_llm_tab.html -msgid "No models returned — check your API key." +msgid "No models returned by the provider." msgstr "" #: changedetectionio/blueprint/settings/templates/settings_llm_tab.html @@ -3128,10 +3128,6 @@ msgstr "" msgid "API Key" msgstr "" -#: changedetectionio/forms.py -msgid "Leave blank to use LITELLM_API_KEY env var" -msgstr "" - #: changedetectionio/forms.py msgid "API Base URL" msgstr "" diff --git a/changedetectionio/translations/es/LC_MESSAGES/messages.po b/changedetectionio/translations/es/LC_MESSAGES/messages.po index 7501e355..faa7e30d 100644 --- a/changedetectionio/translations/es/LC_MESSAGES/messages.po +++ b/changedetectionio/translations/es/LC_MESSAGES/messages.po @@ -1157,7 +1157,7 @@ msgid "Loading…" msgstr "" #: changedetectionio/blueprint/settings/templates/settings_llm_tab.html -msgid "No models returned — check your API key." +msgid "No models returned by the provider." msgstr "" #: changedetectionio/blueprint/settings/templates/settings_llm_tab.html @@ -3201,10 +3201,6 @@ msgstr "" msgid "API Key" msgstr "Clave API" -#: changedetectionio/forms.py -msgid "Leave blank to use LITELLM_API_KEY env var" -msgstr "" - #: changedetectionio/forms.py msgid "API Base URL" msgstr "" diff --git a/changedetectionio/translations/fr/LC_MESSAGES/messages.po b/changedetectionio/translations/fr/LC_MESSAGES/messages.po index 023e342c..ac1a4d8a 100644 --- a/changedetectionio/translations/fr/LC_MESSAGES/messages.po +++ b/changedetectionio/translations/fr/LC_MESSAGES/messages.po @@ -1125,7 +1125,7 @@ msgid "Loading…" msgstr "" #: changedetectionio/blueprint/settings/templates/settings_llm_tab.html -msgid "No models returned — check your API key." +msgid "No models returned by the provider." msgstr "" #: changedetectionio/blueprint/settings/templates/settings_llm_tab.html @@ -3141,10 +3141,6 @@ msgstr "" msgid "API Key" msgstr "Clé API" -#: changedetectionio/forms.py -msgid "Leave blank to use LITELLM_API_KEY env var" -msgstr "" - #: changedetectionio/forms.py msgid "API Base URL" msgstr "" diff --git a/changedetectionio/translations/it/LC_MESSAGES/messages.po b/changedetectionio/translations/it/LC_MESSAGES/messages.po index a9b3dd09..e1b03bbf 100644 --- a/changedetectionio/translations/it/LC_MESSAGES/messages.po +++ b/changedetectionio/translations/it/LC_MESSAGES/messages.po @@ -1121,7 +1121,7 @@ msgid "Loading…" msgstr "" #: changedetectionio/blueprint/settings/templates/settings_llm_tab.html -msgid "No models returned — check your API key." +msgid "No models returned by the provider." msgstr "" #: changedetectionio/blueprint/settings/templates/settings_llm_tab.html @@ -3130,10 +3130,6 @@ msgstr "" msgid "API Key" msgstr "Chiave API" -#: changedetectionio/forms.py -msgid "Leave blank to use LITELLM_API_KEY env var" -msgstr "" - #: changedetectionio/forms.py msgid "API Base URL" msgstr "" diff --git a/changedetectionio/translations/ja/LC_MESSAGES/messages.po b/changedetectionio/translations/ja/LC_MESSAGES/messages.po index f3ccd6e5..1d7f983b 100644 --- a/changedetectionio/translations/ja/LC_MESSAGES/messages.po +++ b/changedetectionio/translations/ja/LC_MESSAGES/messages.po @@ -1126,7 +1126,7 @@ msgid "Loading…" msgstr "" #: changedetectionio/blueprint/settings/templates/settings_llm_tab.html -msgid "No models returned — check your API key." +msgid "No models returned by the provider." msgstr "" #: changedetectionio/blueprint/settings/templates/settings_llm_tab.html @@ -3147,10 +3147,6 @@ msgstr "" msgid "API Key" msgstr "APIキー" -#: changedetectionio/forms.py -msgid "Leave blank to use LITELLM_API_KEY env var" -msgstr "" - #: changedetectionio/forms.py msgid "API Base URL" msgstr "" diff --git a/changedetectionio/translations/ko/LC_MESSAGES/messages.mo b/changedetectionio/translations/ko/LC_MESSAGES/messages.mo index 3d1727a5..1fba60e4 100644 Binary files a/changedetectionio/translations/ko/LC_MESSAGES/messages.mo and b/changedetectionio/translations/ko/LC_MESSAGES/messages.mo differ diff --git a/changedetectionio/translations/ko/LC_MESSAGES/messages.po b/changedetectionio/translations/ko/LC_MESSAGES/messages.po index 12b40dd0..8b57cd53 100644 --- a/changedetectionio/translations/ko/LC_MESSAGES/messages.po +++ b/changedetectionio/translations/ko/LC_MESSAGES/messages.po @@ -1127,8 +1127,8 @@ msgid "Loading…" msgstr "불러오는 중..." #: changedetectionio/blueprint/settings/templates/settings_llm_tab.html -msgid "No models returned — check your API key." -msgstr "반환된 모델이 없습니다. API 키를 확인하세요." +msgid "No models returned by the provider." +msgstr "" #: changedetectionio/blueprint/settings/templates/settings_llm_tab.html msgid "— choose a model —" @@ -3138,10 +3138,6 @@ msgstr "모델" msgid "API Key" msgstr "API 키" -#: changedetectionio/forms.py -msgid "Leave blank to use LITELLM_API_KEY env var" -msgstr "LITELLM_API_KEY 환경 변수를 사용하려면 비워 두세요" - #: changedetectionio/forms.py msgid "API Base URL" msgstr "API 기본 URL" diff --git a/changedetectionio/translations/messages.pot b/changedetectionio/translations/messages.pot index ef817899..cfd0fbea 100644 --- a/changedetectionio/translations/messages.pot +++ b/changedetectionio/translations/messages.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: changedetection.io 0.55.3\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2026-05-02 18:29+0900\n" +"POT-Creation-Date: 2026-05-12 11:08+0200\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -1118,7 +1118,7 @@ msgid "Loading…" msgstr "" #: changedetectionio/blueprint/settings/templates/settings_llm_tab.html -msgid "No models returned — check your API key." +msgid "No models returned by the provider." msgstr "" #: changedetectionio/blueprint/settings/templates/settings_llm_tab.html @@ -3127,10 +3127,6 @@ msgstr "" msgid "API Key" msgstr "" -#: changedetectionio/forms.py -msgid "Leave blank to use LITELLM_API_KEY env var" -msgstr "" - #: changedetectionio/forms.py msgid "API Base URL" msgstr "" diff --git a/changedetectionio/translations/pt_BR/LC_MESSAGES/messages.po b/changedetectionio/translations/pt_BR/LC_MESSAGES/messages.po index 99666a0f..0668bfe4 100644 --- a/changedetectionio/translations/pt_BR/LC_MESSAGES/messages.po +++ b/changedetectionio/translations/pt_BR/LC_MESSAGES/messages.po @@ -1144,7 +1144,7 @@ msgid "Loading…" msgstr "" #: changedetectionio/blueprint/settings/templates/settings_llm_tab.html -msgid "No models returned — check your API key." +msgid "No models returned by the provider." msgstr "" #: changedetectionio/blueprint/settings/templates/settings_llm_tab.html @@ -3178,10 +3178,6 @@ msgstr "" msgid "API Key" msgstr "Chave da API" -#: changedetectionio/forms.py -msgid "Leave blank to use LITELLM_API_KEY env var" -msgstr "" - #: changedetectionio/forms.py msgid "API Base URL" msgstr "" diff --git a/changedetectionio/translations/tr/LC_MESSAGES/messages.po b/changedetectionio/translations/tr/LC_MESSAGES/messages.po index 7f2f79c9..feac964d 100644 --- a/changedetectionio/translations/tr/LC_MESSAGES/messages.po +++ b/changedetectionio/translations/tr/LC_MESSAGES/messages.po @@ -1154,7 +1154,7 @@ msgid "Loading…" msgstr "" #: changedetectionio/blueprint/settings/templates/settings_llm_tab.html -msgid "No models returned — check your API key." +msgid "No models returned by the provider." msgstr "" #: changedetectionio/blueprint/settings/templates/settings_llm_tab.html @@ -3181,10 +3181,6 @@ msgstr "" msgid "API Key" msgstr "API Anahtarı" -#: changedetectionio/forms.py -msgid "Leave blank to use LITELLM_API_KEY env var" -msgstr "" - #: changedetectionio/forms.py msgid "API Base URL" msgstr "" diff --git a/changedetectionio/translations/uk/LC_MESSAGES/messages.po b/changedetectionio/translations/uk/LC_MESSAGES/messages.po index 315466f3..2dd786e3 100644 --- a/changedetectionio/translations/uk/LC_MESSAGES/messages.po +++ b/changedetectionio/translations/uk/LC_MESSAGES/messages.po @@ -1134,7 +1134,7 @@ msgid "Loading…" msgstr "" #: changedetectionio/blueprint/settings/templates/settings_llm_tab.html -msgid "No models returned — check your API key." +msgid "No models returned by the provider." msgstr "" #: changedetectionio/blueprint/settings/templates/settings_llm_tab.html @@ -3160,10 +3160,6 @@ msgstr "" msgid "API Key" msgstr "Ключ API" -#: changedetectionio/forms.py -msgid "Leave blank to use LITELLM_API_KEY env var" -msgstr "" - #: changedetectionio/forms.py msgid "API Base URL" msgstr "" diff --git a/changedetectionio/translations/zh/LC_MESSAGES/messages.po b/changedetectionio/translations/zh/LC_MESSAGES/messages.po index 31197a54..d2c28a18 100644 --- a/changedetectionio/translations/zh/LC_MESSAGES/messages.po +++ b/changedetectionio/translations/zh/LC_MESSAGES/messages.po @@ -1123,7 +1123,7 @@ msgid "Loading…" msgstr "" #: changedetectionio/blueprint/settings/templates/settings_llm_tab.html -msgid "No models returned — check your API key." +msgid "No models returned by the provider." msgstr "" #: changedetectionio/blueprint/settings/templates/settings_llm_tab.html @@ -3133,10 +3133,6 @@ msgstr "" msgid "API Key" msgstr "API密钥" -#: changedetectionio/forms.py -msgid "Leave blank to use LITELLM_API_KEY env var" -msgstr "" - #: changedetectionio/forms.py msgid "API Base URL" msgstr "" diff --git a/changedetectionio/translations/zh_Hant_TW/LC_MESSAGES/messages.po b/changedetectionio/translations/zh_Hant_TW/LC_MESSAGES/messages.po index adbf2e23..93c08bee 100644 --- a/changedetectionio/translations/zh_Hant_TW/LC_MESSAGES/messages.po +++ b/changedetectionio/translations/zh_Hant_TW/LC_MESSAGES/messages.po @@ -1122,7 +1122,7 @@ msgid "Loading…" msgstr "" #: changedetectionio/blueprint/settings/templates/settings_llm_tab.html -msgid "No models returned — check your API key." +msgid "No models returned by the provider." msgstr "" #: changedetectionio/blueprint/settings/templates/settings_llm_tab.html @@ -3132,10 +3132,6 @@ msgstr "" msgid "API Key" msgstr "API 金鑰" -#: changedetectionio/forms.py -msgid "Leave blank to use LITELLM_API_KEY env var" -msgstr "" - #: changedetectionio/forms.py msgid "API Base URL" msgstr ""