Changer le thème

Maîtriser le coût d'un agent IA : routage de modèles, budgets d'outils, cache et limites de retry

Easton editorial illustration: agent rollout and rollback rail
7
Couches de budget
user, tenant, workflow, task, tool, retry, cache.
4
Actions de contrôle
route, degrade, pause, abort.
3
Types de cache
prompt prefix cache, business result cache, tool response cache.
数据来源: Cette checklist d'ingénierie est issue de la recherche documentaire officielle de phase 1 ; les prix, remises et disponibilités de modèles doivent être revérifiés sur les pages officielles avant publication.

"OpenAI API Pricing"

À 3 h du matin, un Agent de génération de rapport renvoie une réponse vide. Le statut HTTP est 200, mais le body est vide. La logique de retry ne vérifie que le statut, donc elle continue. Chaque requête envoie 500 tokens d’entrée. Après 1 500 retries, 750 000 tokens sont partis. Le lendemain matin, la facture remet tout le monde d’aplomb.

La cause n’est pas « le modèle était trop cher ». Il manquait trois garde-fous : un circuit breaker, une vérification de budget et une classification des erreurs. Le coût d’un Agent part généralement en vrille de trois façons :

Retries sans limite. Les modes d’échec ne sont pas classés, donc une réponse vide devient une erreur récupérable. Il n’y a pas de circuit breaker, donc même 1 500 échecs consécutifs ne l’arrêtent pas. Chaque retry renvoie tout le contexte, ce qui multiplie le coût par 2 à 5.

Contexte qui gonfle. Une tâche longue tourne 6 heures et l’historique de conversation grimpe à 80K tokens. Sans checkpoint, un échec relance tout depuis le début, et chaque étape est repayée.

Surutilisation du modèle. Toutes les tâches utilisent un frontier model parce qu’il n’y a pas de stratégie de routage. Même une simple classification passe par le chemin le plus cher et gaspille 70% des tokens.

Le contrôle des coûts d’un Agent n’est pas une optimisation isolée. C’est une conception en couches : objets de budget, stratégie de routage, cache hits, circuit breakers de retry, journaux de coût et seuils d’alerte. Le bon réflexe consiste à transformer ces six objets d’ingénierie en tables de décision ou en contrôles exécutables.

Objets de budget : quoi tracer, où le garder, quand couper

Le contrôle des coûts commence par un objet de budget. Une somme totale ne suffit pas. Sans découpage, une anomalie de facture ne vous dit pas quel utilisateur, quelle tâche ou quel outil a brûlé le budget.

Sept couches de budget

Les objets de budget se découpent de la couche la plus large à la plus fine :

Couche de budgetObjet de budgetPlafond conseilléDéclencheur d’alerte
Layer 1userPlafond quotidien/mensuel par utilisateurAlerte si reste < 20%
Layer 2tenantPool de budget séparé par tenantAlerte si reste < 30%
Layer 3workflowBudget séparé par type de workflowAlerte si reste < 40%
Layer 4taskBudget séparé par type de tâcheAlerte si reste < 50%
Layer 5toolBudget par appel d’outilIgnorer l’outil au-dessus du plafond
Layer 6retryLimite de retries + circuit breakerDésactiver l’outil après N échecs consécutifs
Layer 7cacheSuivi du taux de cache hitAlerte si le taux est sous la valeur attendue

Les plafonds exacts dépendent du modèle métier et restent de la configuration mutable. La structure en couches est plus stable. Le champ de budget restant doit être écrit dans les journaux de coût pour piloter les alertes et les circuit breakers.

Champs à enregistrer pour chaque couche

Chaque couche de budget doit enregistrer ces champs :

Nom du champUsageTypePourquoi le tracer
modelIdentifier le modèlestringVoir si le routage de modèles est raisonnable
inputTokensNombre de tokens d’entréeintegerCalculer le coût d’entrée
outputTokensNombre de tokens de sortieintegerLe coût de sortie doit être suivi séparément
cachedTokensTokens touchés par le cacheintegerMesurer l’économie liée au cache
costEstimateEstimation du coût de cet appelfloatCumuler les coûts en temps réel
budgetRemainingBudget restantfloatBase de décision du circuit breaker

Le coeur de l’objet de budget est la comptabilité par dimension, pas le simple total_cost. En multi-tenant, le coût doit être réparti par tenantId. Avec plusieurs outils, toolName permet d’ouvrir la boîte noire.

Logique de circuit breaker

Quand le budget restant passe sous un seuil, déclenchez le circuit breaker :

def check_budget_before_retry(budget_remaining, retry_cost_estimate):
    if budget_remaining < retry_cost_estimate:
        return "skip_retry"  # Ignorer le retry, il dépasserait le budget
    if budget_remaining < threshold:  # threshold par exemple 20%
        return "wait_approval"  # Budget faible, attendre une validation
    return "continue"

Le circuit breaker vérifie le budget avant le retry, pas après. Estimez le coût avant chaque retry et arrêtez si la prochaine tentative dépasse le budget. C’est ainsi qu’on évite le cas « réponse vide réessayée 1 500 fois ».

Le futur article sur le context engineering expliquera quels contenus vont dans un prefix stable et lesquels restent des variables d’exécution.

Stratégie de routage de modèles : toutes les tâches n’ont pas besoin du modèle le plus cher

Un Agent de ticket triage ressemble souvent à ceci : 70% des tâches sont de la classification simple, 20% demandent un brouillon de réponse, et seuls 10% doivent monter vers un frontier model avant d’envoyer un email client. Une stratégie de routage peut économiser 40 à 85% de coût.

Routage au niveau modèle

Routez selon la complexité de la tâche :

Niveau de tâcheTâches typiquesNiveau de modèle recommandéPartProfil de coût
70% - niveau SClassification, extraction, filtrage, Q&R simplenano/flash (moins cher)70%Sortie courte, peu de tours, peu d’outils
20% - niveau MBrouillon, résumé, génération de code, raisonnement moyenmid-tier (prix moyen)20%Sortie moyenne, outils possibles
10% - niveau LRevue, architecture, raisonnement complexe, coordination multi-outilsfrontier (plus cher)10%Sortie longue, nombreux tours, outils fréquents

La stratégie de routage tient en trois étapes :

Étape 1 : classer la tâche. Pour chaque workflow, définissez les critères S/M/L : longueur de sortie, nombre d’appels d’outils, profondeur de raisonnement, niveau de risque.

Étape 2 : partir du niveau S par défaut. Ne montez vers M ou L que si la tâche montre des signes de complexité.

Étape 3 : router en cascade. Si le modèle S échoue, montez vers M. Si M échoue, montez vers L. Si L échoue, passez à l’intervention humaine. Vérifiez le budget restant avant chaque montée ; si le budget ne suit pas, sautez l’upgrade.

Routage au niveau service

Le même modèle peut aussi être séparé par latency priority. Les remises et fenêtres de complétion changent ; vérifiez la tarification officielle avant publication.

Niveau de serviceRemise de coûtDélai de complétionCas adapté
Realtime APIAucune remiseRéponse immédiateConversation Agent interactive, tâches prioritaires
Batch API50% cost discount (à revérifier)24-hour turnaround (à revérifier)evals batch, classification, embeddings, traitement de dépôt de contenu
Flex ProcessingCoût réduit (à revérifier)Réponse plus lente, indisponibilité occasionnelleTâches asynchrones peu prioritaires, model evaluations, data enrichment

Checklist de routage hors ligne :

  • La tâche a-t-elle besoin d’une réponse immédiate ? Oui -> Realtime API, avec routage de modèles.
  • Peut-elle attendre 24 heures ? Oui -> Batch API.
  • Est-elle peu prioritaire et tolérante aux échecs occasionnels ? Oui -> Flex Processing.
  • Est-ce un traitement batch comme eval, classification ou embedding ? Oui -> Batch API.

Niveaux de risque

Les tâches se classent aussi par niveau de risque :

Niveau de risqueOpération typiqueStratégie de routageBranche de budget
Risque faibleClassification, extraction, résumé interneModèle S + chemin automatiquePas de validation, plafond plus large
Risque moyenBrouillon de réponse client, suggestion de changement de codeModèle M + validation optionnelleDépassement de budget -> validation possible
Risque élevéEnvoyer un email client, facturer, changer l’architectureModèle L + validation obligatoireAttente, refus et timeout deviennent des branches de budget

L’article Human-in-the-Loop de la même série détaillera l’effet des attentes, refus et timeouts de validation sur les branches de budget.

Contraintes clés

Le routage doit respecter quelques contraintes :

  • Ne pas coder les prix en dur : les prix changent. Enregistrez model + pricingVersion au lieu de figer une formule de coût dans la logique métier.
  • Vérifier le budget restant : avant une montée de niveau, vérifiez budgetRemaining. Si le budget ne suffit pas, ignorez l’upgrade ou demandez une validation.
  • Classifier les erreurs : distinguez les limites récupérables du modèle des erreurs non récupérables de paramètre ou de permission.

Budgets d’appels d’outils : per-tool budget, timeout et limites de retry

Un appel d’outil a un double coût : schema et API. Chaque appel envoie le schema, les paramètres et le contexte de parsing de réponse. L’API externe peut aussi être limitée ou expirer. Ces coûts sont séparés de l’appel au modèle.

Contrôle du budget des outils

Chaque outil doit avoir ses propres contrôles :

ContrôleConfiguration conseilléeChamp de suiviAction déclenchée
Per-tool budgetPlafond par appeltool_cost_estimateIgnorer l’outil ou dégrader
Tool timeoutTimeout de l’API externetool_durationMarquer le timeout comme erreur récupérable
Retry limit per toolLimite de retries par outiltool_retry_countAbandonner l’outil, sans boucle de retries

Les outils qui appellent des API externes, comme recherche, base de données ou service tiers, doivent être comptés séparément. Sinon, l’appel d’outil devient une boîte noire de coût.

Classification des échecs d’outils

Les échecs se divisent en récupérables et non récupérables :

Type d’échecErreurs typiquesStratégieImpact coût
Échec récupérableTimeout réseau, 503 Service Unavailable, 429 Rate LimitRetry automatique avec exponential backoff et retry-afterChaque retry renvoie tout le contexte
Échec non récupérable403 Permission Denied, 400 Bad Request, outil inexistantNe pas retry ; injecter l’erreur pour que le modèle décidePas de retry, donc pas de gaspillage répété

La règle : seules les erreurs temporaires dues à des conditions externes se retry. Les erreurs de configuration interne ne se retry pas.

Circuit breaker

Après N échecs consécutifs, désactivez l’outil pour éviter la réponse vide réessayée 1 500 fois :

def circuit_breaker_tool(tool_name, consecutive_failures, threshold=5):
    if consecutive_failures >= threshold:
        return "disable_tool"  # Désactiver l'outil
    return "continue"

L’état du circuit breaker doit aller dans les logs pour expliquer pourquoi l’outil a été désactivé. Après déclenchement, attendez une intervention humaine ou un check de récupération automatique, au lieu de rappeler sans fin un outil instable.

Les bases du tool calling sont couvertes dans Tool Calling. Ici, on ajoute per-tool budget, timeout et limites de retry.

Conception du Prompt Caching : prefix stable, variables et seuil de 1024 tokens

Prompt Caching optimise le coût des tokens d’entrée d’un prompt prefix stable. Ce n’est pas un cache de résultat métier. Il route les requêtes ayant le même prompt prefix vers un serveur qui a récemment traité ce même prefix, ce qui peut réduire la latence et le coût d’entrée.

Prompt Caching n’est pas un cache de résultat

Prompt Caching garde un prompt prefix stable, pas un résultat métier. La différence compte :

  • Prompt Caching : cache un prompt prefix stable, comme system prompt ou tool schema. Un hit économise des tokens d’entrée, mais l’inférence est quand même exécutée.
  • Cache de résultat métier : garde une sortie complète, comme un résultat d’outil ou une requête de base de données. Un hit retourne directement sans appeler le modèle.

Les objectifs sont différents. Prompt Caching réduit le coût des tokens d’entrée ; le cache métier réduit le coût de l’appel complet. Les deux peuvent coexister : prefix stable côté Prompt Caching, résultats d’outils fréquents côté cache métier.

Exigences de structure

La clé est de séparer le prefix stable des variables d’exécution :

Type de contenuPositionProbabilité de cache hitExemples
Prefix stable (entre en cache)Début du promptÉlevéeSystem prompt, Tool schema, documents Policy, exemples Few-shot
Variables d’exécution (hors cache)Plus loin dans le promptFaibleUser input, File fragments, Runtime state (tour actuel, variables temporaires)

Étapes de conception :

  1. Placer System prompt, Tool schema et Policy au début : ces contenus restent stables sur plusieurs appels et touchent plus facilement le cache.
  2. Placer User input, File fragments et Runtime state plus loin : ces contenus changent à chaque appel et ne doivent pas entrer dans le prefix stable.
  3. Surveiller le Cache hit rate : enregistrer cachedTokens et le total de tokens d’entrée, puis calculer le taux. Au-dessus de 40%, c’est sain ; sous 20%, inspectez la structure du prompt.

Seuil et effet

Le seuil d’activation automatique et les effets de Prompt Caching sont des faits variables ; revérifiez la documentation officielle avant publication :

  • Seuil : activation automatique à partir de 1024 tokens (à revérifier).
  • Effet : un hit peut réduire coût et latence (revérifier les ratios exacts).
  • Vérifier les hits : champ usage.prompt_tokens_details.cached_tokens.

Les modèles pris en charge, seuils et remises de Prompt Caching peuvent changer. Vérifiez-les sur la page officielle de pricing avant publication. Le principe durable reste le même : contenu statique devant, contenu variable derrière.

Circuit breakers de retry : idempotence, checkpoints et budget restant

Les retries sont une des principales sources de coûts incontrôlés. Un Agent de rapport bloqué sur un outil instable peut renvoyer tout le contexte à chaque échec ; après 1 500 retries, le coût n’a plus grand-chose à voir avec le chemin normal.

Vérifier le budget avant le retry

Vérifiez le budget restant avant de retry, pas après :

def should_retry(error_type, budget_remaining, retry_cost_estimate):
    # Classification d'erreur
    if error_type in ["403", "400", "tool_not_exist"]:
        return False  # Erreur non récupérable, pas de retry

    # Vérification du budget
    if budget_remaining < retry_cost_estimate:
        return False  # Hors budget, pas de retry

    return True  # Retry possible

La vérification du budget doit précéder la logique de retry. C’est ce qui évite de brûler le budget quotidien sur une réponse vide retry 1 500 fois.

Idempotence

Un retry ne doit pas répéter un effet de bord, comme envoyer un email ou déclencher une facturation :

  • Utilisez un identifiant d’idempotence, par exemple requestId, pour les appels d’outils. Si l’API externe reçoit le même ID, elle doit renvoyer le résultat mis en cache plutôt que retraiter l’opération.
  • Écrivez cet ID dans les journaux de coût pour diagnostiquer les appels répétés.

Le principe d’idempotence est simple : la même opération ne doit pas consommer deux fois. Sinon, les retries amplifient à la fois coût et effets de bord.

Sauvegarde d’état (Checkpoint)

Les tâches longues doivent reprendre sans relancer tout le workflow :

  • Sauvegardez un checkpoint aux noeuds importants : étapes terminées, état courant, résumé de contexte.
  • Après un échec, reprenez depuis le checkpoint au lieu de repartir du début.
  • Persistez le checkpoint. Le garder uniquement en mémoire ne suffit pas.

La conception des checkpoints et du thread state est détaillée dans Architecture d’un Agent LangGraph.

Stratégie de retry

Chaque type d’erreur demande une stratégie différente :

Type d’erreurErreur typiqueStratégie de retryImpact coût
Timeout réseauPas de réponse après 10 secondesExponential backoff + retry-after, maximum 3 retriesChaque retry renvoie tout le contexte
503/429Service Unavailable, Rate LimitAttendre la fenêtre de rate limit + retry-after, maximum 3 retriesL’attente ne consomme pas de tokens, le retry oui
403/400Permission Denied, Bad RequestNe pas retry ; injecter l’erreur pour que le modèle décidePas de retry, donc pas de coût invalide

La règle : seules les erreurs récupérables se retry. Ne renvoyez pas indéfiniment une requête invalide au modèle.

Circuit breaker

Après N échecs consécutifs, arrêtez les retries et attendez une intervention :

def circuit_breaker(consecutive_failures, threshold=5):
    if consecutive_failures >= threshold:
        return "stop_retry"  # Arrêter les retries
    return "continue"

La décision du circuit breaker doit être journalisée pour expliquer pourquoi les retries se sont arrêtés. Ensuite, attendez une intervention humaine ou le retour du budget, au lieu de rappeler un outil ou un modèle instable.

Journaux de coût et alertes : quels champs et quels seuils

L’observabilité des coûts est la condition du contrôle des coûts. Si les champs de log sont incomplets, vous ne trouverez pas le problème.

OpenTelemetry trace span attributes

Concevez les journaux de coût sur trois niveaux de spans :

Agent run span (niveau supérieur) :

Nom du champUsageTypePourquoi le tracer
runIdIdentifier une exécution précisestringDistinguer plusieurs runs du même workflow
tenantIdIdentifier le tenantstringRépartir le coût en multi-tenant
userIdIdentifier l’utilisateurstringSuivre la tendance de coût par utilisateur
workflowNameIdentifier le workflowstringSuivre les coûts par type de workflow
totalCostCoût total estiméfloatCumuler les coûts en temps réel
budgetRemainingBudget restantfloatBase de décision du circuit breaker
totalRetriesNombre total de retriesintegerMesurer l’amplification par retry

Model call span (span enfant) :

Nom du champUsageTypePourquoi le tracer
modelIdentifier le modèlestringVérifier la pertinence du routage
pricingVersionVersion de tarificationstringÉviter de coder la formule de coût en dur
inputTokensNombre de tokens d’entréeintegerCalculer le coût d’entrée
outputTokensNombre de tokens de sortieintegerSuivre le coût de sortie séparément
cachedTokensTokens touchés par le cacheintegerCalculer l’économie de cache
costEstimateEstimation du coût de cet appelfloatCumuler les coûts en temps réel
latencyMsLatence de l’appelintegerÉvaluer si Batch/Flex convient

Tool call span (span enfant) :

Nom du champUsageTypePourquoi le tracer
toolNameIdentifier l’outilstringIdentifier l’overhead des appels d’outils
toolBudgetPlafond de budget outilfloatBase de décision du circuit breaker
toolTimeoutTimeout de l’outilintegerClasser les échecs de timeout
retryCountNombre de retriesintegerMesurer l’amplification par retry
errorTypeType d’erreurstringSéparer récupérable et non récupérable

Ne stockez pas seulement total_cost. Découpez par dimension. Sans ces champs, une anomalie de facture dit seulement « trop cher », pas quel utilisateur, outil ou retry l’a causée.

La conception complète des logs, alertes et reprises d’échec est couverte dans Surveillance et reprise d’un Agent. Cet article ajoute les champs de coût et les objets de budget.

Seuils d’alerte

Définissez les seuils par dimension :

Dimension d’alerteSeuil d’alerteCanalAction déclenchée
Consommation globale du budget70%, 90%, 100%Slack/email70% notifier, 90% dégrader, 100% couper
Consommation d’un user/tenantPlus de 3x la moyenneSlack/emailVérifier les appels anormaux
Taux d’échec d’un modèle> 5%DashboardVérifier routage ou état du service
Retries d’un outil> seuilDashboardVérifier la stabilité de l’outil
Cache hit rate< valeur attendueDashboardVérifier la structure du prompt

Les seuils doivent être dans la logique de coût pour déclencher automatiquement alertes et circuit breakers.

Stratégie de dégradation

Après une alerte, la dégradation peut suivre ces chemins :

Chemin de dégradationMéthodeCas adaptéImpact coût
Dégradation de modèleGrand modèle -> petit modèleTaux d’échec élevé sur un modèleCoût plus bas, qualité possiblement plus basse
Dégradation de cheminRealtime API -> Batch API -> Flex ProcessingBudget global consommé trop vitePlus de latence, coût réduit
Dégradation de fonctionnalitéDésactiver les outils non essentielsTrop de retries sur un outilRéduit l’overhead des outils
Dégradation utilisateurRate limit, file d’attente, message « réessayez plus tard »Consommation anormale d’un utilisateurÉvite qu’un utilisateur brûle le budget

La dégradation doit vivre dans la logique de budget. Quand une alerte se déclenche, le système doit dégrader automatiquement, pas attendre une intervention manuelle.

Étapes suivantes

Le contrôle des coûts d’un Agent dépend aussi de la surveillance, des appels d’outils et du context engineering :

  • Publié : Surveillance et reprise d’un Agent : champs de log, alertes et reprise d’échec. Cet article ajoute les champs de coût et objets de budget.
  • Publié : Architecture d’un Agent LangGraph : checkpoints, thread state et reprise pour tâches longues.
  • Publié : Tool Calling : bases des appels d’outils. Cet article ajoute per-tool budget, timeout et limites de retry.
  • Même série : context engineering : prefix stable, cache hits et séparation entre contexte stable et variables d’exécution.
  • Même série : Human-in-the-Loop : attente, refus, timeout de validation, et leur impact sur coûts et retries.

Commencez par une barrière au niveau session : définissez un per-session cost limit et terminez automatiquement la session quand elle dépasse le budget. C’est la première protection la plus rapide contre une tâche qui brûle le budget de la journée. Ensuite, étendez vers les couches de budget, le routage de modèles, les budgets d’outils, Prompt Caching, les circuit breakers de retry et les journaux de coût.

Concevoir un budget de coût et un circuit breaker pour Agent

Utilisez objets de budget, routage de modèles, routage de service, cache, budgets d'outils et circuit breakers de retry pour déplacer le contrôle des coûts avant l'exécution de chaque run.

⏱️ Estimated time: 45 min

  1. 1

    Step 1: Lister tous les chemins de coût

    Listez les appels de modèles, appels d'outils, lectures de fichiers, API externes, traitements batch, caches et chemins de retry de l'Agent.
  2. 2

    Step 2: Définir l'objet de budget

    Tracez le budget par tenant, user, run, workflow, model, tool, retry, cache et time window.
  3. 3

    Step 3: Définir la stratégie de routage

    Définissez le routage de modèle et le routage de service selon les types de tâches : online, batch, flex et queue.
  4. 4

    Step 4: Concevoir les cache hits

    Placez le contexte stable dans le prompt prefix, les contenus variables plus loin, et distinguez prompt caching, cache de résultat métier et cache de réponse d'outil.
  5. 5

    Step 5: Limiter les outils et retries

    Donnez à chaque outil un timeout, max retries, idempotency key, per-tool budget et fallback.
  6. 6

    Step 6: Journaliser les spans de coût

    Sur les run/span, tracez token, cached token, tool, retry, latency, estimated cost, budget remaining et traceId.
  7. 7

    Step 7: Configurer la dégradation et le circuit breaker

    Définissez les seuils de dégradation, pause, circuit breaker et alertes, puis testez-les avec de vrais cas d'échec.

FAQ

Faut-il suivre le coût d'un Agent par utilisateur, session, tâche ou outil ?
Suivez-le sur plusieurs couches : user, tenant, workflow, task, tool, retry et cache. Chaque couche doit avoir son budget et son circuit breaker. Avec seulement total_cost, vous ne saurez pas quel utilisateur, outil ou chemin de retry a provoqué l'anomalie.
Le routage de modèles revient-il simplement à utiliser un petit modèle pour les tâches simples ?
Non. Il faut aussi tenir compte du routage de service, comme Batch/Flex/temps réel, et du niveau de risque. Le même modèle peut être séparé par latency priority. Les travaux hors ligne vont vers Batch API, les tâches peu prioritaires vers Flex Processing.
Quelle différence entre Prompt Caching et un cache métier classique ?
Prompt Caching garde un prompt prefix stable, par exemple system prompt, tool schema et policy. Ce n'est pas un résultat métier. Un cache métier stocke des sorties complètes ou des réponses d'outils. Les deux répondent à des besoins différents et peuvent coexister.
Combien de fois faut-il réessayer un appel d'outil qui échoue ?
Ne décidez pas avec un nombre fixe seulement. Regardez la classe d'erreur, le budget restant et l'état du circuit breaker. Réessayez peu de fois les erreurs récupérables ; ne réessayez pas 403, 400 ou un outil inexistant.
Que faire quand une longue tâche Agent arrive presque au bout de son budget ?
Le plus sûr est de mettre en pause, sauvegarder un checkpoint, puis attendre le retour du budget ou une validation humaine. Un échec brutal perd la progression ; une dégradation aveugle peut dégrader la qualité.
Quels champs un journal de coût doit-il enregistrer ?
Au minimum : runId, tenantId, workflow, model, input/output/cached tokens, toolName, retryCount, latency, costEstimate, budgetRemaining, decision et traceId.

16 min de lecture · Publié le: 17 sept. 2026

Commentaires

Connectez-vous avec GitHub pour laisser un commentaire

Easton BlogEaston Blog