DanLevy.net

Arrêtez de construire des agents instables : utilisez des workflows et de la mémoire

Des motifs déterministes pour des modèles non déterministes.

Les LLM ont cette propriété étrange : ils brillent pour comprendre les nuances mais sont catastrophiques pour suivre des recettes. Donnez un problème vague à GPT-4 et il raisonnera sur les possibilités. Donnez-lui une séquence précise d’étapes, et il pourrait sauter l’étape 3 parce que l’étape 5 « semblait plus pertinente ».

Ce n’est pas un bug du modèle. C’est une caractéristique fondamentale des systèmes probabilistes qui tentent de résoudre des problèmes déterministes.

J’ai vu des équipes se débattre avec ce décalage. Elles construisent un agent pour gérer les remboursements clients, lui donnent une douzaine d’outils, et s’attendent à ce qu’il exécute fiablement un processus métier. Parfois ça marche parfaitement. Parfois il hallucine des approbations qui n’ont jamais eu lieu. Parfois il reste bloqué à demander la même information trois fois.

La solution, ce n’est pas de meilleurs prompts. C’est savoir quand arrêter de demander au LLM de « réfléchir » et commencer à lui dire d’« obéir ».


Quand le déterminisme l’emporte sur la créativité

Pensez à ce qui se passe quand vous devez traiter un ticket support. La logique métier réelle ressemble à quelque chose comme :

  1. Récupérer les détails du ticket depuis la base de données
  2. Vérifier si l’utilisateur est éligible à un remboursement (règles de politique)
  3. Vérifier que la transaction existe et n’a pas déjà été remboursée
  4. Calculer le montant du remboursement
  5. Traiter l’annulation du paiement
  6. Mettre à jour le statut du ticket
  7. Envoyer un email de confirmation

Vous pourriez confier ça à un LLM sous forme d’exercice d’appel d’outils. D’après mon expérience, c’est chercher les ennuis. Le modèle pourrait décider que les étapes 2 et 3 sont « fondamentalement la même chose » et en sauter une. Ou il pourrait traiter le remboursement avant de vérifier l’éligibilité parce que l’utilisateur semblait mécontent.

Les workflows existent exactement pour ce scénario. Ils ne sont pas excitants, mais c’est bien le but.

Construire un planificateur d’activités météo

Voici un exemple pratique qui illustre le motif. Nous avons besoin de données météo factuelles et solides, couplées à des suggestions d’activités créatives. La récupération de la météo ne doit jamais être créative, mais les suggestions doivent l’être.

src/mastra/workflows/activity-planner.ts
import { createWorkflow, createStep } from '@mastra/core/workflows';
import { Agent } from '@mastra/core/agent';
import { openai } from '@ai-sdk/openai';
import { z } from 'zod';
// Étape 1 : Récupérer les données météo (Déterministe)
const fetchWeather = createStep({
id: 'fetch-weather',
description: 'Fetches weather forecast for a given city',
inputSchema: z.object({
city: z.string(),
}),
outputSchema: z.object({
location: z.string(),
temperature: z.number(),
conditions: z.string(),
precipitationChance: z.number(),
}),
execute: async ({ inputData }) => {
// ... (fetch logic) ...
const weather = await fetch(`https://api.open-meteo.com/v1/forecast?latitude=52.52&longitude=13.41&current=temperature_2m,weather_code&daily=precipitation_probability_mean`).then(r => r.json());
return {
location: inputData.city,
temperature: weather.current.temperature_2m,
conditions: getWeatherCondition(weather.current.weather_code),
precipitationChance: weather.daily.precipitation_probability_mean[0],
};
},
});
// Étape 2 : L'agent suggère des activités (Créatif)
const activityPlanner = new Agent({
id: 'activity-planner-agent',
name: 'Activity Planner',
instructions: `You are a local activities expert. Based on weather conditions, suggest 3-5 appropriate activities.
- For rain (>50% precipitation), prioritize indoor activities
- For extreme temperatures, consider climate-appropriate options
- Always include one adventurous and one relaxing option`,
model: openai('gpt-5'),
});
const planActivities = createStep({
id: 'plan-activities',
description: 'Uses AI to suggest activities based on weather',
inputSchema: z.object({
location: z.string(),
temperature: z.number(),
conditions: z.string(),
precipitationChance: z.number(),
}),
outputSchema: z.object({
activities: z.string(),
}),
execute: async ({ inputData }) => {
const prompt = `Weather in ${inputData.location}: ${inputData.temperature}°C...`;
const response = await activityPlanner.generate(prompt);
return { activities: response.text };
},
});
// Le pipeline
export const activityPlannerWorkflow = createWorkflow({
id: 'activity-planner',
inputSchema: z.object({ city: z.string() }),
outputSchema: z.object({ activities: z.string() }),
})
.then(fetchWeather)
.then(planActivities);
activityPlannerWorkflow.commit();

Le LLM ne touche jamais à l’API météo. Il reçoit des données de référence en entrée, puis fait ce pour quoi il est réellement bon : faire des suggestions contextuelles. Si vous inversez les rôles et laissez l’agent récupérer les données météo, vous finirez un jour ou l’autre avec un bulletin ensoleillé alors qu’il pleut réellement.

Quand envisager les workflows :


Le problème de la fenêtre de contexte dont personne ne parle

Il y a ce schéma que je continue de voir. Quelqu’un construit un chatbot. Il fonctionne très bien pendant les tests. Puis en production, les utilisateurs ont des conversations plus longues et soudainement le bot se perd.

Le développeur consulte les logs et réalise qu’il envoie l’historique complet de la conversation à chaque requête. Les 47 messages. Il brûle des tokens et de l’espace contexte pour des informations qui sont majoritairement non pertinentes.

Pire, il y a un phénomène que les chercheurs appellent « lost in the middle » (perdu au milieu) : les modèles performent moins bien quand l’information pertinente est enfouie dans un long contexte. Le modèle ne voit littéralement plus le bois à cause des arbres.

Envoyer l’historique complet de la conversation semble sûr. Vous donnez au modèle « toute l’information ». Mais en réalité, vous rendez plus difficile pour le modèle de se concentrer sur ce qui compte.

Mémoire de travail vs. Stockage à long terme

Le système de mémoire de Mastra vous offre les deux. La mémoire de travail conserve les messages récents dans la fenêtre de contexte. Le rappel sémantique recherche dans les messages historiques quand la requête courante semble liée.

src/mastra/agents/memory-agent.ts
import { Agent } from '@mastra/core/agent';
import { Memory } from '@mastra/memory';
import { LibSQLStore } from '@mastra/libsql';
export const memoryAgent = new Agent({
id: 'memory-agent',
name: 'Memory Agent',
instructions: 'You are a helpful assistant with perfect recall of our conversations.',
model: openai('gpt-5'),
memory: new Memory({
storage: new LibSQLStore({
id: 'memory-agent-store',
url: 'file:../mastra.db',
}),
options: {
lastMessages: 20, // Keep last 20 messages in context
semanticRecall: {
enabled: true, // Use embeddings to find old stuff
topK: 5,
threshold: 0.7,
},
},
}),
});

Voici comment cela se concrétise en pratique. Un utilisateur demande : « C’était quoi ce restaurant italien que tu as recommandé le mois dernier ? »

Sans rappel sémantique, l’agent voit les 20 derniers messages. La recommandation du restaurant était le message 487 sur 506. Elle a disparu. L’agent répond : « Je n’ai pas cette information. »

Avec le rappel sémantique :

  1. La requête est embarquée (embedded) : [0.234, -0.567, 0.891, ...]
  2. L’embedding est comparé aux messages historiques
  3. Le message 487 (« Je recommanderais Trattoria Bella — leur carbonara est incroyable ») obtient un score de similarité de 0,89
  4. Ce message est injecté dans le contexte courant
  5. L’agent répond : « J’ai recommandé Trattoria Bella. Leur carbonara est ce qui avait retenu mon attention. »

L’agent semble avoir une mémoire parfaite tout en n’utilisant qu’une fraction de la fenêtre de contexte. Ce n’est pas seulement de l’ingénierie astucieuse — c’est fonctionnellement nécessaire dès que les conversations dépassent quelques dizaines de messages.


Coordination par les réseaux d’agents

Parfois, vous avez besoin à la fois de structure et de flexibilité. Les workflows purs sont trop rigides. Les agents purs sont trop imprévisibles.

Les réseaux d’agents vous offrent un coordinateur qui décide quel agent spécialisé ou workflow invoquer en fonction de la tâche. Voyez-le comme un load balancer intelligent pour les capacités IA.

export const coordinatorAgent = new Agent({
id: 'coordinator-agent',
name: 'Research Coordinator',
instructions: `You are a network of researchers and writers.
- Use researchAgent for gathering facts
- Use writingAgent for producing final content
- Use weatherTool for current weather data
- Use activityPlannerWorkflow for location-based planning
Always produce comprehensive, well-structured responses.`,
model: openai('gpt-5'),
// Available primitives
agents: { researchAgent, writingAgent },
workflows: { activityPlannerWorkflow },
tools: { weatherTool },
// Network requires memory
memory: new Memory({
storage: new LibSQLStore({ id: 'network-store', url: 'file:../network.db' }),
}),
});

Quand vous interrogez ce réseau, le coordinateur analyse la requête et route en conséquence :

Ce motif scale mieux que d’essayer de tout faire entrer dans un seul méga-agent. Les agents spécialisés développent une expertise ciblée. Le coordinateur gère le routage. Chaque pièce fait ce pour quoi elle est bonne.


Assembler le tout

Les systèmes IA de production réels ont besoin d’architecture, pas seulement de prompts. Vous construisez des systèmes distribués où certains nœuds se trouvent être des LLM.

Les workflows vous donnent des garanties quand vous avez besoin que les choses se passent exactement comme prévu. La mémoire vous donne du contexte sans brûler votre budget de tokens. Les réseaux d’agents vous permettent de composer de la complexité à partir de parties plus simples.

Rien de tout cela n’est glamour. Mais après avoir vu suffisamment d’« agents entièrement autonomes » échouer en production, j’en suis venu à apprécier la fiabilité ennuyeuse plutôt que l’imprévisibilité excitante.

Votre expérience peut varier, mais d’après ce que j’ai observé, les systèmes qui sont réellement livrés et qui continuent de tourner sont ceux qui traitent les LLM comme des composants dans une architecture plus large plutôt que comme des boîtes magiques qui résolvent tout.

Ressources

Lire la série

  1. Routage LLM
  2. Sécurité et garde-fous
  3. Intégrations MCP et outils
  4. Workflows et mémoire (cet article)