From 923cf17fa093583be0983a041a1a3ba47bbd1185 Mon Sep 17 00:00:00 2001 From: laurent Date: Sun, 22 Feb 2026 16:43:40 +0100 Subject: [PATCH] fix: use mammouth public api and improve model mapping --- update_models.py | 102 +++++++++++++++++++++++------------------------ 1 file changed, 50 insertions(+), 52 deletions(-) diff --git a/update_models.py b/update_models.py index 4e106f0..d105db5 100644 --- a/update_models.py +++ b/update_models.py @@ -7,38 +7,33 @@ from dotenv import load_dotenv # Charger .env.global depuis le répertoire parent load_dotenv("../.env.global") -MAMMOUTH_APIKEY = os.getenv("MAMMOUTH_APIKEY") AIANALASYS_APIKEY = os.getenv("AIANALASYS_APIKEY") def get_mammouth_models(): - # Mammouth utilise l'API OpenRouter (revendeur) - url = "https://openrouter.ai/api/v1/models" - headers = {"Authorization": f"Bearer {MAMMOUTH_APIKEY}"} + # Utilisation de l'endpoint public LiteLLM de Mammouth + url = "https://mammouth.ai/public/models" try: - response = requests.get(url, headers=headers) + response = requests.get(url) response.raise_for_status() - return response.json()['data'] + # Le format retourné est {'data': [ {id, model_info: {input_cost_per_token, ...}} ]} + return response.json().get('data', []) except Exception as e: - print(f"Error fetching Mammouth models: {e}") + print(f"Error fetching Mammouth public models: {e}") return [] def get_aa_data(): - # URL correcte d'après la doc (version v2) url = "https://artificialanalysis.ai/api/v2/data/llms/models" headers = {"x-api-key": AIANALASYS_APIKEY} try: response = requests.get(url, headers=headers) response.raise_for_status() - # Le diagnostic a montré que les données sont dans 'data' return response.json().get('data', []) except Exception as e: print(f"Error fetching Artificial Analysis data: {e}") return [] def generate_markdown(models_data): - # Trier par catégorie (genre) categories = {} - for m in models_data: cat = m.get('category', 'General') if cat not in categories: @@ -46,10 +41,9 @@ def generate_markdown(models_data): categories[cat].append(m) md = "# Table des Modèles Mammouth.ai\n\n" - md += "*Mise à jour automatique via Artificial Analysis & Mammouth API*\n\n" + md += "*Mise à jour automatique via Artificial Analysis & Mammouth Public API*\n\n" md += "Dernière mise à jour : " + time.strftime("%Y-%m-%d %H:%M:%S") + "\n\n" - # Liste des catégories dans un ordre spécifique order = ['Coding', 'Agents', 'General'] sorted_cats = sorted(categories.keys(), key=lambda x: order.index(x) if x in order else 99) @@ -58,78 +52,80 @@ def generate_markdown(models_data): md += f"## {cat}\n\n" md += "| Modèle | Prix (In / Out / 1M) | Performance (AA Index) | Vitesse (TPS) |\n" md += "| :--- | :--- | :--- | :--- |\n" - # Trier par performance (AA index) - models.sort(key=lambda x: x.get('score') or 0, reverse=True) + # On trie d'abord par score décroissant, puis par prix croissant + models.sort(key=lambda x: (x.get('score') or 0, -(x.get('price_in') or 0)), reverse=True) for m in models: - p_in = f"${m['price_in']:.2f}" if m['price_in'] is not None else "N/A" - p_out = f"${m['price_out']:.2f}" if m['price_out'] is not None else "N/A" + p_in = f"${m['price_in']:.2f}" + p_out = f"${m['price_out']:.2f}" score = f"**{m['score']:.1f}**" if m['score'] else "N/A" speed = f"{m['speed']:.1f}" if m['speed'] else "N/A" md += f"| {m['name']} | {p_in} / {p_out} | {score} | {speed} |\n" md += "\n" - return md def main(): - print("Fetching Mammouth models...") + print("Fetching Mammouth public models...") mammouth_models = get_mammouth_models() - + if not mammouth_models: + print("No models found from Mammouth.") + return + print("Fetching Artificial Analysis data...") - aa_data = get_aa_data() + aa_raw = get_aa_data() - # Créer un dictionnaire de mapping pour AA (clé: nom du modèle en minuscule) + # Construction du mapping AA (index par nom et par ID technique) aa_map = {} - for aa_m in aa_data: + for aa_m in aa_raw: name = aa_m.get('model_name', '').lower() - aa_map[name] = aa_m + model_id = aa_m.get('model_id', '').lower() + if name: aa_map[name] = aa_m + if model_id: aa_map[model_id] = aa_m enriched_models = [] - for m in mammouth_models: - m_id = m['id'] - m_name = m['name'].lower() - short_name = m_id.split('/')[-1].lower() + m_id = m.get('id', '') + info = m.get('model_info', {}) - # Mapping logique plus complet - aa_info = aa_map.get(m_name) or aa_map.get(short_name) + # On ignore les modèles sans ID + if not m_id: continue + + # Normalisation du nom pour le mapping + m_id_low = m_id.lower() + short_name = m_id_low.split('/')[-1] + + # Recherche de correspondance dans AA (Précis puis Approché) + aa_info = aa_map.get(m_id_low) or aa_map.get(short_name) - # Si pas de match exact, on cherche par sous-chaîne ou flou if not aa_info: + # Recherche par sous-chaîne pour les modèles comme "mistral-large-2407" for key in aa_map: - if key in m_name or m_name in key or key in short_name or short_name in key: + if key in m_id_low or m_id_low in key: aa_info = aa_map[key] break - # Extraction des prix Mammouth (prix pour 1 token chez OpenRouter) - pricing = m.get('pricing', {}) + # Extraction des prix (LiteLLM: prix par 1 token) try: - price_in = float(pricing.get('prompt', 0)) * 1000000 - price_out = float(pricing.get('completion', 0)) * 1000000 + price_in = float(info.get('input_cost_per_token', 0)) * 1000000 + price_out = float(info.get('output_cost_per_token', 0)) * 1000000 except (ValueError, TypeError): - price_in = 0 - price_out = 0 + price_in, price_out = 0, 0 score = None speed = None - # On essaie d'extraire la catégorie de AA, sinon on devine - category = "General" - if aa_info: evals = aa_info.get('evaluations', {}) - # On cherche l'intelligence index score = evals.get('artificial_analysis_intelligence_index') speed = aa_info.get('median_output_tokens_per_second') - # Détermination de la catégorie (Genre) - if any(x in m_name or x in short_name for x in ['coding', 'code', 'starcoder', 'stable-code', 'deepseek-coder']): + # Catégorisation simplifiée + category = "General" + if any(x in m_id_low for x in ['coding', 'code', 'starcoder', 'coder']): category = "Coding" - elif any(x in m_name or x in short_name for x in ['agent', 'hermes', 'tool']): + elif any(x in m_id_low for x in ['agent', 'hermes', 'tool', 'function']): category = "Agents" - else: - category = "General" enriched_models.append({ - 'name': m['name'], + 'name': m_id, 'price_in': price_in, 'price_out': price_out, 'score': score, @@ -137,12 +133,14 @@ def main(): 'category': category }) - # On ne garde que les modèles qui ont un score de performance OU un prix raisonnable - # (Certains modèles sont gratuits ou ont des prix nuls) - final_list = [m for m in enriched_models if m['price_in'] > 0 or m['score'] is not None] + # Filtrer les modèles : prix > 0 (ceux qui sont configurés) + final_list = [m for m in enriched_models if m['price_in'] > 0 or m['price_out'] > 0] + if not final_list: + print("No valid models found after filtering.") + return + markdown = generate_markdown(final_list) - with open("README.md", "w", encoding="utf-8") as f: f.write(markdown)