Premier workflow : naviguer et interagir
Sur cette page
Leçon réécrite le 3 novembre 2026 d’après le guide Computer use et les Computer use integration recipes d’OpenAI : l’agent utilise l’outil
computerde la Responses API, un gestionnaire d’actions calqué sur celui de la documentation (actions groupées,keypress,scroll_x/scroll_y, chemins dedrag) et un chaînage parprevious_response_id, à la place du code écrit pour l’API d’Anthropic. Sources : Computer use · Integration recipes.
Assembler les briques
Vous avez la boucle en tête et l’environnement configuré. Reste à les réunir en un programme qui tourne vraiment : un agent qui ouvre une encyclopédie en ligne, cherche un terme, ouvre le premier résultat et vous rapporte le titre de la page atteinte. Le scénario paraît modeste, mais il mobilise les gestes fondamentaux — navigation, frappe, clic, lecture d’information — et tout ce que vous construirez ensuite n’en sera qu’une variation plus longue.
Le gestionnaire d’actions
Le modèle décide, mais il n’exécute rien lui-même : il renvoie une liste d’actions et c’est votre code qui la traduit en gestes Playwright, dans l’ordre. Cette fonction suit le gestionnaire publié par OpenAI dans ses recettes d’intégration, simplifié pour un navigateur.
import time
TOUCHES = {
"ENTER": "Enter", "RETURN": "Enter", "ESC": "Escape", "ESCAPE": "Escape",
"TAB": "Tab", "SPACE": "Space", "BACKSPACE": "Backspace", "DELETE": "Delete",
"UP": "ArrowUp", "DOWN": "ArrowDown", "LEFT": "ArrowLeft", "RIGHT": "ArrowRight",
"CTRL": "Control", "CONTROL": "Control", "SHIFT": "Shift", "ALT": "Alt",
"CMD": "Meta", "META": "Meta",
}
BOUTONS = {"left": "left", "right": "right", "wheel": "middle"}
def executer_actions(page, actions):
"""Exécute dans l'ordre les actions d'un computer_call."""
for action in actions:
if action.type in ("click", "double_click", "drag", "move", "scroll") and getattr(action, "keys", None):
raise ValueError("Action souris avec touche modificatrice : non gérée ici")
match action.type:
case "click":
bouton = getattr(action, "button", "left")
if bouton not in BOUTONS: # back et forward n'existent pas dans Playwright
raise ValueError(f"Bouton non pris en charge : {bouton}")
page.mouse.click(action.x, action.y, button=BOUTONS[bouton])
case "double_click":
page.mouse.dblclick(action.x, action.y)
case "drag":
points = [(p.x, p.y) for p in action.path]
page.mouse.move(*points[0])
page.mouse.down()
for x, y in points[1:]:
page.mouse.move(x, y)
page.mouse.up()
case "move":
page.mouse.move(action.x, action.y)
case "scroll":
page.mouse.move(action.x, action.y)
page.mouse.wheel(action.scroll_x, action.scroll_y)
case "keypress":
page.keyboard.press("+".join(TOUCHES.get(k, k) for k in action.keys))
case "type":
page.keyboard.type(action.text)
case "wait":
time.sleep(2)
case "screenshot":
continue # la capture est prise après chaque lot d'actions
case _:
raise ValueError(f"Action non prise en charge : {action.type}")
Deux choix méritent d’être expliqués. Les noms de touches et de boutons renvoyés par le modèle (ENTER, CTRL, wheel…) ne sont pas ceux qu’attend Playwright (Enter, Control, middle…) : les tables de correspondance, reprises de la documentation, font le pont, et les boutons back et forward, que l’API peut aussi demander, sont refusés comme dans le gestionnaire d’OpenAI. Et une action inconnue lève une erreur plutôt que d’être ignorée en silence : mieux vaut un arrêt net qu’un agent qui croit avoir agi.
La boucle complète
L’agent orchestre ensuite le cycle. Le premier appel envoie la tâche ; s’il ne contient qu’une demande de capture, on renvoie simplement l’écran. Chaque capture revient au modèle comme résultat de l’appel qui l’a demandée, référencé par son call_id, et previous_response_id relie les tours entre eux.
import base64
from openai import OpenAI
from playwright.sync_api import sync_playwright
client = OpenAI()
MODELE = "gpt-6.1-sol"
MAX_TOURS = 15
def lancer_agent(tache: str, url_depart: str = "https://fr.wikipedia.org"):
"""Lance un agent Computer Use sur une tâche donnée."""
pw = sync_playwright().start()
browser = pw.chromium.launch(headless=False) # False pour observer
page = browser.new_page(viewport={"width": 1280, "height": 720})
page.goto(url_depart)
page.wait_for_load_state("networkidle")
try:
response = client.responses.create(
model=MODELE, tools=[{"type": "computer"}], input=tache,
)
for tour in range(MAX_TOURS):
appel = next((i for i in response.output if i.type == "computer_call"), None)
if appel is None:
print(f"Tâche terminée : {response.output_text}")
return response.output_text
if appel.pending_safety_checks: # voir la leçon 12
raise RuntimeError("Vérification de sécurité en attente : intervention humaine requise")
for action in appel.actions:
print(f" Action : {action.type}")
executer_actions(page, appel.actions)
capture = base64.b64encode(page.screenshot()).decode("utf-8")
response = client.responses.create(
model=MODELE,
tools=[{"type": "computer"}],
previous_response_id=response.id,
input=[{
"type": "computer_call_output",
"call_id": appel.call_id,
"output": {
"type": "computer_screenshot",
"image_url": f"data:image/png;base64,{capture}",
"detail": "original",
},
}],
)
raise RuntimeError("Nombre maximal de tours atteint : vérifiez l'état de la page")
finally:
browser.close()
pw.stop()
Trois garde-fous sont déjà en place. MAX_TOURS borne la boucle : sans cette limite, un agent désorienté consommerait votre budget jusqu’à épuisement. Un appel qui porte des vérifications de sécurité en attente (pending_safety_checks) arrête tout, comme dans l’exemple officiel d’OpenAI : la leçon 12 montre comment les faire valider par un humain. Et la fin normale se lit à l’absence de computer_call : le modèle a cessé d’agir et vous répond, et c’est dans output_text que vous récupérerez le titre de la page.
Lancer le workflow
if __name__ == "__main__":
lancer_agent(
"Cherche « Computer Use » dans le moteur de recherche de Wikipédia, "
"ouvre le premier résultat et donne-moi le titre de la page. "
"Utilise l'outil computer pour agir sur l'interface."
)
Avec headless=False, le spectacle est instructif : la fenêtre s’ouvre, le clic se pose dans le champ de recherche, le texte s’affiche, le premier lien s’ouvre et le titre remonte dans votre console. Regardez-le tourner deux ou trois fois avant de passer à des tâches plus ambitieuses ; vous y verrez précisément où l’agent hésite et sur quoi porter vos consignes.
Une fonction réutilisable pour la suite du cours
Les leçons suivantes travaillent sur une page déjà ouverte — après une connexion, au milieu d’un workflow. Elles s’appuient sur run_agent(tache, page=page) : la même boucle, sortie de lancer_agent, avec des instructions facultatives renvoyées à chaque tour (la leçon 5 explique pourquoi).
def run_agent(tache: str, page, instructions: str | None = None, max_tours: int = MAX_TOURS) -> str:
"""Boucle Computer Use sur une page déjà ouverte ; renvoie le message final."""
requete = {"model": MODELE, "tools": [{"type": "computer"}]}
if instructions:
requete["instructions"] = instructions # à renvoyer à chaque requête
response = client.responses.create(**requete, input=tache)
for tour in range(max_tours):
appel = next((i for i in response.output if i.type == "computer_call"), None)
if appel is None:
return response.output_text
if appel.pending_safety_checks:
raise RuntimeError("Vérification de sécurité en attente : intervention humaine requise")
executer_actions(page, appel.actions)
capture = base64.b64encode(page.screenshot()).decode("utf-8")
response = client.responses.create(
**requete,
previous_response_id=response.id,
input=[{
"type": "computer_call_output",
"call_id": appel.call_id,
"output": {
"type": "computer_screenshot",
"image_url": f"data:image/png;base64,{capture}",
"detail": "original",
},
}],
)
raise RuntimeError("Nombre maximal de tours atteint : vérifiez l'état de la page")
Trois pannes que vous rencontrerez
L’agent clique systématiquement à côté de sa cible ? Le coupable est presque toujours un écart d’échelle entre la capture envoyée et la page sur laquelle vous cliquez : capture redimensionnée, zoom du navigateur, écran haute densité. Les coordonnées du modèle se rapportent à l’image qu’il a reçue ; si vous la réduisez, convertissez ses coordonnées en sens inverse.
L’agent tourne en rond et refait le même geste ? La borne de tours finira par le stopper, mais mieux vaut détecter la boucle plus tôt : si la même capture revient plusieurs fois de suite, l’agent n’avance plus, et l’interrompre vous épargne des appels inutiles.
L’agent agit sur une page encore vide ? Sur un site lourd, page.goto() rend la main avant la fin du rendu, et la première capture ne montre qu’un fond blanc. Attendez l’inactivité réseau avant de photographier :
page.goto(url)
page.wait_for_load_state("networkidle")
Points clés à retenir
- Un agent Computer Use combine un gestionnaire d’actions, une boucle de contrôle et le chaînage des réponses
- Le premier appel envoie la tâche ; chaque capture revient dans un
computer_call_outputde mêmecall_id - Le gestionnaire exécute les actions dans l’ordre et refuse ce qu’il ne sait pas faire
- Limitez toujours le nombre de tours, arrêtez-vous sur les
pending_safety_checks, et utilisezheadless=Falsependant le développement run_agent(tache, page=page)reprend cette boucle pour une page déjà ouverte ; c’est elle qu’utilisent les leçons suivantes