> ## Content Index
> Fetch the complete content index at: https://www.sfeir.dev/llms.txt
> Use this file to discover other available public pages before exploring further.

# L'autopsie d'un agent IA en production
- URL: https://www.sfeir.dev/ia/lautopsie-dun-agent-ia-en-production/
- Published: 2026-10-30T11:00:00.000Z
- Updated: 2026-10-30T10:59:59.000Z
- Description: La créature est en production. Notre mission est de reconstituer les événements observables, de limiter les dégâts et de corriger la cause. Formuler des hypothèses, collecter des preuves, vérifier la chronologie et ajouter des protections testables !
- Author: Thibaut Rety
- Tags: IA, agents, halloween, Observability

> « Force physique grandement améliorée. Mais la réanimation qui en résulte… est un dysfonctionnement… Demain, ce journal et ses secrets doivent être détruits… pour toujours. »  
> Mary Shelley - **Frankenstein ou le Prométhée moderne**

## Sonnez l'alerte !

> 02 h 13\. Une alerte se déclenche : l'assistant de support a tenté d'affecter un ticket à une équipe inattendue.  
> Le service répond toujours. Le tableau de bord est vert. Pourtant, quelque chose ne tourne pas rond.

Dans les deux premiers épisodes, nous avons construit un [agent ](https://www.sfeir.dev/tag/agents/)documentaire puis étudié une injection de prompt. Cette fois, la créature est bien vivante. Sous quels angles [observer](https://www.sfeir.dev/tag/observability/) notre créature pour la comprendre ? Nous allons devoir reconstituer les événements observables, limiter les dégâts et corriger la cause.

Le scénario est fictif, mais la méthode est celle d'une investigation scientifique : formuler des hypothèses, collecter des preuves, vérifier la chronologie et ajouter des protections testables.

## Mais que fait la créature ?

Notre chère créature s'est comportée de manière anormale : elle a proposé une opération de modification alors que l'utilisateur a demandé un simple résumé.

### Hypothèses de travail

- [**Injection de prompt**](https://www.sfeir.dev/tag/prompt-injection/) : le document consulté contenait une instruction hostile.
- **Régression d'**[**orchestration**](https://www.sfeir.dev/tag/orchestration/) : une branche de code appelle un outil d'écriture alors qu'elle ne le devrait pas.
- **Boucle incontrôlée** : l'agent répète des appels ou poursuit son exécution au-delà de ce qui était attendu.

### Que faut-il tracer ?

Une exécution d'agent traverse plusieurs composants. Il faut pouvoir identifier les traces et les relier aux composants :

| Étape                     | Informations utiles                                                                  |
| ------------------------- | ------------------------------------------------------------------------------------ |
| Requête entrante          | Identifiant de trace, route, statut, durée, identifiant pseudonymisé de l'appelant   |
| Appel au modèle           | Fournisseur/modèle, durée, statut, nombre de tokens si disponible, version du prompt |
| Récupération documentaire | Identifiants de documents, nombre de résultats, durée, source autorisée              |
| Proposition d'outil       | Nom de l'outil, arguments validés ou rejetés, étape de la boucle                     |
| Contrôle d'autorisation   | Décision, règle appliquée, motif du refus                                            |
| Exécution d'outil         | Résultat, statut, durée, identifiant de ressource                                    |
| Réponse finale            | Statut, durée totale, résultat de la politique de sécurité                           |

⚠️ **Attention** : observabilité ne signifie pas enregistrer toutes les données. Les prompts et documents peuvent contenir des informations personnelles, des secrets ou des données confidentielles. Il faut définir une politique de rétention, masquer les valeurs sensibles et ne capturer le contenu nécessaire que lorsque c'est justifié et autorisé.

### Place à la reconstitution

Nos journaux structurés contiennent ces événements : 

```json
{"ts":"02:13:04.112","trace_id":"tr-7f21","event":"agent.started","request_id":"req-882"}
{"ts":"02:13:04.380","trace_id":"tr-7f21","event":"retrieval.completed","document_ids":["DOC-19"],"count":1}
{"ts":"02:13:05.021","trace_id":"tr-7f21","event":"llm.completed","prompt_version":"support-v12","status":"ok"}
{"ts":"02:13:05.044","trace_id":"tr-7f21","event":"tool.proposed","tool":"update_ticket"}
{"ts":"02:13:05.046","trace_id":"tr-7f21","event":"policy.denied","tool":"update_ticket","reason":"read_only_context"}
{"ts":"02:13:05.050","trace_id":"tr-7f21","event":"agent.completed","status":"blocked"}

```

Nous observons les éléments suivants :

1. La requête a récupéré un seul document.
2. Le modèle a proposé un outil d'écriture.
3. La couche de politique a refusé l'appel.
4. L'application a terminé l'exécution sans effectuer la modification.

Afin de vérifier si le document est responsable ou non, il faut inspecter les métadonnées de la source et, si les règles de confidentialité le permettent, le contenu conservé de façon contrôlée. Nous pouvons aussi comparer avec une exécution de référence.

✅ Le fait que l'appel soit bloqué est une bonne nouvelle. Le comportement du modèle reste suspect, mais la barrière de sécurité a rempli son rôle.

## Comment observer la créature ?

### Instrumenter le code

Voici un exemple minimaliste :

```python
import logging
import time

logger = logging.getLogger("assistant.agent")

def execute_tool_with_policy(
    *,
    trace_id: str,
    tool_name: str,
    arguments: dict,
    allowed_tools: set[str],
    executor,
):
    started = time.monotonic()

    if tool_name not in allowed_tools:
        logger.warning(
            "agent.tool_denied",
            extra={
                "trace_id": trace_id,
                "tool_name": tool_name,
                "reason": "tool_not_allowed",
            },
        )
        raise PermissionError(f"Outil non autorisé : {tool_name}")

    try:
        result = executor(**arguments)
        logger.info(
            "agent.tool_completed",
            extra={
                "trace_id": trace_id,
                "tool_name": tool_name,
                "duration_ms": round((time.monotonic() - started) * 1000),
                "status": "ok",
            },
        )
        return result
    except Exception:
        logger.exception(
            "agent.tool_failed",
            extra={"trace_id": trace_id, "tool_name": tool_name},
        )
        raise

```

Ce code illustre le principe, pas un système d'autorisation complet. La liste allowed\_tools doit être définie côté serveur à partir du contexte et des droits effectifs, et non à partir d'une valeur que le modèle peut choisir. Chaque outil doit également valider ses propres arguments et ses autorisations sur la ressource cible.

Pour une architecture distribuée, [OpenTelemetry](https://www.sfeir.dev/prometheus-vs-opentelemetry-comparaison-des-metriques-et-des-usages-2/) permet de relier la requête HTTP, l'orchestration, les appels au modèle et les outils au sein d'une trace distribuée. Les noms d'événements et attributs ci-dessus sont illustratifs : ils ne constituent pas une convention OpenTelemetry universelle.

## Identification des causes possibles

### Hypothèse 1 : une prompt injection indirecte

**Indices possibles :**

- un document a été consulté juste avant la proposition inattendue ;
- le comportement apparaît uniquement avec ce document ou un contenu similaire ;
- le prompt ou les données de récupération ont changé ;
- le modèle propose une action sans rapport avec la demande initiale.

**Vérification :** rejouer la même requête dans un environnement de test, avec le document original puis une version assainie, en gardant les versions du modèle et du prompt aussi constantes que possible. Les résultats d'un LLM peuvent varier ; une seule exécution ne suffit donc pas toujours.

### Hypothèse 2 : une régression d'orchestration

**Indices possibles :**

- l'appel d'outil est possible même dans un contexte censé être en lecture seule ;
- un déploiement récent a modifié le registre d'outils ;
- la décision d'autorisation dépend d'un champ fourni par le modèle.

**Vérification :** examiner la configuration effective et écrire un test qui vérifie qu'un outil d'écriture est refusé dans un contexte de lecture seule, indépendamment de la sortie du modèle.

### Hypothèse 3 : une boucle incontrôlée

**Indices possibles :**

- nombre inhabituel d'appels au modèle ou aux outils ;
- répétition des mêmes arguments ;
- durée, coût ou nombre de tokens en forte hausse ;
- absence de condition d'arrêt robuste.

**Vérification :** ajouter des limites explicites : nombre maximal d'itérations, délai total, budget de [tokens](https://www.sfeir.dev/are-you-token-to-me/) ou de coût, et arrêt sur répétition anormale. Les limites doivent être appliquées par l'orchestrateur.

## Il faut contenir la créature !

La créature fait des choses dangereuses ! Avant d'investiguer, il faut réduire le risque. L'ordre des actions est très important :

1. **Bloquer la capacité concernée.** Désactiver temporairement l'outil d'écriture ou revenir à un profil strictement en lecture seule.
2. **Révoquer ou réduire les permissions.** Ne pas compter uniquement sur un prompt modifié.
3. **Vérifier les effets réels.** Examiner les systèmes cibles pour savoir si des modifications ont été appliquées.
4. **Préserver les preuves utiles.** Conserver les identifiants de trace, les versions de configuration et les événements pertinents, selon les règles de sécurité et de confidentialité.
5. **Reproduire en environnement isolé.** Éviter de rejouer un scénario hostile contre des systèmes réels.
6. **Corriger puis** [**tester**](https://www.sfeir.dev/product/pourquoi-tester-son-code/)**.** Ajouter un test et vérifier que le comportement interdit est bloqué par l'application.

## Autopsie d'une créature devenue dangereuse

La créature est désormais hors d'état de nuire. 

Notre enquête révèle que le document a probablement influencé le modèle, qui a proposé une action hors de la mission initiale. La couche de politique a refusé cette action, et aucune modification n'a été effectuée dans le scénario présenté.

Le défaut principal n'était donc pas simplement « une IA devenue folle ». C'était un système qui devait anticiper qu'un modèle peut produire une proposition inadaptée et qui avait besoin de contrôles indépendants, de permissions minimales et de traces suffisamment riches pour expliquer ce qui s'était passé.

La créature n'est pas devenue malveillante au sens humain. Elle a suivi une trajectoire de calcul influencée par son contexte, dans un système dont les limites devaient être conçues et vérifiées par les ingénieurs.

### Quelques vérifications avant de la réveiller de nouveau

- Les outils sont autorisés côté application, selon le contexte et l'identité.
- Les outils de lecture et d'écriture sont séparés lorsque c'est possible.
- Les actions sensibles nécessitent une validation appropriée.
- Les arguments et les ressources cibles sont validés côté serveur.
- Les traces permettent de relier requête, modèle, récupération et outils.
- Les données sensibles sont masquées et leur rétention est encadrée.
- Les limites d'itérations, de durée et de coût sont actives.
- Un test confirme que l'action interdite est bloquée même si le modèle la propose.
- La procédure de confinement et de retour arrière est documentée.

## Morale de toute cette histoire

Construire un agent IA ne consiste pas seulement à choisir un modèle et à écrire un prompt. Il faut concevoir un système autour de lui : des outils aux permissions limitées, une validation indépendante, des limites d'exécution, des tests et une observabilité adaptée.

La leçon de Frankenstein reste étonnamment moderne : le danger ne vient pas uniquement de ce que l'on crée, mais aussi des pouvoirs qu'on lui donne et de l'absence de mécanismes pour comprendre ses actes.

La créature est contenue. Le laboratoire peut rouvrir. Mais cette fois, les interrupteurs d'urgence sont à portée de main.