DanLevy.net

Arrêtez de construire des agents capricieux : utilisez Workflows & Mémoire

Des patrons déterministes pour des modèles non-déterministes.

Les LLM ont cette étrange propriété : ils sont brillants pour comprendre les nuances mais terribles pour suivre des recettes. Donnez à GPT-4 un problème vague 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 « lui 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 lutter contre 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 fonctionne 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 n’est pas de meilleurs prompts. C’est de savoir quand arrêter de demander au LLM de « penser » et commencer à lui dire d’« obéir ».


Quand le déterministe bat le créatif

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

  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 le reversement de paiement
  6. Mettre à jour le statut du ticket
  7. Envoyer l’email de confirmation

Vous pourriez confier cela à un LLM comme 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 contrarié.

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

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

Voici un exemple pratique qui montre le motif. Nous avons besoin de données météorologiques factuelles et fiables associées à des suggestions d’activités créatives. La récupération météo ne doit jamais être créative, mais les suggestions devraient 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';
// Step 1: Fetch weather data (Deterministic)
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],
};
},
});
// Step 2: Agent suggests activities (Creative)
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 };
},
});
// The 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 vérifiées en entrée, puis fait ce pour quoi il est réellement bon : proposer des suggestions contextuelles. Si vous inversez cela et laissez l’agent récupérer les données météo, vous finirez par obtenir un forecast ensoleillé quand il pleut.

Quand envisager les workflows :


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

Il y a ce motif que je vois sans cesse. Quelqu’un construit un chatbot. Ça fonctionne très bien pendant les tests. Puis en production, les utilisateurs ont des conversations plus longues et soudain le bot se perd.

Le développeur regarde les logs et se rend compte qu’il envoie l’historique complet de la conversation à chaque requête. Les 47 messages. Il brûle des tokens et de l’espace de contexte pour des informations en grande partie non pertinentes.

Pire, il existe un phénomène que les chercheurs appellent « perdu au milieu » où les modèles performent moins bien lorsque l’information pertinente est enterrée dans un long contexte. Le modèle ne voit littéralement pas la forêt à cause des arbres.

Envoyer l’historique complet de la conversation semble sûr. Vous donnez au modèle « toutes les informations. » Mais vous rendez en fait plus difficile pour le modèle de se concentrer sur l’essentiel.

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 cherche dans les messages historiques lorsque la requête actuelle semble connexe.

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 déroule en pratique. Un utilisateur demande : « C’était quoi ce restaurant italien que tu m’as recommandé le mois dernier ? »

Sans rappel sémantique, l’agent voit les 20 derniers messages. La recommandation de 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 encodée en embedding : [0.234, -0.567, 0.891, ...]
  2. L’embedding est comparé aux messages historiques
  3. Le message 487 (« Je vous recommande la Trattoria Bella — leur carbonara est incroyable ») obtient un score de similarité de 0.89
  4. Ce message est injecté dans le contexte actuel
  5. L’agent répond : « J’ai recommandé la 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 une ingénierie astucieuse — c’est fonctionnellement nécessaire dès que les conversations dépassent quelques dizaines de messages.


Coordination via 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. Considérez-le comme un répartiteur de charge intelligent pour les capacités d’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 achemine en conséquence :

Ce motif passe mieux à l’échelle que d’essayer de fourrer tout dans un méga-agent unique. Les agents spécialisés développent une expertise ciblée. Le coordinateur gère l’acheminement. Chaque pièce fait ce pour quoi elle est douée.


Assembler le tout

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

Les workflows vous offrent 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 assez d’« agents totalement autonomes » échouer en production, j’ai appris à apprécier la fiabilité ennuyeuse plutôt que l’imprévisibilité excitante.

Les résultats peuvent varier, mais dans mon expérience, les systèmes qui sont réellement déployés et qui restent opérationnels 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)