Au-delà d'un appel d'outil : découvrir le Code Mode

Fait partie de la série Construire le Code Mode pour les API avec MCP et Workers

Les LLM peuvent demander des appels d'outils. Cela paraît évident aujourd'hui, mais ce n'était pas possible au début. C'est amusant de parler de « début » comme s'il remontait à longtemps, alors que cela ne fait que 3 ans.

Souvent appelée « tool calling » ou « function calling », cette capacité permet à un modèle d'interagir avec un système externe. Un LLM peut par exemple lire ou écrire un fichier grâce à un appel d'outil. Sans cette capacité, un modèle peut toujours expliquer et générer du contenu, mais il ne peut pas agir directement sur un système externe.

Associé à des outils utiles, un modèle peut avancer vers un objectif. Il peut recueillir les informations dont il a besoin et effectuer les actions nécessaires. Une demande claire peut suffire pour une tâche bien délimitée, même si la fiabilité des résultats dépend toujours des outils disponibles, des instructions et des protections en place.

Nous devons toutefois lui fournir des outils qui lui permettent d'effectuer des actions précises pour notre compte.

Ajouter un outil personnalisé à ChatGPT

Imaginez que vous utilisez ChatGPT et que vous souhaitez lui permettre de lire les fichiers de votre disque pour fournir des réponses contextualisées. Vous devez lui donner un outil capable de lire les fichiers, mais vous n'avez pas accès à l'implémentation du modèle.

Du point de vue du code, cela ressemble à ceci :

ts
import OpenAI from 'openai'

const client = new OpenAI()

const tools = [
  {
    type: 'function',
    name: 'get_weather',
    description: 'Get current temperature for a given location.',
    parameters: {
      type: 'object',
      properties: {
        location: {
          type: 'string',
          description: 'City and country e.g. Bogotá, Colombia',
        },
      },
      required: ['location'],
      additionalProperties: false,
    },
    strict: true,
  },
]

const response = await client.responses.create({
  model: 'gpt-5.5',
  input: [
    { role: 'user', content: 'What is the weather like in Paris today?' },
  ],
  tools,
})

// From the OpenAI Developer documentation: https://developers.openai.com/api/docs/guides/tools?tool-type=function-calling

Nous comprenons vite que nous n'avons pas accès à ce client dans ChatGPT. Même si nous y avions accès, deux problèmes se poseraient :

  1. Les personnes qui ne développent pas ne feraient pas ces modifications elles-mêmes.
  2. Nous devrions les implémenter pour chaque client, comme Claude, VS Code, Dust, etc.

Un cauchemar pour tout le monde, nous compris.

C'est là que MCP intervient.

MCP pour exposer les outils de manière uniforme

[!INFO] Les principes fondamentaux de MCP sortent du cadre de cette série. Si vous avez besoin d'une introduction au protocole ou à la relation entre un Agent IA et un serveur MCP, commencez par Agents IA et serveur MCP au service du web agentique, en particulier son article sur le serveur MCP.

En résumé, MCP est un protocole qui permet à un Agent IA de communiquer avec un serveur. Le serveur peut fournir des capacités supplémentaires à l'Agent, comme l'accès à des outils. L'Agent, appelé client, peut alors utiliser ces outils pour agir au nom de l'utilisateur.

Concrètement, le serveur expose les outils par l'intermédiaire d'une API uniforme. Le client peut appeler cette API pour connaître les outils disponibles, puis les enregistrer auprès du modèle. Le modèle peut alors appeler ces outils comme s'il s'agissait de fonctions natives. Lorsqu'un outil est appelé, le client envoie la requête au serveur au lieu de l'exécuter en local. Le serveur exécute l'outil et renvoie le résultat au client, qui le transmet ensuite au modèle.

Du point de vue du code, le client ressemble à ceci :

ts
// This is pseudo-code, not a real implementation.
const client = new LlmClient({
  provider: 'openai',
})

const mcpClient = new McpClient({
  server: 'https://mcp.example.com',
})

const tools = await mcpClient.getTools() // Provide a weather tool, similar to the previous example.

const response = await client.responses.create({
  model: 'gpt-5.5',
  input: [
    { role: 'user', content: 'What is the weather like in Paris today?' },
  ],
  tools, // The tools are now provided by the MCP server, not hardcoded in the client.
})

Dépendance entre les appels d'outils

Avec cette approche, le modèle reçoit le résultat de chaque outil avant de pouvoir décider de la suite. Si une action nécessite trois appels d'outils dépendants, le modèle a besoin de trois tours, et les résultats intermédiaires rejoignent son contexte.

Soyons honnêtes, cela fonctionne très bien la plupart du temps. Ce n'est toutefois pas optimal dans tous les cas.

Avec le serveur MCP dans la boucle, la chaîne d'événements ressemble à ceci :

  1. Le client MCP se connecte au serveur MCP.
  2. Le client MCP demande au serveur MCP la liste des outils disponibles.
  3. Le serveur MCP renvoie la liste des outils disponibles au client MCP.
  4. Le client MCP enregistre les outils auprès du modèle.
  5. Le modèle appelle un outil, par exemple get_weather, avec les paramètres requis.
  6. Le client MCP reçoit la demande d'appel d'outil du modèle.
  7. Le client MCP envoie la demande d'appel d'outil au serveur MCP.
  8. Le serveur MCP exécute l'outil, par exemple en récupérant la météo du lieu indiqué.
  9. Le serveur MCP renvoie le résultat de l'exécution au client MCP.
  10. Le client MCP renvoie le résultat de l'exécution au modèle.

La bonne nouvelle, et la raison pour laquelle nous parlons d'une approche « uniforme », est que toutes les étapes liées à MCP restent identiques pour chaque client et chaque serveur MCP. Vous n'avez qu'à vous concentrer sur l'implémentation de l'outil.

Imaginez maintenant le contexte suivant. Vous travaillez sur un Drive qui contient de nombreux fichiers Markdown. Votre Agent est connecté à ce Drive grâce à un MCP qui propose deux outils : readFile et writeFile. Votre objectif est de créer un fichier qui contient tous les titres d'un fichier Markdown.

L'outil readFile est défini comme suit dans le serveur MCP :

ts
server.registerTool(
  'readFile',
  {
    title: 'Drive Read File',
    description: 'Read a Markdown file from the local Drive fixture.',
    inputSchema: z.object({
      path: z.string().min(1)
    }),
    annotations: {
      title: 'Drive Read File',
      readOnlyHint: true
    }
  },
  async ({ path }) => {
    const content = await readDriveFile(path)
    return { content: [{ type: 'text', text: content }] }
  }
)

L'outil writeFile est défini comme suit dans le serveur MCP :

ts
server.registerTool(
  'writeFile',
  {
    title: 'Drive Write File',
    description: 'Write a Markdown file to the local Drive fixture.',
    inputSchema: z.object({
      path: z.string().min(1),
      content: z.string()
    }),
    annotations: {
      title: 'Drive Write File',
      destructiveHint: true
    }
  },
  async ({ path, content }) => {
    await writeDriveFile(path, content)
    return { content: [{ type: 'text', text: JSON.stringify({ success: true }) }] }
  }
)

Avec l'approche MCP actuelle, le modèle doit appeler readFile pour lire le fichier, charger son contenu dans son contexte, en extraire les titres, puis appeler writeFile pour écrire ces titres dans un nouveau fichier. Le modèle doit donc charger l'intégralité du fichier dans son contexte. Celui-ci peut être très volumineux et provoquer un dépassement de contexte, une injection de prompt et des coûts inutiles en jetons, sans aucune garantie que le modèle extraira correctement les titres.

C'est précisément dans ce cas que le Code Mode montre son intérêt. Dans le prochain article, nous transformerons ce serveur MCP en serveur Code Mode. Nous verrons comment le modèle peut orchestrer les appels d'outils sans charger l'intégralité du contenu dans son contexte.

Pd

Merci de me lire ! Je m'appelle Estéban, et j'adore écrire sur le développement web et le parcours humain qui l'entoure.

Je code depuis plusieurs années maintenant, et j'apprends encore de nouvelles choses chaque jour. J'aime partager mes connaissances avec les autres, car j'aurais aimé avoir accès à des ressources aussi claires et complètes lorsque j'ai commencé à apprendre la programmation.

Si vous avez des questions ou souhaitez discuter, n'hésitez pas à commenter ci-dessous ou à me contacter sur Bluesky, X, et LinkedIn.

J'espère que vous avez apprécié cet article et appris quelque chose de nouveau. N'hésitez pas à le partager avec vos amis ou sur les réseaux sociaux, et laissez un commentaire ou une réaction ci-dessous, cela me ferait très plaisir ! Si vous souhaitez soutenir mon travail, vous pouvez me sponsoriser sur GitHub !

Continuer la lectureLaisser le modèle orchestrer les outils avec du code local

Réactions

Discussions

Ajouter un commentaire

Vous devez être connecté pour accéder à cette fonctionnalité.