L'agent appelle votre endpoint de transcodage vidéo. L'endpoint renvoie 202 Accepted et un ID de tâche. L'agent, qui n'a aucune idée de ce que signifie 202 dans votre système, signale que le transcodage est terminé et passe à l'étape suivante, qui lit un fichier qui n'existe pas encore.
Les opérations de longue durée perturbent les agents d'une manière spécifique. Un appel synchrone a un contrat évident : vous envoyez, vous attendez, vous obtenez une réponse. Un appel asynchrone divise cela en un début et une fin, et l'intervalle entre les deux est là où les agents se perdent. Ils déclarent le succès prématurément, ils interrogent mille fois dans une boucle serrée, ou ils restent bloqués pendant six minutes, maintenant une conversation ouverte.
Ce guide explique comment concevoir le contrat asynchrone pour qu'un agent puisse le suivre, quand interroger et quand passer le relais, comment écrire les outils pour que le modèle se comporte correctement, et comment tester le chemin complet, y compris les cas lents et les échecs. Notre article sur la récupération d'erreurs d'agent IA couvre le côté échec des appels d'API ; celui-ci couvre ceux qui réussissent lentement.
Apidog intervient au moment où vous devez prouver que l'agent gère une tâche qui prend quatre minutes et échoue ensuite, ce qui n'est pas quelque chose que vous voulez découvrir en production.
Pourquoi les agents gèrent mal l'asynchrone
Trois habitudes sont à l'origine de la plupart des problèmes.
Les modèles traitent un code 2xx comme une tâche terminée. Un code 202 indique que la requête a été acceptée pour traitement, et la spécification sémantique HTTP stipule explicitement que le traitement peut ne pas être terminé. Les modèles entraînés sur un trafic de requête/réponse ordinaire ont tendance à interpréter tout code 2xx comme une complétion, à moins que la réponse n'indique le contraire explicitement.
Les boucles sont coûteuses. Si un agent interroge à l'intérieur de sa boucle de raisonnement, chaque vérification coûte un tour de modèle plus les jetons de la conversation précédente. Interroger toutes les deux secondes pour une tâche de quatre minutes représente 120 tours, et l'exécution épuise soit le contexte, soit le budget. Notre article sur la façon de garder les réponses des outils hors de la fenêtre de contexte explique pourquoi cela s'accumule plus vite que prévu.
Les agents perdent le fil des tâches. Un outil qui démarre un travail et renvoie un ID de tâche a créé un état que l'agent doit conserver. Si l'ID se retrouve au milieu d'une longue conversation, il peut être compacté et l'agent oublie qu'il a une tâche en cours.
Concevez la réponse de manière à ce que le modèle ne puisse pas la mal interpréter
La solution la plus efficace est la formulation, pas l'architecture. Quel que soit votre code de statut, faites en sorte que le corps indique clairement ce qui s'est passé et ce qu'il faut faire ensuite.
{
"status": "processing",
"job_id": "job_7f21c",
"message": "The transcode has STARTED and is NOT complete. Do not report success. Check status with getJobStatus(job_id) after at least 30 seconds.",
"poll_after_seconds": 30,
"estimated_duration_seconds": 240,
"status_url": "/v1/jobs/job_7f21c"
}
Cela peut sembler lourd pour un consommateur d'API humain. C'est destiné à un modèle, et les modèles suivent les instructions explicites dans le corps d'une réponse bien plus fidèlement qu'ils n'infèrent le sens d'un code de statut. Trois détails font le travail : le mot « non terminé », l'outil suivant nommé et un délai d'attente minimum.
L'AIP-151 de Google sur les opérations de longue durée décrit une forme de ressource claire pour cela, avec un seul objet Operation contenant les champs done, error et response. Copier cette structure vous offre une surface cohérente sur chaque endpoint lent, ce qui est important car un agent qui apprend un modèle d'interrogation peut ensuite les gérer tous.
Gardez la réponse de statut tout aussi directe :
{
"job_id": "job_7f21c",
"status": "processing",
"done": false,
"progress_percent": 45,
"elapsed_seconds": 108,
"poll_after_seconds": 45,
"message": "Still processing. Do not proceed to the next step."
}
Et à la fin, renvoyez le résultat directement lorsqu'il est petit, afin que l'agent n'ait pas besoin d'un troisième appel :
{
"job_id": "job_7f21c",
"status": "succeeded",
"done": true,
"result": { "output_url": "https://cdn.example.com/out/7f21c.mp4", "duration_seconds": 372 }
}
Interrogez en dehors du modèle, pas à l'intérieur
Le choix d'implémentation le plus important : placez l'attente dans votre enveloppeur d'outil (tool wrapper), et non dans la boucle de raisonnement de l'agent.
import time
def start_and_await_transcode(client, source_url, max_wait=600):
job = client.post("/v1/transcode", json={"source_url": source_url}).json()
job_id = job["job_id"]
delay = job.get("poll_after_seconds", 5)
waited = 0
while waited < max_wait:
time.sleep(delay)
waited += delay
status = client.get(f"/v1/jobs/{job_id}").json()
if status.get("done"):
if status["status"] == "succeeded":
return {"status": "succeeded", "result": status["result"]}
return {"status": "failed", "error": status.get("error")}
delay = min(int(delay * 1.5), 60)
return {
"status": "timed_out",
"job_id": job_id,
"message": f"Still running after {max_wait}s. Job {job_id} continues in the background.",
}
Du côté du modèle, il s'agit d'un appel d'outil unique qui prend un certain temps et renvoie une réponse finale. Pas de boucle d'interrogation dans le contexte, pas d'ID de tâche oubliés, pas de 120 tours. Le backoff maintient le nombre de requêtes raisonnable, et le plafond empêche une tâche bloquée de suspendre l'exécution indéfiniment. L'article d'Amazon sur les timeouts, les tentatives et le backoff avec jitter est la référence à lire avant d'ajuster ces chiffres.
Deux règles rendent cela sûr. Limitez toujours le temps d'attente, et renvoyez toujours l'ID de la tâche en cas de timeout afin que l'agent ou un humain puisse vérifier plus tard. Ne renvoyez jamais un résultat ambigu : succeeded, failed et timed_out sont trois résultats différents et le modèle devrait voir trois mots différents.
Pour les tâches mesurées en heures plutôt qu'en minutes, l'interrogation intégrée au wrapper n'a plus de sens. La bonne approche est alors de disposer de deux outils, un pour démarrer et un pour vérifier, plus un enregistrement durable des tâches en cours en dehors de la conversation afin que rien ne soit perdu par la compaction. Stockez l'job_id, la tâche à laquelle il appartient et l'heure de début, et demandez à l'agent de lire cette liste au début de chaque exécution.
Quand les webhooks sont la meilleure solution
L'interrogation est simple et fonctionne partout. Les callbacks sont plus efficaces et demandent plus de travail à gérer. Le compromis est bien couvert dans notre comparaison webhooks vs polling, et la version spécifique aux agents est plus restreinte.
Utilisez l'interrogation lorsque la tâche prend de quelques secondes à quelques minutes, lorsque l'agent attend le résultat pour continuer, ou lorsque vous ne pouvez pas héberger un endpoint public. La plupart des charges de travail des agents se situent ici.
Utilisez les webhooks lorsque les tâches prennent des heures, lorsque l'agent lance un travail et passe à autre chose, ou lorsque de nombreuses tâches s'exécutent simultanément et que l'interrogation de chacune est inefficace. Le coût est réel : vous avez besoin d'un récepteur public, d'une vérification de signature, d'une gestion des tentatives et d'un moyen de réveiller l'agent lorsque le callback arrive. Nos guides sur la conception de webhooks fiables et la vérification de signature de webhook couvrent ce travail préparatoire.
Une option intermédiaire mérite d'être connue. La diffusion du progrès de la tâche via des événements envoyés par le serveur (server-sent events) vous offre une sémantique de poussée sans endpoint public, puisque le client maintient la connexion. Cela convient aux agents interactifs où un humain observe, et notre guide sur la diffusion de réponses API avec SSE couvre l'implémentation.
Quelle que soit l'option choisie, le chemin de complétion doit être idempotent. Les webhooks réessaient, les interrogations se concurrencent, et un agent qui voit « réussi » deux fois ne devrait pas démarrer l'étape suivante deux fois. Notre article sur les clés d'idempotence pour les agents IA couvre les clés qui garantissent cette sécurité.
Testez le chemin lent, pas seulement le rapide
Les bugs asynchrones se cachent parce que les environnements de test sont rapides. Une tâche qui prend quatre minutes en production se termine en 200 millisecondes contre un stub local, de sorte que l'agent n'expérimente jamais l'état qu'il rencontrera réellement.
Quatre scénarios méritent d'être construits délibérément.
- La tâche réellement lente. Simulez (mockez) l'endpoint de statut pour qu'il renvoie
processingpour les premiers appels etsucceededaprès cela. Cela prouve que l'enveloppeur interroge, applique un backoff et finit par renvoyer un résultat. Dans Apidog, vous pouvez gérer cela avec un mock qui varie en fonction du nombre de requêtes ou d'un paramètre de contrôle, afin que le même test s'exécute de la même manière à chaque fois.

La tâche qui échoue tardivement. Renvoie processing trois fois, puis failed avec un corps d'erreur. L'agent doit signaler l'échec plutôt que de considérer un sondage terminé comme une tâche terminée. C'est le cas qui produit une perte de données silencieuse lorsqu'il est incorrect.
Le timeout. Maintenez le mock à renvoyer processing au-delà du plafond de l'enveloppeur et assurez-vous que l'outil renvoie timed_out avec l'ID de tâche intact, et non une exception ni un faux succès.
La complétion en double. Livrez le succès deux fois, par re-tentative de webhook ou par une interrogation concurrente, et assurez-vous que l'étape suivante ne s'exécute qu'une seule fois.
Enregistrez les quatre comme scénarios pour qu'ils s'exécutent en CI. Ils ne coûtent rien à réexécuter et ils détectent la régression où quelqu'un raccourcit un timeout ou ignore une erreur. L'approche plus large se trouve dans notre guide de test de contrat d'API.
Trois types de tâches qui exposent le problème
- Génération de rapports. Un agent financier demande une exportation trimestrielle. Cela prend 90 secondes. Avec un outil naïf, l'agent obtient un ID de tâche, annonce que le rapport est prêt, puis fournit un lien de téléchargement cassé à l'utilisateur. Avec un enveloppeur bloquant, il attend 90 secondes et renvoie la vraie URL. Même API, résultats opposés, et la seule différence est l'endroit où l'attente se produit.
- Importations en masse. Un agent d'opérations télécharge 20 000 enregistrements. L'importation s'exécute pendant huit minutes et échoue partiellement à la ligne 14 000. C'est le cas qui sanctionne une vérification de succès naïve : la tâche est terminée, donc un statut
doneest vrai, mais le résultat contient une liste de lignes rejetées. Retournez les résultats partiels explicitement, avec des décomptes, et faites en sorte que l'agent les lise avant de passer à l'étape suivante. - Pipelines de modèle et de build. Un agent déclenche une exécution d'entraînement ou un build CI qui prend 40 minutes. L'interrogation intégrée au wrapper n'est pas la bonne approche ici ; l'exécution maintiendrait un tour ouvert beaucoup trop longtemps. Démarrez la tâche, enregistrez l'ID dans un stockage durable, terminez le tour, et laissez une vérification planifiée ou un callback déclencher le suivi. Notre article sur le transfert inter-agents et le passage de contexte couvre le déplacement de cet état entre les exécutions sans le perdre.
Donnez une forme aux résultats partiels
Les tâches longues se terminent souvent quelque part entre le succès et l'échec, et un modèle à deux états vous force à mentir à ce sujet. Rendez le troisième état explicite :
{
"job_id": "job_a11f",
"status": "completed_with_errors",
"done": true,
"summary": { "processed": 20000, "succeeded": 19860, "failed": 140 },
"errors_url": "/v1/jobs/job_a11f/errors?limit=50",
"message": "Import finished. 140 rows failed and were not written. Review errors before reporting success."
}
Deux choses importent dans cette charge utile. Les décomptes sont en ligne, de sorte que l'agent peut décider sans autre appel. Les lignes défectueuses sont derrière une URL avec une limite, de sorte que 140 objets d'erreur n'atterrissent pas dans le contexte sans y être invités.
Quelqu'un doit voir la tâche qui a calé
Le chemin de timeout se termine par un ID de tâche et un message indiquant que le travail est toujours en cours. C'est la valeur de retour correcte, et elle n'est utile que si elle parvient à une personne.
Lorsque l'agent est votre propre service, acheminez-le vers la file d'attente que votre équipe surveille déjà. Lorsque l'agent est un runtime de codage exécutant des tâches assignées, la plateforme qui l'exécute a généralement un endroit où cela peut atterrir. Dans Sharkly, une exécution qui se termine bloquée reste sur sa Tâche avec son état d'exécution et son résultat, et la Boîte de réception sépare les éléments qui nécessitent une réponse ou une révision humaine des mises à jour ordinaires. Le point n'est pas l'outil spécifique. C'est que « toujours en cours, vérifier plus tard » a besoin d'un responsable, sinon cela devient « personne n'a vérifié ».

Une courte liste de vérification
- Chaque endpoint lent renvoie un ID de tâche, une URL de statut et un message en langage clair indiquant que le travail n'est pas terminé.
- Les réponses de statut contiennent un champ booléen
done, et non une simple chaîne que le modèle doit interpréter. - L'interrogation réside dans l'enveloppeur d'outil avec un backoff exponentiel et un plafond fixe.
- Les timeouts renvoient l'ID de la tâche afin que le travail puisse être repris plutôt que perdu.
- Le succès, l'échec et le timeout sont trois valeurs de retour distinctes.
- Les tâches en cours sont enregistrées en dehors de la conversation pour toute durée supérieure à quelques minutes.
- La gestion de la complétion est idempotente, que le signal arrive par interrogation ou par callback.
- Les complétions lentes, en échec tardif, en timeout et dupliquées ont toutes des tests enregistrés.
Si la formulation de la réponse et l'enveloppeur sont corrects, les opérations de longue durée cessent d'être un cas particulier pour l'agent. Il appelle un outil, attend et obtient une réponse, ce qui est le contrat qu'il gère le mieux. Téléchargez Apidog pour créer les mocks de tâches lentes en même temps que les tests.
Questions fréquemment posées
L'API doit-elle renvoyer 202 ou 200 pour un démarrage asynchrone ? 202 Accepted est le code honnête et signale aux clients standards que le traitement n'est pas terminé. Ne vous fiez pas uniquement à lui pour les agents, car le corps est ce que le modèle lit le plus fiablement. Utilisez les deux.
Combien de temps l'enveloppeur d'outil doit-il attendre avant d'abandonner ? Définissez le plafond légèrement au-dessus du pire cas réaliste de l'endpoint, généralement de deux à dix minutes. Au-delà, l'enveloppeur bloque une tour de conversation trop longtemps, et un outil de vérification ultérieure est une meilleure approche.
Quel intervalle d'interrogation dois-je utiliser ? Partez de l'indication poll_after_seconds du serveur s'il en fournit une, puis appliquez un backoff d'environ 1,5 avec un plafond d'environ 60 secondes. Une interrogation fixe d'une seconde gaspille des requêtes et peut déclencher des limites de débit, comme expliqué dans notre guide sur les dépassements de limites de débit.
L'agent peut-il faire quelque chose d'utile pendant qu'il attend ? Seulement si votre orchestrateur prend en charge les appels d'outils concurrents. Si c'est le cas, démarrez la tâche, effectuez le travail indépendant, puis vérifiez le statut. Si ce n'est pas le cas, l'enveloppeur bloquant est plus simple et moins sujet aux erreurs qu'un ordonnanceur fait à la main.
Comment empêcher l'agent de déclarer un succès prématurément ? Dites-le explicitement dans le corps de la réponse, exposez un champ booléen done et faites de l'outil de complétion le seul endroit où un résultat apparaît. Si la réponse de démarrage ne contient aucun résultat, il n'y a rien que le modèle puisse signaler comme étant un résultat.
Les webhooks fonctionnent-ils pour les agents exécutés sur un ordinateur portable ? Pas directement, puisqu'il n'y a pas d'endpoint public. Utilisez un tunnel pour le développement, comme dans notre guide sur le test d'API localhost avec des services de webhook, ou tenez-vous-en à l'interrogation jusqu'à ce que l'agent s'exécute dans un environnement accessible.
