Aller au contenu
astorlm
Langue: Français
← Carte

Niveau 12

Planifier et réfléchir

Écrivez le plan avant de toucher à quoi que ce soit, et vérifiez le travail avant d'accepter la réponse. Le plan garde l'agent sur les rails ; la vérification attrape ce qu'il dit avoir fait sans l'avoir fait.
1/52 Plis du bandonéon :
  • user
  • assistant
  • tool_result
Une tournée de journaux dans la rue Tango. Le kiosque, c'est là où se trouve le modèle, avec la feuille de tournée épinglée à côté : le plan. Le rédacteur en chef, c'est votre code.

EventBus

Le problème

Confiez à un agent un travail en plusieurs parties, et il commence par ce qui se trouve devant lui. À mi-parcours, les premières étapes sont loin dans l'historique, et il en oublie une. À la fin, il répond « fini ! » avec une assurance totale, parce que rien ne l'oblige à regarder.

C'est Scatterbrain. Deux échecs en un : pas de plan, donc des étapes se perdent ; pas de vérification, donc une étape qui a mal tourné est déclarée faite. Dans la rue Tango, l'outil a dit clairement que le journal était tombé dans les buissons. Le modèle l'a lu et a coché la case quand même.

La solution

D'abord, planifier. Avant la première vraie action, l'agent écrit le travail sous forme de liste de tâches, et coche chacune au fur et à mesure. L'astuce qui fait marcher le tout : le plan complet est ajouté à chaque requête, donc le modèle voit toujours ce qui est fait et ce qui reste, quelle que soit la longueur de l'historique. Dans astorlm, c'est pattern: 'PLAN_EXECUTE' : deux outils, add_plan_item et update_plan_item, et le plan dans le system prompt à chaque tour.

Réfléchir avant d'accepter. Une case cochée, ce n'est que ce que dit le modèle. Quand run() rend la main, votre code vérifie le résultat avant de l'accepter. S'il manque quelque chose, le constat repart sous forme de nouveau message dans la même session : l'agent garde son historique et son plan, et ne corrige que ce qui ne va pas. Plafonnez le nombre de rounds. Il y a trois façons de vérifier, de la plus solide à la plus faible :

  • Vérifier le monde

    Votre code regarde le résultat lui-même : les perrons, les lignes de la base de données, la suite de tests, le fichier sur le disque.

    Le meilleur vérificateur quand vous pouvez l'avoir : peu coûteux, exact, et impossible à baratiner. Il faut que le travail soit vérifiable par du code.

  • Un modèle critique

    Un second appel lit la tâche, la réponse et une checklist, et liste ce qui ne va pas ou ce qui manque.

    Pour un travail qu'aucun code ne peut vérifier : un résumé, un e-mail, un plan. Ça coûte un appel, ça peut aussi rater des choses, et il faut des critères concrets, pas « est-ce que c'est bien ? ».

  • Demander à l'agent

    Le system prompt dit à l'agent de relire son travail avant de répondre.

    Gratuit, et parfois suffisant. Mais c'est le même modèle qui se note lui-même, avec les mêmes angles morts : il a déjà coché le n° 14 une fois.

C'est la même idée qu'une évaluation du niveau 11, utilisée à l'exécution : une évaluation note des exécutions après coup pour améliorer l'agent ; une vérification note cette exécution-ci avant que l'utilisateur ne la voie.

Les personnages

Les mêmes personnages que d'habitude, cette fois sur une tournée de journaux.

Le kiosque le modèle
L'Oracle, derrière le comptoir. Il décide de chaque étape, et ne pédale jamais.
La feuille de tournée le plan
Les tâches et leurs cases. Elle s'illumine en doré à chaque tour : elle part avec chaque requête.
Le vélo d'Astor la boucle
Il porte chaque appel d'outil à l'aller et au retour, avec l'historique dans son bandonéon.
Un lancer deliver
Un outil ordinaire. Il dit où le journal a atterri.
Le rédacteur en chef votre code
Il confie le travail, et inspecte les perrons avant d'accepter la réponse. Sa loupe, c'est review().

Le code

Avec astorlm : pattern: 'PLAN_EXECUTE' ajoute les outils du plan et met le plan dans chaque requête ; getPlan() le relit. La vérification est du code ordinaire après run(), et un second run() sur le même agent poursuit la même session.

À partir de zéro : Une liste, deux outils qui la modifient, et un system prompt reconstruit avec la liste à chaque tour. L'historique vit en dehors de run(), donc la correction poursuit la même conversation.

import { OpenAIProvider, createLocalAgent, tool } from 'astorlm'
import { z } from 'zod'

// Any OpenAI-compatible endpoint: OpenAI, Ollama, LM Studio, vLLM, a proxy…
const LLM = { baseURL: 'http://localhost:11434/v1', apiKey: 'YOUR_API_KEY' } // local servers usually ignore the key

const SUBSCRIBERS = [12, 14, 18]
const porches = new Set<number>() // the real world: which porches have a paper

const deliver = tool({
  name: 'deliver',
  description: 'Ride to a house and throw today’s paper onto its porch. Says where the paper landed.',
  schema: z.object({ house: z.number() }),
  execute: async ({ house }) => {
    const landed = throwPaper(house) // your code: 'porch' or 'bushes'
    if (landed === 'porch') porches.add(house)
    return landed === 'porch' ? `Paper on the porch at #${house}.` : `Paper landed in the bushes at #${house}.`
  },
})

// PLAN: the agent gets add_plan_item and update_plan_item,
// and the current plan is added to the system prompt on every turn.
const agent = await createLocalAgent({
  provider: new OpenAIProvider({ ...LLM, model: 'your-model' }), // e.g. 'llama3.1', 'gpt-4o-mini'
  pattern: 'PLAN_EXECUTE',
  systemPrompt: 'You deliver newspapers. Plan every stop before you start, and tick each task as you go.',
  tools: [deliver],
  maxTurns: 20,
})

// REFLECT: check the work itself before accepting the answer. Deterministic when you can;
// a second model with a rubric when you can't.
const review = (): string[] => SUBSCRIBERS.filter((house) => !porches.has(house)).map((house) => `#${house} has no paper on the porch`)

let answer = await agent.run(`Deliver today’s paper to every subscriber on Tango Street: ${SUBSCRIBERS.join(', ')}.`)
for (let round = 1; round <= 2; round++) {
  const problems = review()
  if (problems.length === 0) break
  // Same agent, same session: it keeps its history and its plan, and fixes what's missing.
  answer = await agent.run(`Review found: ${problems.join('; ')}. Fix it.`)
}

console.log(agent.getPlan()) // [{ id: '1', description: 'Deliver to #12', status: 'completed' }, …]
console.log(answer.content)

Points de vigilance

  • Gardez des tâches petites et vérifiables. « Livrer au n° 14 » se vérifie ; « s'occuper de la rue », non. Une tâche que vous pouvez vérifier est une tâche que la vérification peut attraper.
  • Laissez le plan évoluer. Les plans se heurtent à la réalité : une rue est fermée, un client annule. L'agent doit pouvoir ajouter, retirer ou réordonner des tâches, pas suivre une liste périmée.
  • Vérifiez le monde, pas le plan. La feuille affichait trois coches. Vérifier la feuille aurait réussi. La vérification doit regarder le résultat lui-même.
  • Plafonnez les rounds. Une vérification qui ne peut jamais passer, ou un agent incapable de corriger ce qu'il trouve, tourne en boucle indéfiniment. Deux ou trois rounds, puis passez la main à une personne (niveau 13).
  • Ne planifiez pas une tâche d'une ligne. Planifier coûte des tours et des tokens. Pour une simple recherche, passez-vous-en. Ça vaut le coup quand le travail a plusieurs étapes faciles à perdre.