Contrôler la boucle d'exécution
- Leçon 6
- 50 min
- Module 2 : Représenter l'état et les outils
Leçon 6 sur 18 · Agents d'IA et orchestration des flux de travailContrôler la boucle d'exécution
1. Choisir le bon modèle d'orchestration
2. Représenter l'état et les outils
3. Raisonnement, planification et contexte
4. Approbation humaine et reprise
5. Plusieurs exécutants et intégrations
6. Évaluer et exploiter un agent borné
Ce que vous saurez faire
Assurez-vous que chaque exécution de l’agent se termine pour une raison précise, quoi que fasse le modèle. Prouvez-le avec des tests. Incluez un modèle qui appelle des outils inexistants et un modèle qui échoue sans cesse.
Avant de commencerCe qu’il faut savoir et avoir sous la main
Ce que vous devez savoir
- La limite d’étapes, la limite de temps et la vérification des appels répétés de l’assistant de billets de soutien, dans Limiter la boucle. Cette leçon intègre les mêmes idées à la boucle de l’agent et en ajoute deux.
- L’état de la tâche et son
stop_reason(raison d’arrêt), dans Un état de tâche explicite. Les résultats d’erreur des outils, dans Contrats d’outils.
Ce qu’il vous faut sur votre ordinateur
- Le projet après Contrats d’outils : ajouter les contrats au projet :
python -m pytestaffiche24 passed(24 tests réussis). - L’instantané
end-of-m02. Vous y copiezresolver/loop.pyettests/test_loop.py.
Aucun compte ni aucune clé : chaque décision du modèle est un enregistrement. Pour cette leçon, gemma3:4b dans Ollama a traité cinq billets sans règles d’arrêt, sur un ordinateur avec 16 Go de mémoire. Tests effectués avec Python 3.12.13 sur macOS. Les commandes Windows n’ont pas été testées.
Une carte qui échoue sans cesse
Section intitulée « Une carte qui échoue sans cesse »Un client paie par carte dans un magasin. Le lecteur affiche « declined » (refusée). La caissière réessaie, car les lecteurs échouent parfois une première fois. Cette fois, ça fonctionne. Bien.
La carte d’un autre client est refusée trois fois de suite. La caissière s’arrête et demande un autre moyen de paiement. Elle ne passe pas la carte tout l’après-midi. Parfois, le paiement passe et le client lui redonne la carte pour le même achat. Elle ne facture pas deux fois : quelque chose ne va pas, et elle s’arrête. Si la file derrière le client devient trop longue, la responsable intervient.
Trois règles : réessayer après un échec, s’arrêter après trois échecs de suite, ne jamais répéter ce qui a déjà fonctionné. Plus une limite de temps. La caissière n’a pas besoin de comprendre pourquoi la carte échoue. Elle a besoin de règles.
Le problème : le modèle décide quand s’arrêter
Section intitulée « Le problème : le modèle décide quand s’arrêter »Dans la boucle de l’agent, le modèle choisit la prochaine étape : une lecture ou finish (terminer). Rien ne l’oblige à terminer. À ce stade, la boucle a une seule limite : 8 appels au modèle. Elle s’arrête ensuite avec max_steps (limite d’étapes). Est-ce suffisant?
Pour le savoir, cinq exécutions de gemma3:4b ont été enregistrées pour cette leçon, sans aucune autre règle, jusqu’à 12 appels au modèle (reference/c10/m02/record_unbounded.py). Ce sont de vraies exécutions de l’agent du projet, pas des scénarios inventés :
- T-80001 (la commande du voisin) : après une recherche refusée, le modèle a appelé
get_customer_orders11 fois de suite. Chaque fois, il a reçu la même liste vide. - T-80009 (un identifiant de commande à 5 chiffres) : il a alterné six fois entre deux appels. D’abord
get_orderavecLK-55190(refusé par le contrat), puissearch_policyavec « invalid order id » (identifiant de commande invalide). - T-90608 (la liste des commandes est indisponible, par simulation) : il a appelé
get_customer_orders11 fois. Le service a dépassé le délai 11 fois. - T-64872 (facturé deux fois) : il a proposé la même résolution invalide six fois. Il a ensuite donné quatre réponses coupées à la limite de jetons. Cette exécution a attendu le modèle pendant 111,9 secondes.
- T-80005 (un hamac en retard, en français) : ses trois premières résolutions ont été refusées. La quatrième a été acceptée, à l’appel 5.
Quatre des cinq exécutions n’ont jamais terminé. Une limite de 8 étapes les arrêterait, mais seulement après 8 appels. Chaque appel renvoie toute la conversation et peut prendre plusieurs secondes. La limite ne dit rien sur la raison du problème. Personne n’en tire donc de leçon.
L’idée : une boucle qui termine toujours, pour une raison précise
Section intitulée « L’idée : une boucle qui termine toujours, pour une raison précise »La terminaison signifie que la boucle se termine sur tous les chemins, quelle que soit la réponse du modèle. La boucle du projet se termine pour exactement une des raisons suivantes, inscrite dans state.stop_reason : finished (terminé), max_steps, time (temps), cost (coût), repeated_call (appel répété), too_many_errors (trop d’erreurs), no_recording (aucun enregistrement) ou provider_error (erreur du fournisseur).
Il existe deux types de limites. Les limites de budget sont vérifiées avant chaque appel au modèle. Elles arrêtent toute exécution trop longue :
- La limite d’étapes : au plus 8 appels au modèle.
- Le budget de temps : au plus 120 secondes à attendre le modèle. Lors d’une relecture, la boucle additionne les latences enregistrées. La relecture s’arrête donc là où l’exécution réelle s’arrêterait.
- Le plafond de dépenses : au plus 0,05 $ US par tâche, selon le nombre de jetons et les prix datés.
Les règles de comportement sont vérifiées après chaque appel au modèle. Elles arrêtent rapidement une exécution bloquée :
- Détection des appels répétés : la même lecture, avec les mêmes arguments, après sa réussite. La réponse ne peut pas changer; le modèle est donc bloqué.
- Erreurs consécutives : trois échecs de suite arrêtent la boucle. Un échec est une lecture refusée ou échouée, une résolution refusée ou une réponse sans étape utilisable. Une réussite remet le compteur à 0.
Le schéma place les vérifications dans l’ordre :
Description textuelle du diagramme
Avant chaque appel au modèle, la boucle vérifie le budget : 8 étapes, 120 secondes de temps du modèle, 0,05 $ US. Si un budget est épuisé, elle s’arrête avec max_steps, time ou cost.
Elle appelle ensuite le modèle. Une erreur du fournisseur l’arrête avec provider_error; un enregistrement manquant l’arrête avec no_recording.
Si la réponse n’a aucune étape utilisable, le compteur d’erreurs augmente de 1. Si la réponse est finish, la boucle vérifie la résolution : valide, et toutes les commandes consultées. Si oui, elle s’arrête avec finished; sinon, le compteur d’erreurs augmente de 1. Si la réponse est une lecture ayant déjà réussi avec les mêmes arguments, elle s’arrête avec repeated_call. Sinon, run_read l’exécute : une réussite remet le compteur à 0, une erreur ajoute 1.
Après une erreur, s’il y en a 3 de suite, la boucle s’arrête avec too_many_errors. Sinon, elle revient à la vérification du budget.
Les valeurs viennent des enregistrements. Le nombre maximal d’appels au modèle pour une exécution réelle sur Azure était de 4 pour chat-small et de 5 pour chat-strong. La limite de 8 laisse donc une marge. La tâche réelle la plus lente a pris 72.857 secondes de temps du modèle (gemma3:4b). La limite de 120 secondes n’arrête donc qu’une exécution très loin de la normale. La tâche chat-strong la plus chère a coûté environ 0,033 $ US. Le plafond de 0,05 $ US par tâche n’arrête donc qu’une exécution qui dépasse largement la normale. La règle des trois erreurs correspond à la requête (prompt), qui dit au modèle : « Si un outil échoue, vous pouvez réessayer une fois. »
Pourquoi seule une répétition réussie arrête la boucle
Section intitulée « Pourquoi seule une répétition réussie arrête la boucle »La règle des appels répétés pourrait arrêter toute répétition. Elle ne le fait pas, car répéter après un échec est souvent approprié. Pour T-90601, le service des commandes dépasse le délai une fois (par simulation). chat-small a demandé get_order LK-645001 et reçu service_timeout (délai du service dépassé). Il a redemandé avec les mêmes arguments, reçu la commande et terminé avec la bonne réponse. Une règle qui arrêterait chaque répétition confierait ce billet à une personne sans raison.
La règle des erreurs s’occupe des appels qui continuent d’échouer. Pour T-90608, la liste des commandes dépasse le délai chaque fois. Le troisième dépassement consécutif arrête la boucle, à l’appel 3.
Un petit exemple détaillé : T-80009, appel par appel
Section intitulée « Un petit exemple détaillé : T-80009, appel par appel »Voici les quatre premiers appels de l’exécution sans limites de T-80009, avec les règles du projet appliquées :
| Appel | Demande du modèle | Réponse de la boucle | Erreurs consécutives | Répétition d’une réussite? |
|---|---|---|---|---|
| 1 | get_order LK-55190 |
invalid_arguments (arguments invalides) |
1 | non |
| 2 | search_policy « invalid order id » |
réussite | 0 | non |
| 3 | get_order LK-55190 |
invalid_arguments |
1 | non : le premier a échoué |
| 4 | search_policy « invalid order id » |
non exécuté | 1 | oui : arrêt, repeated_call |
La règle des erreurs ne se déclenche jamais ici, car chaque recherche réussie remet le compteur à 0. La limite d’étapes ne se déclencherait qu’à l’appel 8. La règle des appels répétés détecte le problème à l’appel 4, après 7,99 secondes de temps du modèle plutôt qu’environ 15.
Explorer où la boucle s’arrête
Section intitulée « Explorer où la boucle s’arrête »Chaque exécution dans l’explorateur est l’une des cinq exécutions sans limites de gemma3:4b. L’explorateur applique les règles aux appels enregistrés, dans le même ordre que run_loop. Un script de vérification le compare à la boucle réelle du projet pour les 1 080 combinaisons de paramètres.
Explorer
Où la boucle s'arrête-t-elle?
Prédisez d’abord : Pour T-80001, le modèle demande get_customer_orders sans cesse. Avec les limites du projet, après combien d'appels au modèle la boucle s'arrête-t-elle, et quelle règle l'arrête? Que se passe-t-il si vous désactivez la règle des appels répétés?
- Raison de l’arrêt
repeated_call- Appels au modèle
- 3
- Temps du modèle
- 16,2 s
L’appel 3 demandait get_customer_orders avec les mêmes arguments qu’un appel réussi plus tôt. La boucle s’arrête avant son exécution. Aucune proposition : le billet va à une personne. Sans aucune règle, l’exécution enregistrée a continué jusqu’à 12 appels, le maximum permis, sans jamais terminer.
- Exécuté : une lecture réussie ou une résolution valide
- Exécuté : une erreur (refus, échec ou réponse inutilisable)
- Jamais demandé : la boucle s’était arrêtée
| Appel | Demande du modèle | Réponse de la boucle | Temps (s) |
|---|---|---|---|
| 1 | get_ | other_customer: No order LK-615204 is available for this customer. (1 de suite) | 3,3 |
| 2 | get_ | ok | 10,0 |
| 3 | get_ | non exécuté : la boucle s’arrête (appel répété) | 16,2 |
| 4 | get_ | jamais demandé | - |
| 5 | get_ | jamais demandé | - |
| 6 | get_ | jamais demandé | - |
| 7 | get_ | jamais demandé | - |
| 8 | get_ | jamais demandé | - |
| 9 | get_ | jamais demandé | - |
| 10 | get_ | jamais demandé | - |
| 11 | get_ | jamais demandé | - |
| 12 | get_ | jamais demandé | - |
Description textuelle de l’explorateur
L’explorateur affiche une exécution enregistrée à la fois. Il y en a cinq. T-80001 concerne la commande du voisin. T-80009 a un identifiant de commande à 5 chiffres. Dans T-90608, la liste des commandes est indisponible (par simulation). T-64872 concerne une double facturation, et T-80005 un hamac en retard, en français.
La commande principale est la limite d’étapes, de 1 à 12 appels au modèle; le projet utilise 8. Les autres commandes sont la règle des appels répétés (désactivée, même appel réussi, tout appel identique) et celle des erreurs consécutives (désactivée, 3). La dernière est le budget de temps (désactivé, 120 s, 30 s).
Un tableau présente chaque appel au modèle. Il affiche la demande du modèle, la réponse de la boucle, les erreurs consécutives et le temps du modèle cumulé. Une ligne épaisse marque l’arrêt de la boucle.
Avec les limites du projet : T-80001 s’arrête à l’appel 3 (repeated_call, 16,2 s). T-80009 s’arrête à l’appel 4 (repeated_call, 8,0 s). T-90608 s’arrête à l’appel 3 (too_many_errors, 8,4 s). T-64872 s’arrête à l’appel 5 (too_many_errors, 39,1 s). T-80005 s’arrête à l’appel 4 (too_many_errors, 27,9 s), un appel avant que le modèle aurait terminé.
Sans la règle des appels répétés, T-80001 atteint la limite d’étapes : 8 appels et 35,9 s. Sans la règle des erreurs, T-90608 atteint aussi 8 appels. Avec un budget de temps de 30 s, T-64872 s’arrête avant l’appel 5 (time, 30,5 s).
Regardez T-80005 avec les limites du projet. La règle des erreurs l’a arrêté à l’appel 4. Le modèle aurait terminé à l’appel 5 avec reply_only (réponse seulement). C’est le résultat attendu, que l’auteur du cours a rédigé à partir de la politique de Grace. La règle a donc un coût : elle abandonne certaines exécutions qui auraient pu se rétablir. Ici, le compromis est clair : une personne lit un billet, et aucun modèle ne tourne pendant des minutes sur une tâche bloquée.
Faire terminer la boucle dans le projet
Section intitulée « Faire terminer la boucle dans le projet »-
Copiez
resolver/loop.pyde l’instantanéend-of-m02par-dessus votre fichier. Les changements sont petits : lisez-les dans la prochaine section. -
Ouvrez
resolver/config.py. DansSettings, remplacez la lignemax_steps: int = 8 …par trois lignes :resolver/config.py (Settings) max_steps: int = 8 # the usage cap, per taskmax_seconds: float = 120.0max_cost_usd: float = 0.05 -
Dans
from_env, remplacez la lignemax_steps=int(env.get("RESOLVER_MAX_STEPS", "8")))par :resolver/config.py (from_env) max_steps=int(env.get("RESOLVER_MAX_STEPS", "8")),max_seconds=float(env.get("RESOLVER_MAX_SECONDS", "120")),max_cost_usd=float(env.get("RESOLVER_MAX_COST_USD", "0.05"))) -
Remplacez
limitsà la fin de la classe :resolver/config.py (limits) def limits(self) -> Limits:return Limits(max_steps=self.max_steps, max_seconds=self.max_seconds, max_cost_usd=self.max_cost_usd) -
Copiez
tests/test_loop.pyde l’instantané dans votre dossiertests. -
Exécutez les tests :
Fenêtre de terminal python -m pytestLa dernière ligne est
35 passed(35 tests réussis).
Ce qui a changé dans la boucle
Section intitulée « Ce qui a changé dans la boucle »Limits gagne trois champs : max_seconds, max_cost_usd et max_errors (3). check_budget s’exécute avant chaque appel au modèle et vérifie maintenant les trois budgets :
def check_budget(state: TaskState, limits: Limits) -> None: if state.step >= limits.max_steps: raise Stop("max_steps") if state.usage.model_seconds > limits.max_seconds: raise Stop("time") if state.usage.cost_usd is not None and state.usage.cost_usd > limits.max_cost_usd: raise Stop("cost")run_loop conserve deux nouvelles variables locales : calls_seen et errors_in_a_row. La première est un dictionnaire qui associe la signature d’un appel à sa réussite ou à son échec. La signature contient le nom de l’outil et ses arguments compacts. Avant d’exécuter une lecture, la boucle vérifie la signature :
signature = name + compact(json.loads(args) if isinstance(args, str) and args.strip().startswith("{") else args) if calls_seen.get(signature) is True: state.log("stop", "repeated_call", tool=name, arguments=args) raise Stop("repeated_call")Après la lecture, calls_seen[signature] = result.ok, et errors_in_a_row revient à 0 après une réussite ou augmente de 1 après une erreur. Une résolution refusée et une réponse sans étape utilisable ajoutent aussi 1. À la fin de chaque étape, trois erreurs consécutives déclenchent Stop("too_many_errors"). Chaque Stop aboutit au même endroit : la raison entre dans state.stop_reason, et l’exécuteur définit l’état à stopped (arrêté).
Voir chaque règle arrêter une exécution réelle
Section intitulée « Voir chaque règle arrêter une exécution réelle »-
Exécutez T-80006 avec gemma3:4b :
Fenêtre de terminal python -m resolver run T-80006 --variant agent_structured --model gemma3:4b -
Exécutez T-64872 avec gemma3:4b, avec sa trace :
Fenêtre de terminal python -m resolver run T-64872 --variant agent_structured --model gemma3:4b --trace -
Exécutez T-80009 avec une limite de 2 étapes. Sur macOS et Linux :
Fenêtre de terminal RESOLVER_MAX_STEPS=2 python -m resolver run T-80009Sur Windows, dans PowerShell :
Fenêtre de terminal $env:RESOLVER_MAX_STEPS = "2"; python -m resolver run T-80009; Remove-Item Env:RESOLVER_MAX_STEPS -
Exécutez T-80009 avec un budget de temps de 3 secondes, puis un plafond de dépenses de 0,0005 $ US, de la même façon :
Fenêtre de terminal RESOLVER_MAX_SECONDS=3 python -m resolver run T-80009RESOLVER_MAX_COST_USD=0.0005 python -m resolver run T-80009
L’étape 1 se termine avec Stopped: too_many_errors. No proposal: the ticket goes to a person. (Arrêt : trop d’erreurs. Aucune proposition : le billet est confié à une personne.) gemma3:4b a envoyé get_order avec un identifiant null, puis proposé deux résolutions invalides. Avant cette leçon, la même commande se terminait avec no_recording à l’étape 4. La boucle a sollicité le modèle une quatrième fois. Mais l’exécution enregistrée s’était arrêtée après trois appels, donc il n’y a pas de quatrième réponse.
L’étape 2 affiche l’enregistrement du projet pour T-64872, une exécution différente de celle de l’explorateur :
Run T-64872-agent_structured-gemma3:4b: agent_structured, gemma3:4b, ticket T-64872, status stopped4 model call(s), 9209 tokens in, 334 out, US$0.00000, 13.508 s waiting for the model
1 plan ["Tool: search_policy"] 1 model gemma3:4b: search_policy({"query": "double charge terracotta pot…) (1884 in, 50 out, 2.795 s) 1 tool search_policy({"query": "double charge terracotta pots"}) -> ok 2 plan ["Tool: get_payments"] 2 model gemma3:4b: get_payments({"order_id": "LK-739156"}) (2241 in, 44 out, 2.106 s) 2 tool get_payments({"order_id": "LK-739156"}) -> ok 3 plan ["Tool: finish"] 3 model gemma3:4b: finish({"outcome": "reply_only", "rule": "W7",…) (2409 in, 193 out, 6.589 s) 3 proposal REFUSED: The resolution is not valid: actions.0.create_return_label.sku: Input should be a valid string; act… 4 plan ["Tool: search_policy"] 4 model gemma3:4b: search_policy({"query": "double charge terracotta pot…) (2675 in, 47 out, 2.018 s) 4 stop repeated_call 4 stop repeated_callLa recherche à l’appel 4 est identique à la recherche réussie à l’appel 1. La boucle s’arrête donc avant de l’exécuter. La ligne stop (arrêt) apparaît deux fois dans cette version de la trace. La boucle consigne l’appel répété, puis l’arrêt lui-même.
Les étapes 3 et 4 arrêtent l’exécution enregistrée de chat-small pour T-80009, qui termine normalement après 4 appels :
Stopped: max_steps. No proposal: the ticket goes to a person.Stopped: time. No proposal: the ticket goes to a person.Stopped: cost. No proposal: the ticket goes to a person.L’arrêt lié au temps utilise la latence enregistrée. Après l’appel 3, le temps du modèle est de 1,06 + 0,877 + 1,373 = 3,31 secondes, soit plus de 3. L’arrêt lié au coût survient aussi après l’appel 3, à environ 0,0007 dollar. La boucle vérifie chaque budget avant un appel. Une exécution peut donc dépasser un budget d’un appel, jamais plus.
Comment les 70 exécutions de gemma3:4b se sont terminées
Section intitulée « Comment les 70 exécutions de gemma3:4b se sont terminées »Les règles de la boucle comptent surtout pour un modèle faible. Voici comment l’exécution enregistrée du projet avec gemma3:4b s’est terminée sur les 70 tâches, avec les règles de cette leçon :
Description textuelle du graphique
Un diagramme à barres montrant comment les 70 exécutions se sont terminées : finished 23, too_many_errors 37, repeated_call 10, max_steps 0.
Il y a eu 37 arrêts too_many_errors. Parmi eux, 34 suivaient trois résolutions refusées de suite, 2 des réponses coupées à la limite de jetons et 1 un dépassement du délai du service. Aucune exécution n’a atteint la limite d’étapes. Les règles de comportement ont arrêté chaque exécution bloquée avant, après au plus 8 appels. chat-small et chat-strong ont terminé 69 tâches sur 70. La 70e était T-80002, arrêtée par le filtre de contenu du fournisseur (provider_error). Le modèle local gratuit exécute donc correctement la boucle, et celle-ci vous protège de ses mauvaises décisions. Elle ne les rend pas bonnes : gemma3:4b a proposé la bonne résolution pour 28 des 70 tâches.
Quand utiliser chaque règle, et ce qu’elle coûte
Section intitulée « Quand utiliser chaque règle, et ce qu’elle coûte »Chaque boucle d’agent a besoin d’une limite d’étapes et d’un budget de temps. Ils s’appliquent à tous les modèles et à toutes les défaillances. Ajoutez un plafond de dépenses lorsqu’un fournisseur réel vous facture. Ajoutez les règles de comportement quand vos traces montrent des exécutions bloquées; avec un petit modèle, vous en verrez.
Chaque règle a un coût. Une limite d’étapes basse arrête des exécutions lentes, mais correctes. La règle d’erreurs a abandonné T-80005 un appel trop tôt. La règle d’appels répétés exige une bonne signature : {"order_id":"LK-640436"} et {"order_id": "LK-640436"} doivent compter comme le même appel. La boucle compare donc du JSON compact. Et chaque arrêt confie un billet de soutien à une personne, ce qui lui prend du temps. Choisissez les valeurs à partir de vos enregistrements, comme ici, et revérifiez-les quand vous changez de modèle.
Erreur courante
« Arrêter tout appel répété, par sécurité »
Symptôme : Amira essaie la règle plus stricte « tout appel identique » dans l’explorateur. Sur T-80009, elle arrête la boucle au troisième appel, un appel plus tôt. Amira remplace donc la vérification dans loop.py par if signature in calls_seen:. Maintenant, python -m resolver run T-90601 affiche Stopped: repeated_call. No proposal: the ticket goes to a person. (arrêt : appel répété; aucune proposition; le billet passe à une personne). Le test test_a_failed_call_may_be_tried_again échoue. Le service de commandes avait dépassé le délai une fois (simulation). Le deuxième appel, qui aurait réussi, n’a jamais eu lieu.
Cause : répéter un appel après un échec est souvent la bonne étape. Les dépassements de délai et les courtes pannes passent, et la requête permet une nouvelle tentative.
Correction : arrêtez un appel répété seulement après une réussite, car la réponse ne peut alors plus changer. Laissez la règle d’erreurs gérer les appels qui échouent sans cesse : trois de suite, et la boucle s’arrête.
Ce que cela signifie pour un vrai service d’IA : une règle d’arrêt vous protège seulement si vous comprenez pourquoi le modèle se répète. Testez chaque règle sur des traces réelles, autant celles qui restent bloquées que celles qui reprennent.
Exercice du module : une boucle d’outils simulée aux résultats prévisibles
Section intitulée « Exercice du module : une boucle d’outils simulée aux résultats prévisibles »Créez votre propre fichier de tests avec un modèle à scénario prédéfini. Ce faux modèle renvoie les étapes que vous écrivez, donc chaque résultat est prévisible. tests/scripted.py en contient un (Scripted, call, finish). Les outils sont les vrais outils de lecture sur une copie neuve de la base de données d’exercice. Les défaillances sont simulées là où vous les demandez.
- Créez
tests/test_my_loop.py. Utilisez le billet T-90103 (la jardinière fissurée, commande LK-640436). - Écrivez un cas de réussite : un
get_order, puis unfinishavec une réexpédition. La boucle s’arrête avecfinished(terminé) après 2 appels. - Écrivez deux cas d’échec : un modèle qui appelle toujours un outil inconnu, et un service
get_orderqui dépasse toujours le délai (Faults). Les deux s’arrêtent avectoo_many_errors(trop d’erreurs) après 3 appels. - Écrivez un cas qui montre la remise à zéro : deux lectures échouées, une réussite, un échec, puis
finish. Il doit se terminer après 5 appels. - Exécutez
python -m pytest tests/test_my_loop.py.
Afficher un exemple de réponse
"""Module practice: a mock tool loop with predictable success and failure cases.
The model is SCRIPTED (tests/scripted.py): no model wrote these steps. The tools are the realread tools on a fresh copy of the practice database, with simulated failures where a test asks for them."""
from resolver.agent import run_agentfrom resolver.data import load_taskfrom resolver.loop import Limitsfrom resolver.runner import new_statefrom resolver.systems import Faults
from .scripted import Scripted, call, finish
PLANTER = [{"tool": "reship_item", "order_id": "LK-640436", "sku": "PLANTER", "quantity": 1}]
def run(world, script, faults=None, limits=Limits()): task = load_task("T-90103") # the cracked cedar planter state = new_state(task, "agent", "chat-small", "practice") return run_agent(state, Scripted(script).complete, world("T-90103", faults), "tools", limits)
def test_success_read_then_finish(world): s = run(world, [[call("get_order", order_id="LK-640436")], [finish("resolve", "W5", PLANTER)]]) assert (s.stop_reason, s.step) == ("finished", 2) assert [e.code for e in s.evidence] == ["ok"]
def test_an_unknown_tool_again_and_again_ends_the_loop(world): s = run(world, [[call("refund_now", order_id="LK-640436", amount=89)]]) assert (s.stop_reason, s.step) == ("too_many_errors", 3) assert [e.code for e in s.evidence] == ["unknown_tool"] * 3
def test_a_service_that_never_answers_ends_the_loop(world): down = Faults([{"tool": "get_order", "mode": "timeout", "times": 99}]) # simulated s = run(world, [[call("get_order", order_id="LK-640436")]], faults=down) assert (s.stop_reason, s.step) == ("too_many_errors", 3) assert [e.code for e in s.evidence] == ["service_timeout"] * 3
def test_a_success_resets_the_error_count(world): script = [[call("get_order", order_id="LK-000001")], [call("get_order", order_id="LK-000002")], [call("get_order", order_id="LK-640436")], [call("get_order", order_id="LK-000003")], [finish("resolve", "W5", PLANTER)]] s = run(world, script) assert (s.stop_reason, s.step) == ("finished", 5) assert [e.ok for e in s.evidence] == [False, False, True, False]python -m pytest tests/test_my_loop.py affiche 4 passed (4 tests réussis), et la suite complète affiche 39 passed (39 tests réussis). Le modèle à scénario prédéfini répète sa dernière étape quand le scénario se termine. Un scénario d’une seule étape représente donc un modèle qui ne change jamais d’avis. L’outil inconnu n’est jamais exécuté, et l’appel échoué peut être répété. Aucun des deux cas ne s’arrête donc avec repeated_call (appel répété) : la règle d’erreurs met fin aux deux.
À vous. Changez une condition : exécutez le cas de l’outil inconnu avec Limits(max_errors=2). Prédisez la raison de l’arrêt et le nombre d’appels avant l’exécution. (Réponse : too_many_errors après 2 appels.)
Vérification du module
Section intitulée « Vérification du module »Prouvez que la boucle se termine après des erreurs répétées et des appels d’outils inconnus. Exécutez les deux tests de l’instantané et les vôtres :
python -m pytest tests/test_loop.py -k "unknown or repeated_errors" -vpython -m pytest tests/test_my_loop.pyLa première commande affiche 2 passed, 9 deselected (2 tests réussis, 9 non sélectionnés). Vous avez terminé le module quand :
python -m pytestréussit, avec vos nouveaux tests.- Vous pouvez expliquer quelle règle arrête chacune des cinq exécutions sans limites dans l’explorateur, avec les limites du projet. Vous pouvez aussi expliquer pourquoi la limite d’étapes seule ne suffit pas.
- Vous pouvez expliquer pourquoi un appel répété après un échec est permis, avec T-90601 comme exemple.
Réinitialisation. Si votre projet ne fonctionne plus, renommez-le et copiez l’instantané end-of-m02, comme l’indique le README des instantanés. Recréez l’environnement virtuel. python -m pytest affiche alors 35 passed (35 tests réussis). L’instantané contient les étapes guidées de ce module, mais pas l’exercice du module.
Vérification des connaissances
Section intitulée « Vérification des connaissances »Vérifiez votre compréhension
Un modèle demande get_order avec LK-000001, puis LK-000002, puis LK-000003. Aucune de ces commandes n'existe. Quelle règle arrête la boucle, après combien d'appels au modèle, et pourquoi pas la règle d'appels répétés?
Afficher la réponse
La règle d’erreurs : too_many_errors après 3 appels au modèle. Chaque appel est refusé (not_found, introuvable). Le nombre d’erreurs passe donc de 1 à 2, puis à 3. La boucle s’arrête à la fin de la troisième étape. La règle d’appels répétés ne s’applique jamais, car les trois appels ont des arguments différents et aucun n’a réussi. Le test test_repeated_errors_end_the_loop vérifie ce cas.
Votre réponse contient-elle les points principaux de la nôtre ?
Connectez-vous et inscrivez-vous au cours pour enregistrer vos réponses. Chaque vérification compte pour le certificat.
Vérifiez votre compréhension
Pourquoi le budget de temps utilise-t-il la latence enregistrée lors d'une relecture, plutôt que le temps réel sur votre ordinateur?
Afficher la réponse
Une relecture répond en quelques millisecondes, donc le temps réel n’atteindrait jamais 120 secondes. Sans modèle actif, vous ne pourriez jamais tester le budget de temps. Avec la latence enregistrée, une relecture attend, sur papier, exactement aussi longtemps que l’exécution réelle. Le budget arrête donc une relecture au même appel que l’exécution réelle. test_the_time_budget le vérifie avec une latence prédéfinie.
Votre réponse contient-elle les points principaux de la nôtre ?
Connectez-vous et inscrivez-vous au cours pour enregistrer vos réponses. Chaque vérification compte pour le certificat.
Vérifiez votre compréhension
Prédisez le résultat : dans l'explorateur, choisissez T-90608 et désactivez la règle d'erreurs. Gardez les autres limites du projet. Où la boucle s'arrête-t-elle, et pourquoi la règle d'appels répétés n'aide-t-elle pas?
Afficher la réponse
À la limite d’étapes : max_steps (maximum d’étapes) après 8 appels au modèle. Le modèle appelle get_customer_orders sans cesse, et le service dépasse le délai chaque fois. La règle d’appels répétés du projet arrête seulement la répétition d’un appel réussi. Aucun de ces appels n’a réussi. Seule la règle d’erreurs, ou la règle plus stricte « tout appel identique », l’arrêterait plus tôt, au troisième appel.
Votre réponse contient-elle les points principaux de la nôtre ?
Connectez-vous et inscrivez-vous au cours pour enregistrer vos réponses. Chaque vérification compte pour le certificat.
Récapitulatif
- Le modèle décide s’il doit s’arrêter, donc le code doit garantir que la boucle se termine. Chaque exécution se termine pour une raison nommée dans
stop_reason(raison de l’arrêt). - Limites de budget, avant chaque appel : 8 appels au modèle, 120 secondes de temps du modèle, 0,05 $ US par tâche. Règles de comportement, après chaque appel : la répétition d’une lecture réussie, et trois erreurs de suite.
- Les valeurs viennent des enregistrements. Aucune exécution Azure n’a nécessité plus de 5 appels, et aucune tâche réelle n’a pris plus de 72,9 secondes. Aucune tâche chat-strong n’a coûté plus d’environ 0,033 $ US.
- Sans les règles, quatre des cinq exécutions réelles de gemma3:4b ne se sont jamais terminées en 12 appels. Avec les règles, elles s’arrêtent après 3 à 5 appels. Le coût : T-80005 s’est arrêté un appel avant de pouvoir terminer.
tests/test_loop.pyprouve l’arrêt avec un modèle qui ne termine jamais, se répète, appelle des outils inconnus, échoue sans cesse ou propose des résolutions invalides. Vos propres tests à scénario prédéfini le prouvent à nouveau.
Vous avez terminé le module 2. Ensuite, l’agent élabore un plan et le modifie quand les éléments de preuve changent.
Leçon suivante : Planifier et réviser