Aller au contenu
astorlm
Langue: Français
← Carte

Niveau 4

Quand s'arrêter

Une boucle d'agent continue tant que le modèle demande des outils. Il y a trois sorties : le modèle dit qu'il a fini, un outil que vous avez marqué ferme la boucle, ou le budget de tours est épuisé. Seules les deux premières vous donnent une réponse.
1/42 Plis du bandonéon :
  • user
  • assistant
  • tool_result
World 1-1. Ce niveau n'a aucune étoile, mais le modèle va quand même continuer à chercher. L'horloge, c'est maxTurns : 3 tours pour cette exécution.

EventBus

Le problème

La boucle du niveau 2 a une sortie normale : le modèle répond sans demander d'outil. Mais un modèle qui ne trouve pas ce qu'il cherche le dit rarement. Il essaie une autre recherche, puis une autre orthographe, puis une autre encore. Chaque tour renvoie tout l'historique, donc chaque tour coûte plus que le précédent.

C'est Loopboros, la boucle qui ne finit jamais. maxTurns l'arrête. Mais regardez à nouveau le monde 1-1 : quand l'horloge s'épuise, la boucle n'échoue pas. Elle s'arrête et vous rend son dernier message, et ce message est une demande de recherche. Ni réponse, ni erreur.

Les trois sorties

  • Le drapeau

    end_turn

    World 1-2

    Le modèle décide. Il répond sans demander d'outil. C'est la sortie normale, et la plupart des exécutions devraient finir ici.

    Renvoie : Un message avec la réponse en texte.

  • Le tuyau de raccourci

    stopOnToolNames

    World 1-3

    Votre code décide, quand le modèle appelle un outil que vous avez marqué. La boucle se ferme juste après que cet outil s'est exécuté sans erreur. S'il échoue, l'erreur revient au modèle et la boucle continue.

    Renvoie : Un message avec l'appel d'outil. Son entrée est la réponse, déjà vérifiée par le schéma.

  • L'horloge

    maxTurns

    World 1-1

    Personne ne décide. Le budget est épuisé. La boucle s'arrête après ce nombre de tours, quoi que le modèle soit en train de faire. C'est un filet de sécurité, pas une réponse.

    Renvoie : Le dernier message, quel qu'il soit. Souvent un appel d'outil.

Un outil terminal ne vous fait pas gagner un tour par rapport à end_turn. Ce qu'il vous donne, c'est la réponse sous forme de données, vérifiées par un schéma, prêtes à être utilisées par votre app : ici, un marqueur sur la carte du niveau. Sans stopOnToolNames, après l'exécution de mark_star, la boucle redemanderait au modèle juste pour qu'il dise « terminé ».

Le code

Avec astorlm : maxTurns règle l'horloge et stopOnToolNames marque les tuyaux de raccourci. run() renvoie toujours le dernier message : lisez-le pour savoir par quelle sortie l'exécution s'est terminée.

À partir de zéro : La boucle du niveau 2 avec les trois sorties signalées. Au lieu d'une simple chaîne, elle renvoie comment l'exécution s'est terminée, pour que l'appelant ne confonde pas un timeout avec une réponse.

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

const searchBlocks = tool({
  name: 'search_blocks',
  description: 'Search the ? blocks in one area of the level. Returns what each one hides.',
  schema: z.object({ area: z.string() }),
  execute: async ({ area }) => level.search(area), // your code
})

// The answer as data. Its schema is checked before the loop is allowed to stop.
const markStar = tool({
  name: 'mark_star',
  description: 'Put a marker on the level map where the star is. Call it once, at the end.',
  schema: z.object({ item: z.literal('star'), block: z.number().int().positive() }),
  execute: async ({ block }) => map.addMarker(block), // your code
})

const agent = await createLocalAgent({
  // Any OpenAI-compatible endpoint: OpenAI, Ollama, LM Studio, vLLM, a proxy…
  provider: new OpenAIProvider({
    baseURL: 'http://localhost:11434/v1', // e.g. Ollama's default address
    model: 'your-model', // e.g. 'llama3.1', 'gpt-4o-mini'
    apiKey: 'YOUR_API_KEY', // local servers usually ignore it
  }),
  tools: [searchBlocks, markStar],
  appendSystemPrompt:
    'When you know where the star is, deliver it with mark_star. ' +
    "If a few searches come back empty, say you couldn't find it.",
  maxTurns: 10, // the clock. Without it, the default is 25.
  stopOnToolNames: ['mark_star'], // the warp pipe
})

const last = await agent.run('Is there a hidden star in this level?')

// The loop hands back its last message whichever way it ended. Read it to find out which.
const call = last.content.find((block) => block.type === 'tool_use')
if (!call) {
  console.log('end_turn:', last.content) // the flag: a plain text answer
} else if (call.name === 'mark_star') {
  console.log('answer:', call.input) // the warp pipe: { item, block }, schema-checked
} else {
  // The clock: maxTurns ran out while the model was still asking for tools.
  throw new Error(`No answer: the run stopped while asking for ${call.name}`)
}

Points de vigilance

  • Définissez toujours maxTurns, et dimensionnez-le selon la tâche. Une recherche demande une poignée de tours ; un refactoring peut en demander des dizaines. La valeur par défaut dans astorlm est 25.
  • Traitez l'horloge comme un échec. Si l'exécution s'est terminée par un appel d'outil, dites à l'utilisateur que vous n'avez pas pu finir, ou réessayez avec un prompt plus clair. Ne lui montrez pas une réponse vide.
  • Donnez au modèle un moyen d'abandonner. Dites-lui dans le system prompt de répondre « je ne l'ai pas trouvé » quand une recherche revient vide encore et encore. Un modèle qui a le droit de s'arrêter s'arrête plus tôt.
  • Des tours, ce n'est pas du temps. maxTurns ne sert à rien face à un seul tour qui ne finit jamais. Pour cela, il vous faut un timeout ou un AbortSignal.