La boucle n'a pas changé, juste les outils
« La boucle n'a pas bougé d'une ligne, j'ai juste ajouté des entrées dans le tableau TOOLS. » — s02_tool_use.py
Où faut-il toucher le code pour ajouter un outil ?
L'agent de S01 ne sait faire que bash. Pour lui ajouter read_file, write_file et edit_file, que faut-il modifier ?
Le premier réflexe : modifier la boucle. C'est faux. La boucle ne change pas d'une ligne. Il suffit de trois choses :
- Écrire une fonction handler Python (
run_read(path, limit)). - L'enregistrer dans la table
TOOL_HANDLERS("read_file": lambda **kw: run_read(...)). - Ajouter une déclaration JSON schema dans le tableau
TOOLS(pour indiquer au modèle le nom de l'outil et ses paramètres).
Quand la boucle rencontre un bloc tool_use, elle cherche la fonction dans la dispatch map via block.name, l'exécute et réinjecte le résultat dans un tool_result. Exactement le même chemin que pour bash.
# Dispatch map: name → handler lambda TOOL_HANDLERS = { "bash": lambda **kw: run_bash(kw["command"]), "read_file": lambda **kw: run_read(kw["path"], kw.get("limit")), "write_file": lambda **kw: run_write(kw["path"], kw["content"]), "edit_file": lambda **kw: run_edit(kw["path"], kw["old_text"], kw["new_text"]), }
Voir le dispatch router en action
Le widget ci-dessous vous permet de cliquer sur une requête tool_use que le modèle pourrait émettre, et d'observer comment elle est routée vers la fonction Python correspondante. À noter : c'est block.name qui détermine le chemin emprunté.
safe_path : un garde-fou indispensable
Donner à l'agent un accès au système de fichiers expose au risque le plus classique : la traversée de répertoire. Le modèle est censé travailler dans /home/user/project/, mais il envoie read_file("../../etc/passwd").
s02 inclut une petite fonction qui règle ce problème :
def safe_path(p: str) -> Path: path = (WORKDIR / p).resolve() # normalise les .. et les liens symboliques if not path.is_relative_to(WORKDIR): raise ValueError(f"Path escapes workspace: {p}") return path
La clé, c'est l'enchaînement .resolve() + .is_relative_to() : on résout d'abord en chemin absolu, puis on vérifie qu'il est toujours dans le bac à sable. Sans le premier, foo/../../etc passerait ; sans le second, rien n'est vérifié du tout.
Identifier les chemins sûrs
Voici 5 valeurs que le modèle pourrait passer comme paramètre path à read_file. Lesquelles safe_path() laisse-t-il passer, lesquelles rejette-t-il ? Supposons WORKDIR = /home/user/project.
N'ajoutez pas d'outils dangereux
Ajouter un outil est simple, mais n'oubliez pas : chaque outil que vous ajoutez élargit le périmètre d'action du modèle. Dans s02, bash dispose déjà d'une liste noire (rm -rf /, sudo, shutdown) et write_file est limité par safe_path. Avant d'ajouter un outil en production, posez-vous trois questions :
- Cet outil peut-il produire des effets irréversibles ? (rm, envoi d'e-mail, git push)
- Peut-il exposer des données sensibles ? (variables d'environnement, .ssh, cookies)
- Sa sortie pourrait-elle être interprétée comme une instruction ? (injection de prompt via la sortie d'outil)
La leçon s07 introduira une « couche de permissions » pour externaliser ces décisions du code et les rendre déclaratives.