Public Access
chore: Serveur 100% stable, ports corrigés, prêt pour le RAG
Build and Push Docker Image / build-and-push (push) Successful in 1m50s
Build and Push Docker Image / build-and-push (push) Successful in 1m50s
This commit is contained in:
1 parent
7125842853
commit
ffe820b00f
20 files changed
+7772
-198
No files matched your search
+148
-31
@@ -1,52 +1,169 @@
|
||||
import os
|
||||
import json
|
||||
import time
|
||||
from datetime import datetime
|
||||
import tiktoken
|
||||
from modules.logger import log_event
|
||||
|
||||
# Tarification indicative par token (À synchroniser avec OpenRouter)
|
||||
BALANCES_FILE = "aethas_balances.json"
|
||||
AUDIT_FILE = "aethas_financial_audit.json"
|
||||
|
||||
TARIFS_OPENROUTER = {
|
||||
"meta-llama/llama-3.1-8b-instruct:free": 0.0,
|
||||
"qwen/qwen-2.5-coder-32b-instruct:free": 0.0,
|
||||
"google/gemini-2.5-pro": 0.000005 # Exemple de modèle payant
|
||||
"mistralai/mistral-small-24b-instruct-2501:free": 0.0,
|
||||
"openai/gpt-4o-mini": 0.0000015,
|
||||
"anthropic/claude-3.5-sonnet": 0.000015,
|
||||
"mistralai/mistral-large-2411": 0.000006,
|
||||
"google/gemini-2.5-pro": 0.000005
|
||||
}
|
||||
|
||||
def compter_tokens(texte, modele="cl100k_base"):
|
||||
"""Compte le nombre de tokens en local pour éviter les requêtes API inutiles."""
|
||||
VIP_ADMIN_USERS = ["xavier", "céline", "celine", "anaëlle", "anaelle", "renan", "audric"]
|
||||
|
||||
def charger_soldes() -> dict:
|
||||
"""Charge les soldes réels depuis le stockage persistant."""
|
||||
if os.path.exists(BALANCES_FILE):
|
||||
try:
|
||||
with open(BALANCES_FILE, "r", encoding="utf-8") as f:
|
||||
return json.load(f)
|
||||
except Exception:
|
||||
pass
|
||||
# Initialisation de base : cagnotte commune de 35.00 $
|
||||
soldes = {"cagnotte_commune_vip": 35.00}
|
||||
sauvegarder_soldes(soldes)
|
||||
return soldes
|
||||
|
||||
def sauvegarder_soldes(soldes: dict):
|
||||
"""Sauvegarde les soldes sur disque."""
|
||||
with open(BALANCES_FILE, "w", encoding="utf-8") as f:
|
||||
json.dump(soldes, f, indent=4)
|
||||
|
||||
def enregistrer_transaction_audit(utilisateur: str, rang: str, modele: str, tokens: int, cout_brut: float, prix_facture: float, type_op: str = "DEBIT_REQUETE"):
|
||||
"""Journalise chaque opération financière de manière inaltérable pour les litiges."""
|
||||
entree = {
|
||||
"timestamp": time.time(),
|
||||
"date": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
||||
"utilisateur": utilisateur,
|
||||
"rang": rang,
|
||||
"type_operation": type_op,
|
||||
"modele": modele,
|
||||
"tokens": tokens,
|
||||
"cout_brut_api": cout_brut,
|
||||
"prix_facture_client": prix_facture
|
||||
}
|
||||
|
||||
audits = []
|
||||
if os.path.exists(AUDIT_FILE):
|
||||
try:
|
||||
with open(AUDIT_FILE, "r", encoding="utf-8") as f:
|
||||
audits = json.load(f)
|
||||
except Exception:
|
||||
audits = []
|
||||
|
||||
audits.append(entree)
|
||||
with open(AUDIT_FILE, "w", encoding="utf-8") as f:
|
||||
json.dump(audits, f, indent=4)
|
||||
|
||||
def compter_tokens(texte: str, modele: str = "cl100k_base") -> int:
|
||||
"""Compte le nombre de tokens d'un texte."""
|
||||
try:
|
||||
enc = tiktoken.get_encoding(modele)
|
||||
return len(enc.encode(texte))
|
||||
except Exception as e:
|
||||
log_event("ERROR", "BILLING", f"Erreur lors du comptage des tokens : {e}")
|
||||
log_event("ERROR", "BILLING", f"Erreur comptage tokens : {e}")
|
||||
return 0
|
||||
|
||||
def estimer_cout_facture(prompt, modele_id):
|
||||
def estimer_cout_facture(prompt: str, modele_id: str, rang_utilisateur: str = "standard", multi_ia: bool = False) -> tuple[float, float, int]:
|
||||
"""
|
||||
Calcule le coût de la requête.
|
||||
Applique la marge stricte de 15% pour générer le Prix Facture HT.
|
||||
Calcule le coût brut API et le prix facturé selon le rang :
|
||||
- VIP / Admin : marge 0 % (coût réel déduit de la cagnotte commune)
|
||||
- Famille : marge 7,5 % (coût / 0.925)
|
||||
- Standard : marge 15 % (coût / 0.85)
|
||||
"""
|
||||
tokens = compter_tokens(prompt)
|
||||
cout_brut_api = tokens * TARIFS_OPENROUTER.get(modele_id, 0.0)
|
||||
cout_unitaire = TARIFS_OPENROUTER.get(modele_id, 0.0)
|
||||
cout_brut = tokens * cout_unitaire
|
||||
|
||||
if cout_brut_api == 0.0:
|
||||
return 0.0
|
||||
|
||||
# Application stricte de votre formule comptable
|
||||
prix_facture_ht = cout_brut_api / 0.85
|
||||
|
||||
return round(prix_facture_ht, 6)
|
||||
if multi_ia and cout_brut > 0:
|
||||
cout_brut *= 2
|
||||
|
||||
def verifier_et_valider_budget(prompt, modele_id, utilisateur_vip=False):
|
||||
"""
|
||||
Fonction appelée avant chaque requête par le Chat Handler.
|
||||
Si le coût > 0 et que l'utilisateur n'a pas explicitement validé, on stoppe tout.
|
||||
"""
|
||||
cout_estime = estimer_cout_facture(prompt, modele_id)
|
||||
if cout_brut == 0.0:
|
||||
return 0.0, 0.0, tokens
|
||||
|
||||
rang = rang_utilisateur.lower()
|
||||
if rang in ["super admin", "admin", "vip"] or rang in VIP_ADMIN_USERS:
|
||||
prix_facture = cout_brut
|
||||
elif rang == "famille":
|
||||
prix_facture = cout_brut / 0.925
|
||||
else:
|
||||
prix_facture = cout_brut / 0.85
|
||||
|
||||
return round(cout_brut, 6), round(prix_facture, 6), tokens
|
||||
|
||||
def verifier_et_valider_budget(prompt: str, modele_id: str, utilisateur_nom: str = "xavier", rang_utilisateur: str = "super admin", multi_ia: bool = False):
|
||||
"""Vérifie le solde avant exécution et applique le débit approprié."""
|
||||
cout_brut, prix_facture, tokens = estimer_cout_facture(prompt, modele_id, rang_utilisateur, multi_ia)
|
||||
|
||||
# Les VIP ne paient pas de marge, on affiche juste le coût brut pour information
|
||||
if utilisateur_vip:
|
||||
log_event("INFO", "BILLING", f"Utilisateur VIP. Coût brut estimé de la requête : {cout_estime * 0.85} $")
|
||||
if prix_facture == 0.0:
|
||||
return True, 0.0
|
||||
|
||||
if cout_estime > 0.0:
|
||||
# Lève une exception spécifique que l'interface FastHTML devra attraper pour afficher le pop-up
|
||||
raise Exception(f"VALIDATION_PAIEMENT_REQUISE|{cout_estime}")
|
||||
|
||||
return True, cout_estime
|
||||
|
||||
soldes = charger_soldes()
|
||||
utilisateur_cle = utilisateur_nom.lower()
|
||||
|
||||
# Distinction cagnotte commune VIP/Admin vs compte individuel
|
||||
if utilisateur_cle in VIP_ADMIN_USERS or rang_utilisateur.lower() in ["super admin", "admin", "vip"]:
|
||||
solde_disponible = soldes.get("cagnotte_commune_vip", 0.0)
|
||||
cle_compte = "cagnotte_commune_vip"
|
||||
else:
|
||||
solde_disponible = soldes.get(utilisateur_cle, 0.0)
|
||||
cle_compte = utilisateur_cle
|
||||
|
||||
if solde_disponible < prix_facture:
|
||||
log_event("WARNING", "BILLING", f"Rejet : solde insuffisant pour {utilisateur_nom} ({solde_disponible} $ < {prix_facture} $)")
|
||||
raise Exception(f"SOLDE_INSUFFISANT|{prix_facture}|{solde_disponible}")
|
||||
|
||||
# Débit strict
|
||||
soldes[cle_compte] = round(solde_disponible - prix_facture, 6)
|
||||
sauvegarder_soldes(soldes)
|
||||
|
||||
enregistrer_transaction_audit(utilisateur_nom, rang_utilisateur, modele_id, tokens, cout_brut, prix_facture)
|
||||
log_event("INFO", "BILLING", f"Requête validée [{rang_utilisateur}] - Débit : {prix_facture} $ - Solde restant : {soldes[cle_compte]} $")
|
||||
return True, prix_facture
|
||||
|
||||
def obtenir_solde(utilisateur_nom: str) -> float:
|
||||
"""Retourne le solde actuel de l'utilisateur ou de la cagnotte commune."""
|
||||
soldes = charger_soldes()
|
||||
if utilisateur_nom.lower() in VIP_ADMIN_USERS:
|
||||
return soldes.get("cagnotte_commune_vip", 0.0)
|
||||
return soldes.get(utilisateur_nom.lower(), 0.0)
|
||||
|
||||
def generer_dossier_litige(utilisateur_nom: str, est_admin: bool = False) -> list:
|
||||
"""
|
||||
Génère les éléments d'audit pour un litige :
|
||||
- Pour l'utilisateur : date, modèle, tokens, prix facturé (marge masquée).
|
||||
- Pour l'Admin : audit complet avec coût brut API.
|
||||
"""
|
||||
if not os.path.exists(AUDIT_FILE):
|
||||
return []
|
||||
|
||||
try:
|
||||
with open(AUDIT_FILE, "r", encoding="utf-8") as f:
|
||||
tous_les_audits = json.load(f)
|
||||
except Exception:
|
||||
return []
|
||||
|
||||
dossier = []
|
||||
for item in tous_les_audits:
|
||||
if item.get("utilisateur", "").lower() == utilisateur_nom.lower():
|
||||
if est_admin:
|
||||
dossier.append(item)
|
||||
else:
|
||||
# Filtrage des marges pour le client
|
||||
dossier.append({
|
||||
"date": item.get("date"),
|
||||
"type_operation": item.get("type_operation"),
|
||||
"modele": item.get("modele"),
|
||||
"tokens": item.get("tokens"),
|
||||
"montant_facture_ttc": item.get("prix_facture_client")
|
||||
})
|
||||
return dossier
|
||||
Reference in new issue
Block a user