Acte 2 : Tool calling, le LLM discute avec votre app.

Intelligence artificielle 6 juin 2026

Mon assistant a maintenant un system prompt qui fait de lui un expert RH, une mémoire qui permet de se souvenir de tout sur dix tours de conversation, et un advisor qui comme un interceptor peut tout capturer avant ou après l'appel au LLM. Je lui pose ainsi une question parfaitement légitime pour ce rôle :

Le modèle me répond qu'il n'a pas accès à la donnée et me renvoie poliment vers le logiciel RH de mon entreprise. Et il a raison de le faire.

C'est la frontière qu'on ne franchit pas avec ce qu'on a vu jusqu'ici. Le LLM peut raisonner, structurer et proposer un plan en plusieurs étapes mais il ne peut rien aller chercher dans votre SI ni rien y déclencher. Il vit dans sa bulle, et votre base des employés, vos APIs internes lui sont invisibles.

Qu'est ce qu'un tool calling ?

Le rôle du tool calling est d'offrir au LLM la capacité de chercher une information dans votre SI. Le LLM reste à la base prédictif, c-a-d qu'il génère du texte token par token. Ici on lui donne simplement la possibilité de demander à notre application d'exécuter un appel à sa place avant de lui rendre la main.
En gros, nous disons donc au LLM : "tu n'as pas accès à ces données directement, mais si tu me demandes les congés d'un employé, je peux aller te les chercher." C'est comme un contrat entre le LLM et mon application, c'est ce que Spring AI appelle un tool.

Comment ça marche concrètement

Petite mise en garde avant de rentrer dans le mécanisme : tous les modèles ne supportent pas le tool calling. Vous pouvez vérifier ce que Spring AI prend en charge selon le modèle choisi ici : https://docs.spring.io/spring-ai/reference/api/chat/comparison.html

Maintenant, comment ça marche. Que votre assistant soit Claude Code, Gemini CLI, ou celui que nous sommes en train de construire avec Spring AI, le principe est le même, quand l'utilisateur pose une question, l'assistant l'envoie au LLM avec, en gros, ce message « voici la question, et voici ce que je sais faire si tu me le demandes ».
Concrètement (comme sur le schéma ci-dessus), l'assistant fournit au LLM la liste de tous ses tools : nom, description, input schema. Côté Spring AI, c'est le rôle de l'interface ToolDefinition. Elle expose ces trois méthodes : name(), description() et inputSchema().
Le LLM décide s'il veut appeler un tool, il renvoie une réponse contenant le nom du tool et les paramètres pour l'exécuter. Votre assistant retrouve l'outil correspondant et l'exécute via la méthode call() de l'interface ToolCallback. Il renvoie ensuite le résultat au modèle, qui génère la réponse finale affichée à l'utilisateur. La bonne nouvelle est que Spring AI gère tous ces allers-retours pour vous par défaut. Il suffit de définir le tool. Voyons ça sur deux exemples.

Pour les exemples plus complexes, il y a des alternatives programmatiques (MethodToolCallback, FunctionToolCallback) et même de la résolution dynamique de tools déclarés comme beans (ToolCallbackResolver). On n'en aura pas besoin ici, la doc Spring AI les détaille si vous y arrivez un jour. Je vous mets le lien quand même ici.

Exemple 1 : Retrouver une information

On veut récupérer les congés d'un employé via notre assistant. Dans la vraie vie, la méthode qui implémente ce tool ferait un RestTemplate.getForObject(...) vers l'API du logiciel RH. Pour ne pas alourdir l'article avec un second service à monter, je remplace cet appel HTTP par deux repositories locaux, EmployeeRepository et LeaveRepository, adossés à des données de test. Le mécanisme du tool calling, lui, est le même.
Au lieu d'exposer une API, on définit un tool :

@Component
public class Rhtools {

    private final EmployeeRepository employeeRepository;
    private final LeaveRepository leaveRepository;

    public Rhtools(EmployeeRepository employeeRepository, LeaveRepository leaveRepository) {
        this.employeeRepository = employeeRepository;
        this.leaveRepository = leaveRepository;
    }

    @Tool(description = "Récupère la liste des congés d'un employé à partir de son nom")
    public List<Leave> getLeavesDataByEmployeeName(
        @ToolParam(description = "Nom de l'employé") String employeeName
    ) {
        return employeeRepository.findByName(employeeName)
            .map(employee -> leaveRepository.findByEmployeeId(employee.id()))
            .orElse(List.of());
    }
}

L'annotation @Tool au-dessus de getLeavesDataByEmployeeName expose la méthode au LLM. Attention ici la description, c'est du prompt : le texte que le modèle lit pour décider s'il appelle le tool, et quand. La description doit donc être claire et précise et doit correspondre à ce que fait exactement la méthode. Une description floue et la méthode sera ignorée par le LLM. Même logique pour l'annotation @ToolParam, elle permet au modèle de savoir quel paramètre fournir et à quoi il sert.

Pour que le LLM soit au courant de ce tool, on peut le lui donner au travers du ChatClient comme ceci :

public ChatController(ChatClient.Builder chatClientBuilder,
                      MessageChatMemoryAdvisor messageChatMemoryAdvisor,
                      LoggingAdvisor loggingAdvisor,
                      Rhtools rhtools) {
    this.chatClient = chatClientBuilder
        .defaultSystem("Tu es un expert en gestion des resources humaines et tu réponds de manière concise et précise.")
        .defaultAdvisors(loggingAdvisor, messageChatMemoryAdvisor)
        // On passe les tools ici
        .defaultTools(rhtools)
        .build();
}

Le résultat donne ceci :

Exemple 2 : Exécuter une action

Jusqu'ici, notre assistant se contentait de lire : on l'interroge, il va chercher l'info. On veut le laisser agir. C'est justement tout l'intérêt quand on parle de agentic ce buzzword qu'on entend partout. On parle ici d'une IA qui agit. Elle déclenche de vraies opérations dans le système. Concrètement, c'est un tool de plus sauf que celui-là écrit en base au lieu de lire.

@Tool(description = "Planifie un congé pour un employé et marque l'employé comme indisponible pendant la durée du congé")
public Leave scheduleLeave(
      @ToolParam(description = "Nom de l'employé") String employeeName,
      @ToolParam(description = "Motif du congé (ex: Congés annuels, Maladie, Mariage)") String reason,
      @ToolParam(description = "Date de début du congé au format YYYY-MM-DD") String date,
      @ToolParam(description = "Durée du congé (ex: '5 jours', '2 semaines')") String duration
    ) {
    Employee employee = employeeRepository.findByName(employeeName)
            .orElseThrow(() -> new IllegalArgumentException("Employé introuvable : " + employeeName));

    Leave leave = leaveRepository.save(new Leave(reason, date, duration, "APPROVED", employee.id()));

    employeeRepository.save(new Employee(
        employee.id(),
        employee.name(),
        employee.salary(),
        employee.department(),
        employee.position(),
        employee.email(),
        STATUT_INDISPONIBLE
    ));

    return leave;
}

En passant en paramètre une instance de notre classe Rhtools, toutes les méthodes annotées par @Tool sont lues et ajoutées à la liste des tools envoyés au LLM.

On lance la requête en langage humain :


Vous remarquerez que le message n'est pas forcément précis : date mal formatée, pas de motif. Et pourtant le tool part avec des paramètres nickel : 2026-12-01, 3 semaines, motif Congés annuels, un libellé que je n'ai jamais tapé, le modèle l'a pioché dans les exemples de mes @ToolParam. C'est tout l'intérêt des description qu'on a écrit plus haut. Elles apprennent au modèle à remplir l'appel à partir d'une phrase floue.
Et on voit bien qu'une ligne a été insérée en base :

L'assistant a agi pour de vrai.

Conclusion

Le tool calling, c'est le moment où l'assistant arrête de parler pour agir. Vous lui balancez une phrase bien humaine et mal foutue, du genre « Mayacine part en congés au mois décembre pour 3 semaines », et il effectue un appel propre et structuré qui va écrire une vraie ligne dans votre base. Et voilà, Mayacine est bien en congé, il suffisait de le dire.

Cependant on peut bien voir qu'il a tranché à notre place sur certains choix. Par exemple le type de congés ou encore le 1er décembre 2026. Et si votre agent vous posait la question avant de trancher ? C'est ce qu'on verra dans notre prochain épisode.

Mots clés

Mohammed Diedhiou

Solutions Architect | AI engineer.