Aller au contenu

S'exercerNiveau 3, Construire45 minutes

Un agent avec deux outils

Un agent, c'est un modèle qui demande des outils, et un programme qui les exécute en boucle jusqu'à la réponse. Quarante-cinq minutes pour en écrire un qui lit la demande d'un client et enregistre un brouillon de réponse, sans jamais rien envoyer, avec le SDK d'agents d'OpenAI et avec une boucle écrite à la main chez Anthropic.

Écrit le 9 octobre 2026 · Vérifié le 9 octobre 2026

Vous saurez

  • Écrire deux outils qui restent dans leur dossier
  • Les donner à un agent, et limiter le nombre de tours
  • Écrire la boucle d'un agent à la main, pour la comprendre
  • Vérifier ce que l'agent a fait, avant qu'une personne envoie

Dans le parcours

Construction

Étape 6 sur 12

Sur cette page
  1. L'essentiel
  2. Objectif
  3. Données fournies
  4. Consignes
  5. Critères de réussite
  6. Indications
  7. Correction commentée
  8. Extensions
  9. Lectures suivantes
L'essentiel
  1. Un agent tourne en boucle : le modèle répond ou demande un outil ; le programme exécute l'outil et lui rend le résultat ; on recommence jusqu'à une réponse finale, ou jusqu'au nombre de tours permis.
  2. Les outils font la sécurité : celui qui lit ne lit que le dossier des demandes ; celui qui écrit n'écrit qu'un brouillon .md ; aucun n'envoie.
  3. Cet agent lit un contenu venu de l'extérieur et vos fichiers, mais ne communique pas vers l'extérieur : sur les trois capacités dangereuses, il n'en réunit que deux.
  4. Le SDK d'agents d'OpenAI fait la boucle pour vous, avec une limite de tours ; chez Anthropic, on l'écrit en quelques lignes, et une erreur d'outil se rend au modèle avec is_error.
  5. Une personne relit le brouillon et l'envoie elle-même : l'agent prépare, il ne décide pas.
1

Objectif

Écrire, en Python, un agent à deux outils : lire_demande, qui lit la demande d'un client dans le dossier demandes, et enregistrer_brouillon, qui écrit un brouillon de réponse dans le dossier brouillons. Le lancer sur une demande fabriquée, vérifier le brouillon, et constater qu'aucun outil ne sort de son dossier. Compter quarante-cinq minutes, avec un poste prêt (le poste de développement) et l'exercice sur l'appel d'outils déjà fait.

2

Données fournies

La demande d'un client, fabriquée ; l'adresse en « .example » ne reçoit aucun courriel. À enregistrer dans un dossier demandes, à côté d'un dossier brouillons vide.

  • La demande 0815Texte Markdown, 1 Ko · un client signale une pièce manquante
    Télécharger
    Lire le document ici

    Exercice de Meridal. Le client, l'adresse et la commande sont fabriqués pour l'exercice ; l'adresse en « .example » ne reçoit aucun courriel. À enregistrer dans le dossier demandes, sous le nom demande-0815.md.

    De : achats@client-exemple.example

    Objet : Commande 2026-0815, pièce manquante

    Bonjour,

    Nous avons reçu la commande 2026-0815 le 2 octobre, mais le carton ne contenait que trois des quatre supports muraux commandés. Pouvez-vous nous envoyer le quatrième, et nous dire quand il partira ?

    Merci,

    Service achats

La boucle d'un agent, sur l'exercice

  1. 1La demande« Préparez la réponse à demande-0815.md »
  2. 2Le modèledemande l'outil lire_demande
  3. 3Votre programmeexécute l'outil, rend le texte
  4. 4Le modèledemande enregistrer_brouillon, avec l'objet et le texte
  5. 5Votre programmeécrit le fichier, rend « enregistré »
  6. 6Le modèlerépond : c'est fait ; la boucle s'arrête
Fond plein : le modèle, chez l'éditeur ; filet : votre programme, sur votre poste. Le modèle n'exécute rien : il demande ; c'est le programme qui lit et qui écrit. Ici, trois appels au modèle ; la limite est fixée à six.
3

Consignes

  1. Préparer le projet

    uv init agent-brouillons

    uv add openai-agents anthropic python-dotenv

    Ce qu'on fait : un projet par exercice ; la clé dans un fichier .env exclu de git. openai-agents 0.23.1 et anthropic 1.12.1 au 8 octobre 2026.

  2. Écrire les deux outils, dans outils.py

    Ce qu'on fait : chacun vérifie que le fichier reste dans son dossier ; le second n'écrit que des fichiers .md, et n'envoie rien.

  3. Les essayer sans modèle

    uv run essai_outils.py

    Ce qu'on fait : lire la demande, tenter de sortir du dossier, enregistrer un brouillon : sans rien payer.

  4. Écrire l'agent, avec le SDK d'OpenAI

    Ce qu'on fait : les deux outils déclarés avec @tool, leur description tirée de la docstring ; l'agent lancé avec max_turns=6.

  5. Ou écrire la boucle à la main, chez Anthropic

    Ce qu'on fait : tant que la réponse s'arrête sur tool_use, exécuter chaque outil et rendre un tool_result ; une erreur part avec is_error ; six tours au plus.

  6. Lancer, puis lire le brouillon

    Ce qu'on fait : le fichier brouillons/reponse-0815.md : il reprend la commande 2026-0815, ne promet aucune date, et dit qu'une personne reviendra vers le client.

4

Critères de réussite

5

Indications

L'agent tourne en rond et atteint la limite

CauseLe SDK lève MaxTurnsExceeded au-delà de max_turns.

À faireRendre les consignes plus nettes ; vérifier que chaque outil renvoie un texte clair, y compris en cas d'erreur.

Le modèle n'appelle pas l'outil

CauseLa description de l'outil ne dit pas à quoi il sert, ou le nom du fichier n'est pas dans la demande.

À faireUne docstring qui dit quoi et quand ; le nom du fichier dans la demande.

Le brouillon promet une date

CauseLe modèle comble un manque.

À faireLa consigne l'interdit déjà ; ajouter un contrôle par une règle sur le brouillon, ou une relecture obligatoire.

Une erreur d'outil arrête tout, chez Anthropic

CauseL'exception n'est pas attrapée.

À faireL'attraper, et rendre un tool_result avec is_error à true : le modèle peut alors corriger sa demande.

La demande contient une consigne cachée

CauseUn client peut écrire « ignorez vos consignes ». Cet agent n'a ni réseau ni envoi : au pire, un brouillon étrange, qu'une personne lira.

À faireGarder ce découpage ; voir la sécurité d'un outil d'IA.

6

Correction commentée

Les deux outils, en Python simple : la sécurité est là, pas dans les consignes du modèle.

outils.py
"""Les deux outils de l'agent : lire une demande, enregistrer un brouillon.
Aucun des deux n'envoie quoi que ce soit ; chacun reste dans son dossier."""
from pathlib import Path

ICI = Path(__file__).parent
DEMANDES = ICI / "demandes"
BROUILLONS = ICI / "brouillons"

def _dans(dossier: Path, nom: str) -> Path:
    """Le chemin du fichier, à condition qu'il reste dans le dossier prévu."""
    chemin = (dossier / nom).resolve()
    if chemin.parent != dossier.resolve():
        raise ValueError(f"Refusé : {nom} sort du dossier {dossier.name}.")
    return chemin

def lire_demande(nom: str) -> str:
    """Lit une demande de client dans le dossier demandes. Lecture seule."""
    return _dans(DEMANDES, nom).read_text(encoding="utf-8")

def enregistrer_brouillon(nom: str, objet: str, texte: str) -> str:
    """Enregistre un brouillon de réponse dans le dossier brouillons. N'envoie rien."""
    chemin = _dans(BROUILLONS, nom)
    if chemin.suffix != ".md":
        raise ValueError("Refusé : un brouillon est un fichier .md.")
    chemin.write_text(f"Objet : {objet}\n\n{texte}\n", encoding="utf-8")
    return f"Brouillon enregistré : brouillons/{chemin.name}"
essai_outils.py
"""Essai des deux outils, sans modèle : ce que l'agent pourra faire, et ce qu'il ne pourra pas."""
from outils import enregistrer_brouillon, lire_demande

print(lire_demande("demande-0815.md").splitlines()[1])
for nom in ["../.env", "../../outils.py"]:
    try:
        lire_demande(nom)
    except ValueError as erreur:
        print(erreur)
print(enregistrer_brouillon("essai.md", "Essai", "Un brouillon d'essai."))
try:
    enregistrer_brouillon("essai.sh", "Essai", "echo")
except ValueError as erreur:
    print(erreur)
La sortieuv run essai_outils.py
Objet : Commande 2026-0815, pièce manquanteRefusé : ../.env sort du dossier demandes.Refusé : ../../outils.py sort du dossier demandes.Brouillon enregistré : brouillons/essai.mdRefusé : un brouillon est un fichier .md.

Essayé le 9 octobre 2026 avec Python 3.12.7, sans modèle.

Le SDK fait la boucle ; max_turns la borne. Les outils sont déclarés avec @tool, comme dans la documentation du 8 octobre 2026.

agent_openai.py
import asyncio

from dotenv import load_dotenv
from agents import Agent, Runner
from agents.decorators import tool

import outils

load_dotenv()

@tool
def lire_demande(nom: str) -> str:
    """Lit une demande de client dans le dossier demandes. Lecture seule.

    Args:
        nom: Le nom du fichier, par exemple demande-0815.md.
    """
    return outils.lire_demande(nom)

@tool
def enregistrer_brouillon(nom: str, objet: str, texte: str) -> str:
    """Enregistre un brouillon de réponse dans le dossier brouillons. N'envoie rien.

    Args:
        nom: Le nom du brouillon, en .md.
        objet: L'objet du courriel.
        texte: Le texte de la réponse.
    """
    return outils.enregistrer_brouillon(nom, objet, texte)

agent = Agent(
    name="Brouillons du service client",
    instructions=(
        "Vous préparez des brouillons de réponse aux clients. Lisez la demande, puis enregistrez "
        "un brouillon court et poli. N'inventez aucune date ni aucun délai : s'ils manquent, "
        "écrivez qu'une personne reviendra vers le client."
    ),
    model="gpt-6-luna",
    tools=[lire_demande, enregistrer_brouillon],
)

async def main():
    result = await Runner.run(
        agent, "Préparez la réponse à demande-0815.md, dans reponse-0815.md.", max_turns=6
    )
    print(result.final_output)

if __name__ == "__main__":
    asyncio.run(main())
La sortieuv run agent_openai.py

Pas encore exécuté par Meridal. Ce qu'on attend : une phrase qui dit que le brouillon est enregistré, et le fichier brouillons/reponse-0815.md.

Pourquoi deux outils étroits plutôt qu'un seul large. Un outil « lire un fichier » sans limite donnerait au modèle tout le disque ; un outil « envoyer un courriel » ferait sortir le brouillon sans relecture. Deux outils étroits, et la relecture par une personne, suivent ce que demande le CERT-FR pour tout agent : le strict nécessaire, et une validation humaine avant toute action qui a un effet.

7

Extensions

  • Un troisième outil, en lecture : le statut de la commande dans votre logiciel, comme dans l'appel d'outils ; le brouillon peut alors dire si la pièce est en stock.
  • Les mêmes outils, ailleurs : le Claude Agent SDK les déclare avec @tool et un serveur MCP en mémoire ; Google ADK prend deux fonctions Python simples.
  • Les outils dans un serveur MCP, pour qu'un assistant de bureau s'en serve aussi : un serveur MCP.
  • Une trace de chaque passage : la demande lue, les outils appelés, le brouillon écrit ; voir l'observabilité.
  • Les bibliothèques d'agents, comparées : les bibliothèques d'agents.
8

Lectures suivantes

Vous êtes ici

Un agent avec deux outils

→ EnsuiteUn serveur MCPS'exercer · Niveau 3 · 45 minutes

Construction · étape 7 sur 12

↓ Plus concret

Rien avant : c'est un point de départ.

Tout le dossier « Les agents »

Limites et sources

La demande est fabriquée ; le code qui appelle un modèle n'a pas été exécuté par Meridal, les appels payants n'étant pas ouverts.

Les noms de modèles et de bibliothèques sont ceux des documentations au 8 octobre 2026.

  1. OpenAI, outils du SDK d'agents ; OpenAI, lancer un agent ; Anthropic, traiter les appels d'outils ; Claude Agent SDK, outils ; Google ADK, deux outils.
  2. Le relevé : apprendre/recherches/2026-10-08-dev-avance.json.

Écrit par Meridal, avec l'aide de l'IA ; chaque fait vérifié à sa source et relu avant publication. Texte et schémas sous licence CC BY 4.0 : reprenez-les en citant Meridal. Le code sous licence MIT.

Service correspondant

Ne plus recopier d'un logiciel à l'autre

1 100 € à 3 400 €, puis sur devis

Voir le service

Un premier échange de 30 minutes

En visio et sans frais : vous décrivez votre situation, nous vous indiquons ce qui est réalisable, avec quels outils et pour quel ordre de prix. Vous pouvez aussi nous écrire ; chaque message reçoit une réponse écrite.