L’argumentaire habituel des éditeurs de bots de support IA ressemble à ceci : connectez vos sources de données, nous les indexerons, et votre bot y puisera ses réponses.
Relisez-le en ingénieur. Connectez vos sources de données veut dire : construisez un export. Nous les indexerons veut dire : vos fiches clients vivent maintenant aussi dans le système d’un fournisseur. Et « y puisera ses réponses » veut dire : répondra à partir de ce que la dernière synchronisation a réussi à copier.
Pour la documentation, le compromis est acceptable : les articles changent chaque semaine, et un index leur convient bien. Pour tout ce qui change en direct, c’est un mauvais compromis. Les prix changent. Les stocks changent. Le statut d’une commande change pendant que le client tape. Un index, c’est une photo de quelque chose qui bouge.
L’autre option
Il existe ici une idée bien plus ancienne, qui n’oblige à déplacer vos données nulle part : donner une fonction au modèle et le laisser l’appeler.
C’est ce qu’est l’appel d’outils depuis ses débuts. Ce qui est inhabituel, c’est où la fonction s’exécute. Dans la plupart des implémentations, elle tourne sur les serveurs du fournisseur : vous voilà de nouveau à lui donner des identifiants, à autoriser ses adresses IP et à construire une intégration.
Kookee, lui, l’exécute dans le navigateur du visiteur.
KookeeChat.init({
apiKey: 'YOUR_API_KEY',
tools: [{
name: 'search_orders',
description: "Look up the signed-in customer's orders.",
parameters: { type: 'object', properties: { status: { type: 'string' } } },
handler: ({ status }) =>
fetch('/api/my/orders?status=' + status, { credentials: 'include' })
.then((r) => r.json()),
}],
});
Regardez ce qu’est ce handler. C’est un fetch vers votre API, avec credentials: 'include', qui tourne dans votre page, au nom du visiteur déjà connecté.
Trois conséquences en découlent, et elles sont tout l’intérêt :
Vous n’avez construit aucune intégration. Pas de clé API échangée, pas de liste d’IP autorisées, pas de webhook, pas d’application OAuth. L’endpoint que vous appelez est un endpoint que votre frontend appelle déjà.
Kookee ne garde jamais vos données. Le nom de l’outil, sa description et le schéma de ses paramètres parviennent à Kookee : assez pour que le modèle sache que l’outil existe et quoi lui passer. Le handler s’exécute dans le navigateur ; son résultat transite par Kookee jusqu’au modèle pour cette réponse, et n’est pas stocké. Votre table de commandes n’est pas dans la base de Kookee, parce que je ne l’ai jamais demandée.
L’authentification est déjà réglée. La question la plus difficile de « l’IA qui voit les données clients », c’est quel client. Ici, la question ne se pose même pas. La requête porte le cookie de session du visiteur lui-même : elle renvoie ses commandes et celles de personne d’autre, avec la garantie du même code d’autorisation qui protège le reste de votre application.
Ce que voit le visiteur
Visiteur : où en est ma dernière commande ?
Vérification de vos commandes…
Assistant : Votre commande n° 4821 a été expédiée le 21 août et arrivera mardi. Suivi : DHL 0034…
La pause au milieu, c’est la partie intéressante. Le tour s’arrête réellement : le modèle demande un outil, la réponse s’interrompt, le navigateur exécute votre fonction, le résultat repart, et la réponse reprend. Pour le visiteur, c’est une seule réponse continue avec une ligne d’état.
Comment j’en suis arrivé là
Ma première version renvoyait l’historique de la conversation au navigateur entre les deux moitiés, et laissait le client le renvoyer ensuite. Sans état, sans mémoire côté serveur, élégante sur un tableau blanc.
Deux relecteurs l’ont enterrée, pour des raisons qui méritent d’être répétées :
Elle laissait fuiter des données. La recherche d’articles d’aide de Kookee tourne côté serveur et renvoie le texte complet des articles, y compris les articles chatbot_only, qui sont tenus à l’écart du centre d’aide public. Faire transiter les messages du tour par le navigateur les aurait affichés dans l’onglet Network de quiconque ouvre les outils de développement.
Elle était falsifiable. Si le client détient l’état de la conversation, le client peut le modifier. Supprimez quelques entrées, et le compteur d’itérations repart à zéro : une boucle sans fin d’appels au modèle, sur le budget de quelqu’un d’autre.
Le tour en pause vit donc sur le serveur, dans Redis, derrière un jeton à usage unique qui expire vite. Le navigateur ne reçoit que ce dont il a besoin : quelles fonctions appeler, avec quels arguments, et un jeton opaque pour reprendre. Il ne voit pas les résultats des outils côté serveur et ne peut pas réécrire le tour.
Cette vérification du jeton a sa propre subtilité, que j’ai d’abord ratée : le code d’origine supprimait le jeton avant de vérifier à quel projet il appartenait. Personne ne pourrait deviner un jeton aléatoire de 24 octets, mais une requête venant du mauvais projet aurait quand même détruit un tour légitime en cours. Lire, vérifier, puis supprimer.
Garde-fous, et où je m’arrête
Ce que j’impose :
Les résultats des outils sont encapsulés comme des données non fiables, jamais comme des instructions.
Les marqueurs de citation en sont retirés, pour qu’un résultat d’outil ne puisse pas inventer une source.
Les handlers expirent au bout de 15 secondes ; les résultats sont plafonnés à 4 000 caractères.
Un outil qui lève une erreur renvoie cette erreur au modèle au lieu de faire échouer la réponse.
Et une chose que je ne fais pas, délibérément : aucune confirmation automatique avant l’exécution d’un outil. Si vous enregistrez un outil qui a des effets de bord (annuler une commande, envoyer un e-mail), il s’exécute dès que le modèle décide de l’appeler.
J’ai envisagé d’ajouter une boîte de confirmation pour chaque outil, et j’y ai renoncé. Une confirmation sur une recherche de produits en lecture seule, c’est de la friction sans aucun bénéfice, et un schéma JSON ne me dit pas si un outil lit ou écrit. Vous, vous le savez. Donc : enregistrez librement les outils de lecture, et placez un window.confirm dans tout handler qui écrit.
Quand ne pas s’en servir
Si la réponse se trouve dans votre documentation, écrivez un article. Les outils servent aux données qui changent plus vite que vous ne pouvez publier : prix, stocks, statut des commandes, état du compte. Tout ce qui est stable est moins cher, plus rapide et plus fiable sous forme d’article d’aide, et il est cité avec un lien que le visiteur peut ouvrir.
Ma règle empirique : s’il faut redéployer pour changer la réponse, c’est un outil. S’il suffit de modifier une page, c’est un article.
Disponible dès maintenant dans @kookee/react 1.5.0 et @kookee/sdk 1.5.0, ainsi que dans la balise script, sans rien installer. Dix outils par widget : je n’ai encore vu aucun assistant de support qui en ait vraiment besoin d’autant.