diff --git a/.github/workflows/test-features.yml b/.github/workflows/test-features.yml index 5a58cd48..a13f2aac 100644 --- a/.github/workflows/test-features.yml +++ b/.github/workflows/test-features.yml @@ -7,7 +7,9 @@ on: - 'AppImage/scripts/notification_manager.py' - 'AppImage/scripts/notification_templates.py' - 'AppImage/scripts/ai_context_enrichment.py' - - 'AppImage/scripts/ai_providers/base.py' + - 'AppImage/scripts/ai_providers/**' + - 'AppImage/config/verified_ai_models.json' + - 'AppImage/scripts/tests/test_groq_model_recommendations.py' - 'AppImage/scripts/tests/test_backup_ai_diagnostics.py' - 'AppImage/scripts/notification_channels.py' - 'AppImage/components/apprise-service-help.tsx' @@ -72,7 +74,9 @@ on: - 'AppImage/scripts/notification_manager.py' - 'AppImage/scripts/notification_templates.py' - 'AppImage/scripts/ai_context_enrichment.py' - - 'AppImage/scripts/ai_providers/base.py' + - 'AppImage/scripts/ai_providers/**' + - 'AppImage/config/verified_ai_models.json' + - 'AppImage/scripts/tests/test_groq_model_recommendations.py' - 'AppImage/scripts/tests/test_backup_ai_diagnostics.py' - 'AppImage/scripts/notification_channels.py' - 'AppImage/components/apprise-service-help.tsx' @@ -190,6 +194,19 @@ jobs: bash -n scripts/gpu_tpu/nvidia_installer.sh python3 -B -m unittest discover -s tests -p 'test_nvidia*.py' -v + ai-model-availability: + runs-on: ubuntu-latest + timeout-minutes: 3 + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + - uses: actions/setup-python@v5 + with: + python-version: '3.11' + - name: Test model ordering and curated availability without network or credentials + run: python -B -m unittest discover -s AppImage/scripts/tests -p test_groq_model_recommendations.py -v + installer-and-release: runs-on: ubuntu-latest timeout-minutes: 10 diff --git a/AppImage/config/verified_ai_models.json b/AppImage/config/verified_ai_models.json index b1f8c9b4..d5852db5 100644 --- a/AppImage/config/verified_ai_models.json +++ b/AppImage/config/verified_ai_models.json @@ -110,12 +110,10 @@ "openai/gpt-oss-120b", "meta-llama/llama-3.1-8b-instruct", "nvidia/nemotron-3-nano-omni-30b-a3b-reasoning:free", - "nvidia/nemotron-3-super-120b-a12b:free", - "google/gemma-4-26b-a4b-it:free", - "poolside/laguna-s-2.1:free" + "google/gemma-4-26b-a4b-it:free" ], "recommended": "meta-llama/llama-3.3-70b-instruct", - "_note": "Curated manually from the 249+ models OpenRouter serves — the auto-verifier passes ~200 today but only a subset are practical for notification translation. Includes the 11 previously-curated paid models plus 22 new mainstream additions (gpt-5.1, claude-opus-4.8, sonnet-5, grok-4.3/4.20, kimi-k2, deepseek-v3.2, nova, cohere-a, sonar, ...) and 6 `:free` variants re-added manually — the free tier is intentionally supported per user request even though today's rate limits blocked some of them from passing the test. Reviewed 2026-10-26: three `:free` variants no longer served by OpenRouter were dropped." + "_note": "Curated manually from the 249+ models OpenRouter serves — the auto-verifier passes ~200 today but only a subset are practical for notification translation. Includes the 11 previously-curated paid models plus 22 new mainstream additions (gpt-5.1, claude-opus-4.8, sonnet-5, grok-4.3/4.20, kimi-k2, deepseek-v3.2, nova, cohere-a, sonar, ...) and 6 `:free` variants re-added manually — the free tier is intentionally supported per user request even though today's rate limits blocked some of them from passing the test. Reviewed 2026-10-26: three `:free` variants no longer served by OpenRouter were dropped. Availability reviewed 2026-11-04 against the public models endpoint: expired poolside/laguna-s-2.1:free and unlisted nvidia/nemotron-3-super-120b-a12b:free removed. This availability check is not a new inference verification; no untested models were added." }, "ollama": { "_note": "Ollama models are local, we don't filter them. User manages their own models.", diff --git a/AppImage/scripts/ai_providers/groq_provider.py b/AppImage/scripts/ai_providers/groq_provider.py index 7c07e29b..dfdf179e 100644 --- a/AppImage/scripts/ai_providers/groq_provider.py +++ b/AppImage/scripts/ai_providers/groq_provider.py @@ -21,8 +21,13 @@ class GroqProvider(AIProvider): # Exclude non-chat models EXCLUDED_PATTERNS = ['whisper', 'tts', 'guard', 'tool-use'] - # Recommended models (in priority order - versatile/large models first) - RECOMMENDED_PREFIXES = ['llama-3.3', 'llama-3.1-70b', 'llama-3.1-8b', 'mixtral', 'gemma'] + # Prefer the verified default and current GPT-OSS models. Llama remains + # available for accounts with access; retired Mixtral/Gemma families have + # no recommendation priority. Sorting never adds IDs to the API response. + RECOMMENDED_PREFIXES = [ + 'openai/gpt-oss-20b', 'openai/gpt-oss-120b', 'qwen/qwen3.8-27b', + 'llama-3.3', 'llama-3.1-8b', + ] def list_models(self) -> List[str]: """List available Groq models for chat completions. diff --git a/AppImage/scripts/tests/test_groq_model_recommendations.py b/AppImage/scripts/tests/test_groq_model_recommendations.py new file mode 100644 index 00000000..119258a3 --- /dev/null +++ b/AppImage/scripts/tests/test_groq_model_recommendations.py @@ -0,0 +1,52 @@ +"""Provider model ordering and curated availability, without credentials/network.""" +import io +import json +from pathlib import Path +import sys +import unittest +from unittest.mock import patch + +sys.path.insert(0, str(Path(__file__).resolve().parents[1])) +from ai_providers.groq_provider import GroqProvider + + +class GroqModelRecommendationsTests(unittest.TestCase): + def fetch(self, ids): + response = io.BytesIO(json.dumps({'data': [{'id': model} for model in ids]}).encode()) + with patch('ai_providers.groq_provider.urllib.request.urlopen', return_value=response) as request: + models = GroqProvider(api_key='fixture-only').list_models() + self.assertEqual(request.call_args.kwargs['timeout'], 10) + return models + + def test_current_verified_models_precede_legacy_and_unknown_families(self): + listed = ['mixtral-fixture', 'allam-2-7b', 'llama-3.3-70b-versatile', + 'openai/gpt-oss-120b', 'qwen/qwen3.8-27b', 'openai/gpt-oss-20b'] + self.assertEqual(self.fetch(listed)[:3], + ['openai/gpt-oss-20b', 'openai/gpt-oss-120b', 'qwen/qwen3.8-27b']) + self.assertCountEqual(self.fetch(listed), listed) + + def test_recommendations_never_invent_models_or_remove_arabic_model(self): + self.assertEqual(self.fetch(['allam-2-7b']), ['allam-2-7b']) + self.assertEqual(self.fetch([]), []) + + def test_non_chat_models_still_excluded(self): + self.assertEqual(self.fetch(['whisper-large-v3', 'llama-prompt-guard-2-22m', + 'openai/gpt-oss-20b']), ['openai/gpt-oss-20b']) + + def test_missing_credentials_do_not_fetch_models(self): + with patch('ai_providers.groq_provider.urllib.request.urlopen') as request: + self.assertEqual(GroqProvider().list_models(), []) + request.assert_not_called() + + def test_curated_catalog_keeps_verified_default_and_removes_unavailable_free_ids(self): + catalog = json.loads((Path(__file__).resolve().parents[2] / 'config/verified_ai_models.json').read_text()) + self.assertEqual(GroqProvider.RECOMMENDED_PREFIXES[0], catalog['groq']['recommended']) + for provider in ('groq', 'openrouter'): + self.assertIn(catalog[provider]['recommended'], catalog[provider]['models']) + self.assertNotIn('poolside/laguna-s-2.1:free', catalog['openrouter']['models']) + self.assertNotIn('nvidia/nemotron-3-super-120b-a12b:free', catalog['openrouter']['models']) + self.assertIn('allam-2-7b', catalog['groq']['models']) + + +if __name__ == '__main__': + unittest.main()