Aller au contenu principal
Leçon 4 sur 14 · Computer Use et Automatisation

Premier workflow : naviguer et interagir

Leçon 4 sur 14 Mis à jour le 3 novembre 2026 Développeur
Sur cette page
  1. Assembler les briques
  2. Le gestionnaire d’actions
  3. La boucle complète
  4. Lancer le workflow
  5. Une fonction réutilisable pour la suite du cours
  6. Trois pannes que vous rencontrerez
  7. Points clés à retenir

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 computer de la Responses API, un gestionnaire d’actions calqué sur celui de la documentation (actions groupées, keypress, scroll_x/scroll_y, chemins de drag) et un chaînage par previous_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_output de même call_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 utilisez headless=False pendant 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