Construire des agents IA from scratch avec TypeScript : maîtriser le pattern ReAct avec le Vercel AI SDK

AI Bot
Par AI Bot ·

Chargement du lecteur de synthèse vocale...

Tous les grands laboratoires d'IA parient sur les agents. OpenAI, Anthropic et Google sont tous en course pour livrer des modèles qui ne se contentent pas de répondre aux questions — ils raisonnent, agissent et itèrent jusqu'à ce que le travail soit fait. Mais derrière chaque agent sophistiqué se cache un pattern d'une simplicité trompeuse : ReAct.

Dans ce tutoriel, vous allez construire des agents IA from scratch avec TypeScript. Vous commencerez par une boucle de raisonnement brute, puis vous ajouterez progressivement des outils, une exécution multi-étapes et des protections de production — le tout alimenté par le Vercel AI SDK.

Pourquoi ReAct est important en 2026 : Gartner a rapporté une hausse de 1 445 % des demandes concernant les systèmes multi-agents du T1 2024 au T2 2025. Comprendre le pattern ReAct fondamental est la base pour construire tout système d'agents — des simples assistants aux orchestrations multi-agents complexes.

Ce que vous allez apprendre

À la fin de ce tutoriel, vous serez capable de :

  • Comprendre le pattern ReAct (Raisonnement + Action) et pourquoi il constitue l'épine dorsale des agents IA modernes
  • Construire une boucle d'agent manuelle avec generateText pour voir exactement comment fonctionnent les agents
  • Définir des outils type-safe avec des schémas Zod que les LLM peuvent invoquer
  • Utiliser ToolLoopAgent pour une orchestration d'agents prête pour la production
  • Implémenter un contrôle avancé de boucle : limites d'étapes, budgets de coûts et patterns d'outils forcés
  • Construire un agent de recherche pratique qui cherche, analyse et synthétise l'information

Comment fonctionnent réellement les agents IA

Avant d'écrire du code, comprenons le modèle mental. Un appel LLM traditionnel est un simple aller-retour :

Prompt → LLM → Réponse

Un agent est un LLM dans une boucle. À chaque itération, le modèle peut soit répondre avec du texte (fin de la boucle), soit appeler un outil (la boucle continue) :

Prompt → LLM → « Je dois chercher ceci » → appelle l'outil de recherche
                                                    ↓
         LLM ← résultat de l'outil ← la recherche s'exécute
          ↓
         LLM → « Maintenant je dois calculer » → appelle l'outil calculatrice
                                                    ↓
         LLM ← résultat de l'outil ← la calculatrice s'exécute
          ↓
         LLM → « Voici votre réponse : ... » → réponse textuelle (fin de la boucle)

C'est le pattern ReAct : le modèle fléchit (Reason) à ce qu'il doit faire, puis Agit (Act) en appelant un outil. La boucle se répète jusqu'à ce que le modèle décide qu'il dispose de suffisamment d'informations pour répondre.

Prérequis

Avant de commencer, assurez-vous d'avoir :

  • Node.js 20+ installé
  • Une clé API OpenAI (ou tout fournisseur compatible avec le AI SDK)
  • Des connaissances de base en TypeScript
  • Une familiarité avec async/await et les schémas Zod
  • Un éditeur de code (VS Code recommandé)

Flexibilité du fournisseur : le Vercel AI SDK prend en charge OpenAI, Anthropic, Google, Mistral et de nombreux autres fournisseurs. Nous utiliserons OpenAI dans ce tutoriel, mais vous pouvez changer de modèle en modifiant une seule ligne.

Étape 1 : configuration du projet

Créez un nouveau projet TypeScript et installez les dépendances :

mkdir ai-agent-tutorial && cd ai-agent-tutorial
npm init -y
npm install ai @ai-sdk/openai zod dotenv
npm install -D typescript tsx @types/node

Initialisez TypeScript :

npx tsc --init --target ES2022 --module NodeNext --moduleResolution NodeNext --outDir dist

Créez un fichier .env pour votre clé API :

OPENAI_API_KEY=sk-your-key-here

La structure de votre projet devrait ressembler à ceci :

ai-agent-tutorial/
├── .env
├── package.json
├── tsconfig.json
└── src/
    ├── 01-basic-tool-call.ts
    ├── 02-manual-agent-loop.ts
    ├── 03-tool-loop-agent.ts
    ├── 04-forced-tool-pattern.ts
    └── 05-research-agent.ts

Étape 2 : votre premier appel d'outil

Avant de construire un agent complet, comprenons l'unité atomique : un seul appel d'outil. Créez src/01-basic-tool-call.ts :

import "dotenv/config";
import { generateText, tool } from "ai";
import { openai } from "@ai-sdk/openai";
import { z } from "zod";
 
const result = await generateText({
  model: openai("gpt-4o"),
  tools: {
    weather: tool({
      description: "Obtenir la météo actuelle dans un lieu donné",
      inputSchema: z.object({
        location: z.string().describe("Nom de la ville, ex : San Francisco"),
      }),
      execute: async ({ location }) => {
        // En production, appelez une vraie API météo
        const conditions = ["sunny", "cloudy", "rainy", "windy"];
        return {
          location,
          temperature: Math.round(15 + Math.random() * 20),
          condition: conditions[Math.floor(Math.random() * conditions.length)],
          unit: "celsius",
        };
      },
    }),
  },
  prompt: "What's the weather like in Tokyo?",
});
 
console.log("Réponse :", result.text);
console.log("Appels d'outils :", JSON.stringify(result.toolCalls, null, 2));
console.log("Résultats des outils :", JSON.stringify(result.toolResults, null, 2));

Exécutez-le :

npx tsx src/01-basic-tool-call.ts

Remarquez que le modèle a appelé notre outil weather avec { location: "Tokyo" }. Le AI SDK a automatiquement exécuté l'outil et renvoyé le résultat au modèle, qui a ensuite généré une réponse en langage naturel. Mais il s'agissait d'un seul aller-retour — ce n'est pas encore un agent.

Étape 3 : construire une boucle d'agent manuelle

Construisons maintenant une vraie boucle d'agent from scratch. C'est le pattern ReAct brut — sans abstractions, juste la logique fondamentale. Créez src/02-manual-agent-loop.ts :

import "dotenv/config";
import { generateText, tool } from "ai";
import { openai } from "@ai-sdk/openai";
import { z } from "zod";
import type { ModelMessage } from "ai";
 
// Définition de nos outils
const tools = {
  search: tool({
    description: "Rechercher des informations sur un sujet. Retourne des faits pertinents.",
    inputSchema: z.object({
      query: z.string().describe("La requête de recherche"),
    }),
    execute: async ({ query }) => {
      console.log(`  🔍 Recherche : "${query}"`);
      // Résultats de recherche simulés
      const results: Record<string, string> = {
        typescript:
          "TypeScript 5.7 released in 2025. Features include improved inference, decorator metadata, and faster compilation.",
        "ai agents":
          "AI agents market projected to reach $50B by 2030. Key frameworks: Vercel AI SDK, LangChain, CrewAI.",
        "react pattern":
          "ReAct (Reasoning + Acting) proposed by Yao et al. 2022. Combines chain-of-thought with tool use for grounded reasoning.",
      };
      const key = Object.keys(results).find((k) =>
        query.toLowerCase().includes(k)
      );
      return key
        ? results[key]
        : `Aucun résultat spécifique pour "${query}". Essayez une requête plus précise.`;
    },
  }),
  calculator: tool({
    description: "Effectuer des calculs mathématiques",
    inputSchema: z.object({
      expression: z.string().describe("Expression mathématique, ex : '2 + 2'"),
    }),
    execute: async ({ expression }) => {
      console.log(`  🧮 Calcul : ${expression}`);
      try {
        // Évaluation mathématique sécurisée via le constructeur Function
        const sanitized = expression.replace(/[^0-9+\-*/().% ]/g, "");
        const result = new Function(`return ${sanitized}`)();
        return { expression, result: Number(result) };
      } catch {
        return { expression, error: "Expression invalide" };
      }
    },
  }),
};
 
// La boucle ReAct manuelle
async function runAgent(userPrompt: string, maxSteps = 10) {
  const messages: ModelMessage[] = [
    {
      role: "system",
      content:
        "You are a helpful research assistant. Use the available tools to find information and perform calculations. Think step by step.",
    },
    { role: "user", content: userPrompt },
  ];
 
  console.log(`\n🤖 Agent démarré : "${userPrompt}"\n`);
 
  for (let step = 0; step < maxSteps; step++) {
    console.log(`--- Étape ${step + 1} ---`);
 
    const result = await generateText({
      model: openai("gpt-4o"),
      messages,
      tools,
    });
 
    // Ajouter la réponse du modèle à l'historique de conversation
    messages.push(...result.response.messages);
 
    // Si le modèle a généré du texte (pas d'appels d'outils), c'est terminé
    if (result.text) {
      console.log(`\n✅ Agent terminé en ${step + 1} étape(s)`);
      console.log(`\n📝 Réponse finale :\n${result.text}`);
      return result.text;
    }
 
    // Journaliser les appels d'outils pour cette étape
    for (const toolCall of result.toolCalls) {
      console.log(`  Outil : ${toolCall.toolName}(${JSON.stringify(toolCall.args)})`);
    }
  }
 
  console.log("⚠️ L'agent a atteint le nombre maximum d'étapes sans terminer");
  return null;
}
 
// Exécuter l'agent
await runAgent(
  "Search for information about the ReAct pattern and AI agents. Then calculate what percentage of the $50B projected agent market would be $7.6B."
);

Exécutez-le :

npx tsx src/02-manual-agent-loop.ts

Vous verrez l'agent raisonner à travers plusieurs étapes :

🤖 Agent démarré : « Rechercher des informations sur... »

--- Étape 1 ---
  🔍 Recherche : "react pattern"
  Outil : search({"query":"react pattern"})
--- Étape 2 ---
  🔍 Recherche : "ai agents"
  Outil : search({"query":"ai agents"})
--- Étape 3 ---
  🧮 Calcul : (7.6 / 50) * 100
  Outil : calculator({"expression":"(7.6 / 50) * 100"})
--- Étape 4 ---

✅ Agent terminé en 4 étapes

📝 Réponse finale :
Le pattern ReAct... Le marché des agents IA... 7,6 milliards $ représentent 15,2 % du marché projeté à 50 milliards $.

C'est le cœur de tout agent IA : une boucle où le modèle décide quoi faire ensuite. Le modèle a appelé search deux fois pour collecter des informations, puis calculator pour effectuer des calculs, et finalement a synthétisé le tout en une réponse.

Étape 4 : agent de production avec ToolLoopAgent

La boucle manuelle fonctionne mais nécessite beaucoup de code boilerplate. La classe ToolLoopAgent du AI SDK gère toute l'orchestration pour vous. Créez src/03-tool-loop-agent.ts :

import "dotenv/config";
import { ToolLoopAgent, tool, stepCountIs } from "ai";
import { openai } from "@ai-sdk/openai";
import { z } from "zod";
 
const researchAgent = new ToolLoopAgent({
  model: openai("gpt-4o"),
  system:
    "You are a research assistant. Use tools to find and analyze information. Always verify claims with searches before presenting them as facts.",
  tools: {
    searchWeb: tool({
      description:
        "Rechercher sur le web des informations actuelles sur n'importe quel sujet",
      inputSchema: z.object({
        query: z.string().describe("Requête de recherche"),
        topic: z
          .enum(["technology", "science", "business", "general"])
          .describe("Catégorie du sujet pour cibler les résultats"),
      }),
      execute: async ({ query, topic }) => {
        console.log(`  🔍 [${topic}] Recherche : "${query}"`);
        // Simulé — remplacez par de vrais appels API
        return {
          results: [
            {
              title: `Meilleur résultat pour : ${query}`,
              snippet: `Informations complètes sur ${query} dans le domaine ${topic}. Les résultats clés incluent les développements récents et les tendances projetées pour 2026-2027.`,
              relevance: 0.95,
            },
          ],
          totalResults: 1,
        };
      },
    }),
    analyzeData: tool({
      description:
        "Analyser et comparer des points de données, identifier les tendances et les patterns",
      inputSchema: z.object({
        data: z.string().describe("Les données ou faits à analyser"),
        analysisType: z
          .enum(["comparison", "trend", "summary", "sentiment"])
          .describe("Type d'analyse à effectuer"),
      }),
      execute: async ({ data, analysisType }) => {
        console.log(`  📊 Analyse (${analysisType}) : ${data.slice(0, 60)}...`);
        return {
          analysisType,
          findings: `L'analyse de type « ${analysisType} » sur les données fournies révèle des patterns clés et des insights exploitables.`,
          confidence: 0.87,
        };
      },
    }),
  },
  stopWhen: stepCountIs(10),
});
 
// Exécuter l'agent
const result = await researchAgent.generate({
  prompt:
    "Research the current state of AI agent frameworks in 2026. Compare the top 3 frameworks and tell me which one is best for TypeScript developers.",
});
 
console.log("\n📝 Réponse finale :\n");
console.log(result.text);
console.log(`\n📊 Total des étapes : ${result.steps.length}`);
console.log(
  `📊 Total des tokens : ${result.steps.reduce((sum, s) => sum + (s.usage?.totalTokens ?? 0), 0)}`
);

La classe ToolLoopAgent vous offre :

  • Gestion automatique de la conversation — plus besoin d'ajouter manuellement les messages
  • Conditions d'arrêt intégréesstepCountIs(10) empêche les boucles incontrôlées
  • Suivi des étapes — inspectez chaque étape pour le débogage et l'observabilité
  • Suivi de l'utilisation des tokens — surveillez les coûts sur l'ensemble de l'exécution de l'agent

Étape 5 : contrôle avancé de la boucle

Les agents en conditions réelles nécessitent un contrôle fin de leur exécution. Créez src/04-forced-tool-pattern.ts :

import "dotenv/config";
import { ToolLoopAgent, tool, stepCountIs } from "ai";
import { openai } from "@ai-sdk/openai";
import { z } from "zod";
import type { StopCondition } from "ai";
 
// Définition des outils avec les types pour le générique StopCondition
const tools = {
  gatherEvidence: tool({
    description:
      "Collecter des preuves et des faits sur une affirmation. Toujours appeler ceci avant de faire des assertions.",
    inputSchema: z.object({
      claim: z.string().describe("L'affirmation à investiguer"),
    }),
    execute: async ({ claim }) => {
      console.log(`  📋 Collecte de preuves : "${claim}"`);
      return {
        claim,
        evidence: [
          { source: "Article de recherche", supports: true, confidence: 0.9 },
          { source: "Rapport industriel", supports: true, confidence: 0.85 },
        ],
        consensusStrength: "strong",
      };
    },
  }),
  assessRisk: tool({
    description:
      "Évaluer les risques potentiels ou les contre-arguments d'une conclusion",
    inputSchema: z.object({
      conclusion: z.string().describe("La conclusion à évaluer"),
    }),
    execute: async ({ conclusion }) => {
      console.log(`  ⚠️ Évaluation des risques : "${conclusion.slice(0, 50)}..."`);
      return {
        risks: ["Taille d'échantillon limitée", "Domaine en évolution rapide"],
        overallRisk: "medium",
        recommendation: "Présenter les résultats avec les réserves appropriées",
      };
    },
  }),
  // Un outil « terminé » sans fonction execute — sert de signal de terminaison
  submitReport: tool({
    description:
      "Soumettre le rapport de recherche final. Appeler ceci UNIQUEMENT lorsque les preuves ont été collectées et les risques évalués.",
    inputSchema: z.object({
      title: z.string().describe("Titre du rapport"),
      findings: z.string().describe("Résumé des conclusions principales"),
      confidence: z
        .enum(["high", "medium", "low"])
        .describe("Niveau de confiance global"),
      caveats: z.array(z.string()).describe("Réserves importantes"),
    }),
    // Pas de fonction execute — appeler cet outil arrête la boucle
  }),
};
 
// Condition d'arrêt personnalisée : basée sur le budget
const budgetExceeded: StopCondition<typeof tools> = ({ steps }) => {
  const totalTokens = steps.reduce(
    (sum, step) => sum + (step.usage?.totalTokens ?? 0),
    0
  );
  const estimatedCost = (totalTokens / 1000) * 0.005; // estimation approximative
  if (estimatedCost > 0.10) {
    console.log(`  💰 Budget dépassé : ~${estimatedCost.toFixed(4)} $`);
    return true;
  }
  return false;
};
 
const factCheckAgent = new ToolLoopAgent({
  model: openai("gpt-4o"),
  system: `You are a rigorous fact-checking agent. Follow this process:
1. Gather evidence for each major claim
2. Assess risks and counterarguments
3. Submit a final report with your findings
 
You MUST call tools at every step. When done, call submitReport.`,
  tools,
  toolChoice: "required", // Forcer le modèle à toujours appeler un outil
  stopWhen: [stepCountIs(15), budgetExceeded], // Conditions d'arrêt multiples
  prepareStep: async ({ stepNumber }) => {
    // Disponibilité des outils par phases
    if (stepNumber <= 3) {
      return { activeTools: ["gatherEvidence"] };
    }
    if (stepNumber <= 5) {
      return { activeTools: ["gatherEvidence", "assessRisk"] };
    }
    return { activeTools: ["assessRisk", "submitReport"] };
  },
});
 
const result = await factCheckAgent.generate({
  prompt:
    "Fact-check this claim: AI agents will handle 40% of enterprise applications by end of 2026.",
});
 
// Avec toolChoice: 'required' et un outil de terminaison, la réponse est dans staticToolCalls
console.log("\n📄 Rapport soumis :");
const report = result.staticToolCalls.find(
  (tc) => tc.toolName === "submitReport"
);
if (report) {
  console.log(JSON.stringify(report.args, null, 2));
}
console.log(`\n📊 Terminé en ${result.steps.length} étapes`);

Cet exemple illustre trois patterns avancés :

Le pattern d'outil forcé

En combinant toolChoice: "required" avec un outil submitReport sans fonction execute, on force l'agent à toujours utiliser des outils. Lorsque l'agent appelle submitReport, la boucle se termine car il n'y a pas de fonction à exécuter. La sortie structurée est capturée dans result.staticToolCalls.

Les conditions d'arrêt personnalisées

La fonction budgetExceeded suit l'utilisation cumulative des tokens et arrête l'agent si les coûts dépassent un seuil. Vous pouvez combiner plusieurs conditions dans un tableau — la boucle s'arrête lorsque n'importe quelle condition est remplie.

La disponibilité des outils par phases

Le callback prepareStep contrôle dynamiquement quels outils sont disponibles à chaque étape. Les premières étapes ne permettent que la collecte de preuves, les étapes intermédiaires ajoutent l'évaluation des risques, et les étapes finales se concentrent sur la soumission du rapport. Cela guide l'agent à travers un workflow structuré.

Étape 6 : construire un agent de recherche complet

Rassemblons tout dans un agent de recherche pratique avec des patterns du monde réel. Créez src/05-research-agent.ts :

import "dotenv/config";
import { ToolLoopAgent, tool, stepCountIs } from "ai";
import { openai } from "@ai-sdk/openai";
import { z } from "zod";
 
// --- Définitions des outils ---
 
const searchTool = tool({
  description:
    "Rechercher des informations actuelles. Utilisez des requêtes spécifiques pour de meilleurs résultats.",
  inputSchema: z.object({
    query: z.string().describe("Requête de recherche spécifique"),
  }),
  execute: async ({ query }) => {
    console.log(`  🔍 Recherche : "${query}"`);
    // Remplacez par une vraie API de recherche (Tavily, Serper, Brave, etc.)
    return {
      results: [
        {
          title: `Résultat pour : ${query}`,
          content: `Informations détaillées sur ${query}. Les dernières données de 2026 montrent des développements significatifs dans ce domaine, avec une adoption en croissance de 3x d'une année sur l'autre.`,
          url: `https://example.com/search?q=${encodeURIComponent(query)}`,
        },
      ],
    };
  },
});
 
const readUrlTool = tool({
  description: "Lire le contenu complet d'une URL pour une analyse approfondie",
  inputSchema: z.object({
    url: z.string().url().describe("L'URL à lire"),
  }),
  execute: async ({ url }) => {
    console.log(`  📄 Lecture : ${url}`);
    // Remplacez par un vrai scraper web (Firecrawl, Jina Reader, etc.)
    return {
      url,
      content: `Contenu complet de l'article depuis ${url}. Contient une analyse détaillée, des statistiques et des avis d'experts sur le sujet. Statistiques clés : taille du marché, taux de croissance et métriques d'adoption.`,
      wordCount: 1500,
    };
  },
});
 
const noteTool = tool({
  description:
    "Sauvegarder une découverte importante dans vos notes. Utilisez ceci pour suivre les faits clés pendant vos recherches.",
  inputSchema: z.object({
    category: z
      .enum(["fact", "statistic", "quote", "insight"])
      .describe("Type de note"),
    content: z.string().describe("Le contenu de la note"),
    source: z.string().describe("D'où provient cette information"),
    reliability: z
      .enum(["high", "medium", "low"])
      .describe("Le degré de fiabilité de cette information"),
  }),
  execute: async ({ category, content, source, reliability }) => {
    console.log(`  📝 Note [${category}/${reliability}] : ${content.slice(0, 60)}...`);
    return { saved: true, category, reliability };
  },
});
 
const outlineTool = tool({
  description:
    "Créer ou mettre à jour le plan du rapport avant de rédiger le rapport final",
  inputSchema: z.object({
    sections: z
      .array(
        z.object({
          heading: z.string(),
          keyPoints: z.array(z.string()),
        })
      )
      .describe("Sections du rapport avec les points clés"),
  }),
  execute: async ({ sections }) => {
    console.log(`  📋 Plan créé : ${sections.length} sections`);
    return { sections: sections.length, status: "outline_ready" };
  },
});
 
// --- Définition de l'agent ---
 
const researchAgent = new ToolLoopAgent({
  model: openai("gpt-4o"),
  system: `You are an expert research agent. Your process:
 
1. SEARCH: Start by searching for information from multiple angles
2. READ: Deep-dive into the most relevant sources
3. NOTE: Save key findings with reliability ratings
4. OUTLINE: Organize your findings into a structured outline
5. WRITE: Synthesize everything into a comprehensive response
 
Be thorough. Cross-reference information. Note when sources disagree.
Always save important findings as notes before writing your final response.`,
  tools: {
    search: searchTool,
    readUrl: readUrlTool,
    saveNote: noteTool,
    createOutline: outlineTool,
  },
  stopWhen: stepCountIs(15),
  prepareStep: async ({ stepNumber, messages }) => {
    // Élaguer les anciens messages pour gérer la fenêtre de contexte
    if (messages.length > 30) {
      return {
        messages: [messages[0], ...messages.slice(-20)],
      };
    }
    return {};
  },
});
 
// --- Exécution ---
 
async function main() {
  console.log("🚀 Démarrage de l'agent de recherche\n");
  console.log("=".repeat(60));
 
  const startTime = Date.now();
 
  const result = await researchAgent.generate({
    prompt:
      "Research the state of AI agent development in 2026. Cover the leading frameworks, design patterns, and enterprise adoption trends. Include specific data points.",
  });
 
  const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
 
  console.log("\n" + "=".repeat(60));
  console.log("\n📝 RAPPORT DE RECHERCHE :\n");
  console.log(result.text);
 
  // Afficher les statistiques d'exécution
  console.log("\n" + "=".repeat(60));
  console.log("📊 Statistiques d'exécution :");
  console.log(`   Étapes : ${result.steps.length}`);
  console.log(`   Temps : ${elapsed}s`);
 
  const totalTokens = result.steps.reduce(
    (sum, s) => sum + (s.usage?.totalTokens ?? 0),
    0
  );
  console.log(`   Tokens : ${totalTokens.toLocaleString()}`);
 
  // Journaliser la répartition de l'utilisation des outils
  const toolUsage: Record<string, number> = {};
  for (const step of result.steps) {
    for (const tc of step.toolCalls) {
      toolUsage[tc.toolName] = (toolUsage[tc.toolName] ?? 0) + 1;
    }
  }
  console.log(`   Utilisation des outils :`, toolUsage);
}
 
main().catch(console.error);

Cet agent de recherche illustre des patterns du monde réel :

  • Multiples outils spécialisés — chacun gère un aspect différent du processus de recherche
  • Prise de notes structurée — l'agent sauvegarde ses découvertes avec des niveaux de fiabilité au fur et à mesure de ses recherches
  • Gestion de la fenêtre de contexteprepareStep élague les anciens messages pour éviter le dépassement de contexte
  • Statistiques d'exécution — suivi des étapes, du temps, des tokens et de l'utilisation des outils pour l'observabilité

Tester votre implémentation

Exécutez chaque fichier pour voir l'agent en action :

# Appel d'outil basique
npx tsx src/01-basic-tool-call.ts
 
# Boucle ReAct manuelle — voyez le pattern brut
npx tsx src/02-manual-agent-loop.ts
 
# ToolLoopAgent — orchestration prête pour la production
npx tsx src/03-tool-loop-agent.ts
 
# Patterns avancés — outils forcés, budgets, phases
npx tsx src/04-forced-tool-pattern.ts
 
# Agent de recherche complet
npx tsx src/05-research-agent.ts

Référence des patterns de conception d'agents

Voici une référence rapide des patterns couverts et quand utiliser chacun :

Pattern 1 : boucle d'outils simple

const agent = new ToolLoopAgent({
  model: openai("gpt-4o"),
  tools: { /* ... */ },
  stopWhen: stepCountIs(10),
});

Quand l'utiliser : pour la plupart des tâches d'agents. Le modèle décide quand appeler les outils et quand répondre.

Pattern 2 : outil forcé avec signal de terminaison

const agent = new ToolLoopAgent({
  model: openai("gpt-4o"),
  tools: {
    /* outils de travail... */
    done: tool({
      description: "Signal completion",
      inputSchema: z.object({ answer: z.string() }),
      // Pas de fonction execute
    }),
  },
  toolChoice: "required",
});

Quand l'utiliser : lorsque vous avez besoin d'une sortie structurée ou que vous voulez vous assurer que l'agent utilise toujours des outils avant de répondre.

Pattern 3 : exécution par phases

const agent = new ToolLoopAgent({
  model: openai("gpt-4o"),
  tools: { search, analyze, summarize },
  prepareStep: async ({ stepNumber }) => {
    if (stepNumber <= 3) return { activeTools: ["search"] };
    if (stepNumber <= 6) return { activeTools: ["analyze"] };
    return { activeTools: ["summarize"] };
  },
});

Quand l'utiliser : lorsque l'agent doit suivre un workflow spécifique — d'abord rechercher, puis analyser, puis conclure.

Pattern 4 : boucle manuelle

const messages: ModelMessage[] = [{ role: "user", content: prompt }];
for (let i = 0; i < maxSteps; i++) {
  const result = await generateText({ model, messages, tools });
  messages.push(...result.response.messages);
  if (result.text) break;
}

Quand l'utiliser : lorsque vous avez besoin d'un contrôle maximal — filtrage de messages personnalisé, injection dynamique d'outils ou logique de branchement complexe.

Checklist de production

Avant de déployer des agents en production, prenez en compte ces facteurs :

Sécurité

  • Définissez maxSteps ou stepCountIs() — limitez toujours le nombre d'itérations pour empêcher les boucles incontrôlées
  • Implémentez des budgets de coûts — suivez l'utilisation des tokens et arrêtez lorsque les coûts dépassent un seuil
  • Validez les entrées des outils — les schémas Zod s'en chargent, mais ajoutez une validation de logique métier dans execute
  • Utilisez needsApproval — pour les outils ayant des effets de bord (envoi d'emails, achats, modification de données)

Observabilité

  • Journalisez chaque étape — enregistrez les appels d'outils, les entrées, les sorties et l'utilisation des tokens
  • Suivez le temps d'exécution — les agents peuvent prendre de quelques secondes à plusieurs minutes ; définissez des timeouts
  • Surveillez les taux d'erreur — les outils échoueront ; gérez-les gracieusement et laissez l'agent réessayer ou s'adapter

Performance

  • Utilisez prepareStep pour la gestion du contexte — élaguez les anciens messages pour rester dans les limites du contexte
  • Choisissez le bon modèle — utilisez un modèle plus rapide/moins cher pour les étapes simples, un plus performant pour le raisonnement complexe
  • Mettez en cache les résultats des outils — si la même requête de recherche apparaît deux fois, retournez le résultat en cache

Dépannage

L'agent tourne en boucle infinie : vous avez probablement toolChoice: "required" sans outil de terminaison, ou l'outil de terminaison a une fonction execute. Supprimez la fonction execute pour que son appel termine la boucle.

L'agent n'utilise pas les outils : vérifiez les descriptions de vos outils. Le modèle utilise les descriptions pour décider quand appeler les outils. Soyez spécifique : « Rechercher sur le web des informations actuelles » est mieux que « Rechercher ».

Erreurs de schéma d'outil : assurez-vous que les schémas Zod correspondent à ce que le modèle génère. Utilisez .describe() sur chaque champ pour guider la sortie du modèle.

Dépassement de la fenêtre de contexte : utilisez prepareStep pour élaguer les anciens messages. Conservez le prompt système et les messages récents ; supprimez les anciens résultats d'outils.

Prochaines étapes

Vous avez appris les patterns fondamentaux derrière les agents IA. Voici ce que vous pouvez faire ensuite :

Conclusion

Tout agent IA — d'un simple chatbot à une orchestration multi-agents complexe — repose sur le même fondement : un LLM dans une boucle qui peut raisonner et agir. Le pattern ReAct donne leur puissance aux agents, et le Vercel AI SDK vous fournit les outils natifs TypeScript pour les construire en toute sécurité.

Vous êtes passé d'un simple appel d'outil, à travers une boucle ReAct manuelle, jusqu'à des agents prêts pour la production avec des budgets, des phases et des sorties structurées. Ces patterns sont les briques de base de tout système d'agents que vous construirez en 2026 et au-delà.

L'insight clé : les agents ne sont pas de la magie. Ce sont simplement des boucles avec de bons outils et des instructions claires. Maintenant, allez construire quelque chose.


Vous voulez lire plus de tutoriels? Découvrez notre dernier tutoriel sur 7 Laravel 11 Bases : Réponses.

Discutez de votre projet avec nous

Nous sommes ici pour vous aider avec vos besoins en développement Web. Planifiez un appel pour discuter de votre projet et comment nous pouvons vous aider.

Trouvons les meilleures solutions pour vos besoins.

Articles connexes

Construire un Agent IA Autonome avec Agentic RAG et Next.js

Apprenez a construire un agent IA qui decide de maniere autonome quand et comment recuperer des informations depuis des bases de donnees vectorielles. Un guide pratique complet avec Vercel AI SDK et Next.js, accompagne d'exemples executables.

30 min read·