mirror of
https://github.com/MacRimi/ProxMenux.git
synced 2026-09-14 18:56:52 +00:00
fix(notifications): respect OpenAI-compatible model aliases
This commit is contained in:
@@ -895,6 +895,40 @@ class NotificationManager:
|
||||
self._config[key] = value
|
||||
except Exception as e:
|
||||
print(f"[NotificationManager] Failed to save setting {key}: {e}")
|
||||
|
||||
def _active_ai_model(self, provider_name: str) -> str:
|
||||
"""Return the model selected for the active provider.
|
||||
|
||||
`ai_model` is the legacy global key. Newer settings persist
|
||||
provider-specific models as `ai_model_<provider>`, and those must win
|
||||
whenever present so custom endpoints keep their opaque aliases.
|
||||
"""
|
||||
return (
|
||||
self._config.get(f'ai_model_{provider_name}', '')
|
||||
or self._config.get('ai_model', '')
|
||||
)
|
||||
|
||||
def _build_ai_config(self) -> Dict[str, Any]:
|
||||
"""Build the shared AI config passed to notification rewriters."""
|
||||
ai_provider = self._config.get('ai_provider', 'groq')
|
||||
ai_api_key = self._config.get(f'ai_api_key_{ai_provider}', '') or self._config.get('ai_api_key', '')
|
||||
return {
|
||||
'ai_enabled': self._config.get('ai_enabled', 'false'),
|
||||
'ai_provider': ai_provider,
|
||||
'ai_api_key': ai_api_key,
|
||||
'ai_model': self._active_ai_model(ai_provider),
|
||||
'ai_language': self._config.get('ai_language', 'en'),
|
||||
'ai_ollama_url': self._config.get('ai_ollama_url', ''),
|
||||
# `ai_openai_base_url` was previously dropped from this dict and
|
||||
# the downstream `notification_templates.AIRewriter` read it from
|
||||
# the dict — meaning a user who configured LiteLLM / Azure as a
|
||||
# custom base_url passed the "Test AI" check (which DOES pass it)
|
||||
# but every real notification silently went to api.openai.com.
|
||||
# Privacy + UX deception bug. Audit Tier 3.2 #1.
|
||||
'ai_openai_base_url': self._config.get('ai_openai_base_url', ''),
|
||||
'ai_prompt_mode': self._config.get('ai_prompt_mode', 'default'),
|
||||
'ai_custom_prompt': self._config.get('ai_custom_prompt', ''),
|
||||
}
|
||||
|
||||
def _rebuild_channels(self):
|
||||
"""Rebuild channel instances from current config.
|
||||
@@ -1216,26 +1250,7 @@ class NotificationManager:
|
||||
default_event_enabled = 'true' if template.get('default_enabled', True) else 'false'
|
||||
|
||||
# Build AI config once (shared across channels, detail_level varies)
|
||||
# Use per-provider API key
|
||||
ai_provider = self._config.get('ai_provider', 'groq')
|
||||
ai_api_key = self._config.get(f'ai_api_key_{ai_provider}', '') or self._config.get('ai_api_key', '')
|
||||
ai_config = {
|
||||
'ai_enabled': self._config.get('ai_enabled', 'false'),
|
||||
'ai_provider': ai_provider,
|
||||
'ai_api_key': ai_api_key,
|
||||
'ai_model': self._config.get('ai_model', ''),
|
||||
'ai_language': self._config.get('ai_language', 'en'),
|
||||
'ai_ollama_url': self._config.get('ai_ollama_url', ''),
|
||||
# `ai_openai_base_url` was previously dropped from this dict and
|
||||
# the downstream `notification_templates.AIRewriter` read it from
|
||||
# the dict — meaning a user who configured LiteLLM / Azure as a
|
||||
# custom base_url passed the "Test AI" check (which DOES pass it)
|
||||
# but every real notification silently went to api.openai.com.
|
||||
# Privacy + UX deception bug. Audit Tier 3.2 #1.
|
||||
'ai_openai_base_url': self._config.get('ai_openai_base_url', ''),
|
||||
'ai_prompt_mode': self._config.get('ai_prompt_mode', 'default'),
|
||||
'ai_custom_prompt': self._config.get('ai_custom_prompt', ''),
|
||||
}
|
||||
ai_config = self._build_ai_config()
|
||||
|
||||
# Get journal context if available (will be enriched per-channel based on detail_level)
|
||||
raw_journal_context = data.get('_journal_context', '')
|
||||
@@ -2163,26 +2178,8 @@ class NotificationManager:
|
||||
message = rendered['body']
|
||||
severity = severity or rendered['severity']
|
||||
|
||||
# AI config for enhancement - use per-provider API key
|
||||
ai_provider = self._config.get('ai_provider', 'groq')
|
||||
ai_api_key = self._config.get(f'ai_api_key_{ai_provider}', '') or self._config.get('ai_api_key', '')
|
||||
ai_config = {
|
||||
'ai_enabled': self._config.get('ai_enabled', 'false'),
|
||||
'ai_provider': ai_provider,
|
||||
'ai_api_key': ai_api_key,
|
||||
'ai_model': self._config.get('ai_model', ''),
|
||||
'ai_language': self._config.get('ai_language', 'en'),
|
||||
'ai_ollama_url': self._config.get('ai_ollama_url', ''),
|
||||
# `ai_openai_base_url` was previously dropped from this dict and
|
||||
# the downstream `notification_templates.AIRewriter` read it from
|
||||
# the dict — meaning a user who configured LiteLLM / Azure as a
|
||||
# custom base_url passed the "Test AI" check (which DOES pass it)
|
||||
# but every real notification silently went to api.openai.com.
|
||||
# Privacy + UX deception bug. Audit Tier 3.2 #1.
|
||||
'ai_openai_base_url': self._config.get('ai_openai_base_url', ''),
|
||||
'ai_prompt_mode': self._config.get('ai_prompt_mode', 'default'),
|
||||
'ai_custom_prompt': self._config.get('ai_custom_prompt', ''),
|
||||
}
|
||||
# AI config for enhancement
|
||||
ai_config = self._build_ai_config()
|
||||
|
||||
results = {}
|
||||
channels_sent = []
|
||||
@@ -2268,26 +2265,9 @@ class NotificationManager:
|
||||
else:
|
||||
return {'success': False, 'error': f'Channel {channel_name} not configured'}
|
||||
|
||||
# AI config for enhancement - use per-provider API key
|
||||
ai_provider = self._config.get('ai_provider', 'groq')
|
||||
ai_api_key = self._config.get(f'ai_api_key_{ai_provider}', '') or self._config.get('ai_api_key', '')
|
||||
ai_config = {
|
||||
'ai_enabled': self._config.get('ai_enabled', 'false'),
|
||||
'ai_provider': ai_provider,
|
||||
'ai_api_key': ai_api_key,
|
||||
'ai_model': self._config.get('ai_model', ''),
|
||||
'ai_language': self._config.get('ai_language', 'en'),
|
||||
'ai_ollama_url': self._config.get('ai_ollama_url', ''),
|
||||
# `ai_openai_base_url` was previously dropped from this dict and
|
||||
# the downstream `notification_templates.AIRewriter` read it from
|
||||
# the dict — meaning a user who configured LiteLLM / Azure as a
|
||||
# custom base_url passed the "Test AI" check (which DOES pass it)
|
||||
# but every real notification silently went to api.openai.com.
|
||||
# Privacy + UX deception bug. Audit Tier 3.2 #1.
|
||||
'ai_openai_base_url': self._config.get('ai_openai_base_url', ''),
|
||||
'ai_prompt_mode': self._config.get('ai_prompt_mode', 'default'),
|
||||
'ai_custom_prompt': self._config.get('ai_custom_prompt', ''),
|
||||
}
|
||||
# AI config for enhancement
|
||||
ai_config = self._build_ai_config()
|
||||
ai_provider = ai_config.get('ai_provider', 'groq')
|
||||
|
||||
ai_enabled = self._config.get('ai_enabled', 'false')
|
||||
if isinstance(ai_enabled, str):
|
||||
@@ -2718,7 +2698,7 @@ class NotificationManager:
|
||||
'ai_provider': current_provider,
|
||||
'ai_api_keys': ai_api_keys,
|
||||
'ai_models': ai_models,
|
||||
'ai_model': self._config.get('ai_model', ''),
|
||||
'ai_model': self._active_ai_model(current_provider),
|
||||
'ai_language': self._config.get('ai_language', 'en'),
|
||||
'ai_ollama_url': self._config.get('ai_ollama_url', 'http://localhost:11434'),
|
||||
'ai_openai_base_url': self._config.get('ai_openai_base_url', ''),
|
||||
@@ -2893,7 +2873,7 @@ class NotificationManager:
|
||||
return {'checked': False, 'migrated': False, 'message': 'AI not enabled'}
|
||||
|
||||
provider_name = self._config.get('ai_provider', 'groq')
|
||||
current_model = self._config.get('ai_model', '')
|
||||
current_model = self._active_ai_model(provider_name)
|
||||
|
||||
# Skip Ollama - user manages their own models
|
||||
if provider_name == 'ollama':
|
||||
@@ -2927,7 +2907,13 @@ class NotificationManager:
|
||||
print(f"[NotificationManager] Failed to load verified models: {e}")
|
||||
|
||||
from ai_providers import get_provider
|
||||
provider = get_provider(provider_name, api_key=api_key, model=current_model)
|
||||
provider_kwargs = {
|
||||
'api_key': api_key,
|
||||
'model': current_model,
|
||||
}
|
||||
if provider_name == 'openai':
|
||||
provider_kwargs['base_url'] = self._config.get('ai_openai_base_url', '')
|
||||
provider = get_provider(provider_name, **provider_kwargs)
|
||||
|
||||
if not provider:
|
||||
return {'checked': False, 'migrated': False, 'message': f'Unknown provider: {provider_name}'}
|
||||
@@ -2935,8 +2921,24 @@ class NotificationManager:
|
||||
# Get available models from API
|
||||
api_models = provider.list_models()
|
||||
|
||||
# Combine: use verified models that are also in API (or all verified if API fails)
|
||||
if api_models and verified_models:
|
||||
# Combine: official providers intersect the API list with the
|
||||
# verified catalogue. Custom OpenAI-compatible endpoints are
|
||||
# authoritative for their own opaque aliases, so do not intersect
|
||||
# them with ProxMenux's bundled official OpenAI IDs.
|
||||
openai_custom_endpoint = (
|
||||
provider_name == 'openai'
|
||||
and bool(self._config.get('ai_openai_base_url', '').strip())
|
||||
)
|
||||
if openai_custom_endpoint:
|
||||
if not api_models:
|
||||
return {
|
||||
'checked': True,
|
||||
'migrated': False,
|
||||
'new_model': current_model,
|
||||
'message': 'Could not retrieve custom endpoint model list'
|
||||
}
|
||||
available_models = api_models
|
||||
elif api_models and verified_models:
|
||||
available_models = [m for m in verified_models if m in api_models]
|
||||
elif verified_models:
|
||||
available_models = verified_models
|
||||
@@ -2970,13 +2972,16 @@ class NotificationManager:
|
||||
try:
|
||||
conn = sqlite3.connect(str(DB_PATH), timeout=10)
|
||||
cursor = conn.cursor()
|
||||
cursor.execute('''
|
||||
INSERT OR REPLACE INTO user_settings (setting_key, setting_value, updated_at)
|
||||
VALUES (?, ?, ?)
|
||||
''', (f'{SETTINGS_PREFIX}ai_model', recommended, datetime.now().isoformat()))
|
||||
now_iso = datetime.now().isoformat()
|
||||
for model_key in ('ai_model', f'ai_model_{provider_name}'):
|
||||
cursor.execute('''
|
||||
INSERT OR REPLACE INTO user_settings (setting_key, setting_value, updated_at)
|
||||
VALUES (?, ?, ?)
|
||||
''', (f'{SETTINGS_PREFIX}{model_key}', recommended, now_iso))
|
||||
conn.commit()
|
||||
conn.close()
|
||||
self._config['ai_model'] = recommended
|
||||
self._config[f'ai_model_{provider_name}'] = recommended
|
||||
|
||||
print(f"[NotificationManager] AI model migrated: {old_model} -> {recommended}")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user