# Dépannage : inférence et agents

> Inférence, outils MCP, fédération, chaîne d'agents, échange de jeton et A2A : le symptôme, sa cause et quoi faire.

Dépannage - Depuis la version 0.1.0 - Flexway v1.7.1 - https://flexway.io/docs/operations-troubleshooting-ai/

> Guide d'exploitation : configurer, déployer et observer la passerelle. Ce que fait la fonctionnalité côté produit : [guide produit](https://flexway.io/docs/product-overview/) ; l'appeler : [guide développeur](https://flexway.io/docs/developer-overview/).

## Inférence

| Symptôme | Cause | Action |
| --- | --- | --- |
| Inférence : `401` `tenant_required` malgré les en-têtes de l'ingress | Le pair n'est pas dans `server.trusted_proxies` : ses en-têtes ne sont pas crus | Déclarer la plage de l'ingress, et elle seule |
| Inférence : `429` `budget_exceeded` | Le budget du tenant (jour ou mois) est épuisé - une requête pré-réserve `max_tokens`, 512 sans lui | Relever `ai.budget` ou une surcharge du tenant ; le compteur est dans `ai.budget.store` |
| Inférence : `429` `rate_limited` pour tous les tenants derrière l'ingress | Une politique de débit trop serrée sur `/v1/*` | Une politique propre sur `routes: { "/v1/**": [...] }` ([exemple](https://flexway.io/docs/operations-examples-inference/#inf%C3%A9rence-derri%C3%A8re-un-maillage)) |
| Inférence : `Flexway-Budget: soft` sur toutes les réponses d'un tenant | Il a dépassé `soft.at` de son budget en euros du mois | Attendu ; relever `monthly_eur` du tenant (surcharge) ou attendre le mois suivant |
| Inférence : `Flexway-Degraded: backend-fallback` | Le backend choisi est en panne (connexion, `5xx`, délai, circuit ouvert) ; le suivant de `fallback` a servi | La métrique `flexway.inference.fallback` donne le backend quitté et la cause |
| Refus au chargement « price is required: the budget is counted in euros » | Un budget en euros existe et un backend n'a pas de prix | Déclarer `price` sur chaque backend, `0` pour un interne |
| Inférence : `403` `sensitive_no_internal_backend` | Requête sensible et aucun `ai.sensitive_backend` interne | Déclarer un backend interne ; jamais un externe pour ce flux |
| Inférence : `400` `capability_unavailable` | Le backend que la politique choisit ne sert pas ce que la requête demande - une image sans `vision`, des embeddings sans `embeddings` | Déclarer la capacité sur le backend (`capabilities`) ou une dérogation `match.capability` vers un backend qui la sert |
| Avertissement « is a relay metered locally » (`flexway validate`, démarrage) | Un `kind: relay` sans `metering: upstream` : la Flexway amont compte aussi le coût | Déclarer `metering: upstream` ; le plafond local reste appliqué |
| Inférence : `502` `backend_unavailable` | Backend injoignable, ou sa clé introuvable (`${VAR}` non posée, fichier absent) | Le journal (`inference backend failed`) donne la cause ; la clé est relue à chaque appel |

## Agents et MCP

| Symptôme | Cause | Action |
| --- | --- | --- |
| Agent : `401` malgré un jeton valide | Le jeton n'est pas émis **pour la surface** : son audience doit être l'URL de `agents.mcp.resources` | Demander le jeton avec l'indicateur de ressource (`resource=https://…/mcp`) |
| Un outil attendu manque dans `tools/list` | Le jeton de l'agent n'a pas la portée ou le rôle que l'opération exige - la liste est filtrée | Donner la portée à l'agent, pas élargir l'opération |
| Un outil répond `401` depuis l'amont | L'amont vérifie lui-même un JWT, et le jeton de l'agent ne lui est jamais relayé | Déclarer `upstreams.<nom>.token_exchange` et le `client` de l'émetteur ([Le jeton que reçoit l'amont](https://flexway.io/docs/operations-agents-mcp/#le-jeton-que-re%C3%A7oit-lamont), [Jeton échangé vers l'amont](https://flexway.io/docs/operations-examples-agents/#jeton-%C3%A9chang%C3%A9-vers-lamont)) |
| Un outil d'un serveur fédéré n'apparaît pas | Serveur injoignable ou pas encore découvert ; outil hors de la liste déclarée ; portée manquante ; nom déjà pris | `/config`, `mcp_federation` : état, outils servis, `not_served` et pourquoi ; le journal dit `MCP server not reachable` |
| Refus au chargement « is offered twice … rename one » | Un outil fédéré porte le nom d'un outil du contrat ou d'un autre serveur | `agents.mcp.servers.<nom>.rename` |
| Un outil fédéré répond `forbidden` | L'appelant n'a pas les `scopes` (ou `tool_scopes`) déclarés, ou n'est pas authentifié | Donner la portée à l'agent |
| `403` « The chain of agents … is longer than allowed » ou « older than allowed » | Le jeton porte plus d'agents (`act`) que `max_depth`, ou l'humain s'est authentifié il y a plus de `max_age` - l'opération peut resserrer | Le journal (`delegation chain refused`) donne la raison et les agents ; réauthentifier l'humain, ou raccourcir la chaîne |
| `401` « The delegation was revoked » | L'émetteur répond que le jeton n'est plus actif (introspection) | Attendu après une révocation ; obtenir un nouveau consentement |
| `503` « The delegation cannot be verified now » | L'introspection de l'émetteur est injoignable, ou l'émetteur n'en publie pas | Vérifier `introspection_endpoint` dans sa découverte et le `client` ; un jeton direct, lui, passe |
| `403` sur un amont qui échange, journal `widened` | Le fournisseur a accordé une portée que l'appelant n'avait pas | Régler la politique d'échange du fournisseur : un saut ne doit jamais élargir |
| Découverte en échec `401` | Le serveur exige un jeton même pour lister | `discovery.issuer`, avec un `client` sur cet émetteur |
| `403` « The identity provider refused to issue a token for this upstream » | Le fournisseur refuse l'échange : client non autorisé à échanger, audience inconnue, secret faux | Le journal (`token exchange refused`) donne le code du fournisseur ; régler le client côté fournisseur |
| `503` « could not issue a token », avec `Retry-After` | Fournisseur injoignable, ou secret client introuvable (`${VAR}` non posée, fichier absent) | Le journal (`token exchange unavailable`) dit lequel ; le secret est relu à chaque échange, sans rechargement |

## A2A

| Symptôme | Cause | Action |
| --- | --- | --- |
| A2A : `200` avec l'erreur JSON-RPC `-32009` (`VERSION_NOT_SUPPORTED`) | Pas d'en-tête `A2A-Version` - c'est la 0.3 pour la spécification | Envoyer `A2A-Version: 1.0` (ou `?A2A-Version=1.0`) ; seule la 1.0 est servie |
| A2A : `403` `PUSH_TARGET_NOT_ALLOWED` | Cible push hors `agents.a2a.push.allowed_hosts`, en `http`, écrite en adresse, avec `user@`, ou aucune liste déclarée | Déclarer le **nom** de l'hôte du webhook ; `flexway_a2a_push_refused_total` donne la raison |
| A2A : `403` `TENANT_MISMATCH` | Le `tenant` de la requête (corps, préfixe de chemin, paramètre) n'est pas celui de l'identité, ou l'identité n'en porte aucun | Envoyer le tenant de l'identité, ou rien : la passerelle transmet le sien |
| A2A : `429` `TOO_MANY_STREAMS` | L'appelant tient déjà `max_streams` flux ouverts vers cet agent sur cette réplique | Fermer un flux, ou relever `max_streams` de l'agent |
| Un flux A2A se termine à heure fixe | `max_task_duration` atteint (`flexway_a2a_calls_total{outcome="duration_exceeded"}`) | Le relever sur l'agent, ou suivre la tâche par `GetTask` |
| Démarrage refusé sur une carte A2A | Liaison gRPC, schéma sans mécanisme, amont sans `token_exchange` ou pas `protocol: a2a`, `signatures` déclarées, `signer` (prévu en `1.10.0`), `verify: none` absent, flux sans `max_streams` ou `max_task_duration` | Le message nomme la carte, l'agent et la règle |
