- Donner au modèle une base de données et trois outils pour la lire
- Décrire ces outils afin que le modèle sache quand recourir à chacun
- Exécuter la boucle qui transforme les appels d’outils en résultats d’outils
- Le regarder demander plusieurs outils à la fois
- Renvoyer les erreurs au modèle plutôt que de les lever
- Tracer la frontière entre ce que le modèle refuse de faire et ce qu’il ne peut pas faire
Préparation
Vous avez besoin de Python 3.9 ou plus récent, du paquetrequests, et d’une clé d’API Venice. Consultez Générer une clé d’API si vous n’en avez pas. Tout le reste se trouve dans la bibliothèque standard.
agent.py avec les imports et l’en-tête que chaque appel réutilise :
GET /models/traits associe des noms de traits stables au modèle qui remplit actuellement ce rôle. Lire function_calling_default au démarrage fait que votre agent continue de fonctionner lorsque le modèle sous-jacent est remplacé. Consultez Modèles pour la liste complète des traits.1. Une base de données qui vaut la peine d’être interrogée
N’importe quel fichier SQLite fera l’affaire. Celui-ci est une petite boutique avec des clients, des produits, et les commandes qui les relient, ce qui suffit pour qu’une vraie question nécessite une jointure et une agrégation :2. Trois outils à la portée du modèle
Les outils reflètent la façon dont une personne aborde une base de données inconnue : découvrir ce qu’elle contient, examiner de près une table, puis la requêter.description n’est pas un commentaire. C’est la seule chose que lit le modèle lorsqu’il décide quel outil appeler et ce qu’il faut y mettre :
3. La boucle
L’appel de fonctions est une conversation, pas une requête. Le modèle répond par des appels d’outils, vous les exécutez, vous ajoutez les résultats, puis vous redemandez. Cela se termine lorsque le modèle répond avec du contenu au lieu d’appels.messages avant les résultats. Il porte les tool_calls auxquels les résultats répondent, et sur un modèle de raisonnement il porte aussi un champ reasoning_content. Reconstruire le message à la main en supprimant des champs auxquels vous ne vous attendiez pas est la façon la plus courante de casser le second tour.
Chaque résultat est apparié à son appel via tool_call_id. Rien d’autre ne l’identifie.
max_rounds est une vraie limite, pas une formalité. Un modèle qui continue de requêter sans conclure bouclera sinon jusqu’à épuisement de votre patience ou de vos crédits.
4. Ce que cela fait réellement
Branchez un bloc principal et lancez-le :stderr au fil de leur exécution, afin que vous puissiez le regarder travailler :
Le tour 4 est la partie qu’un appel de fonction unique ne peut pas faire. Le modèle ne pouvait pas écrire cette requête tant qu’il n’avait pas vu la réponse à la précédente.
Votre exécution ne correspondra pas appel pour appel à celle-ci. Le modèle décrit parfois les trois tables en une seule fois et parfois une par une, et il lui arrive de sauter
list_tables et de deviner un nom. Les chiffres sont stables parce qu’ils proviennent de la base de données ; le chemin pour y arriver ne l’est pas.
Le tour 2 a renvoyé trois appels d’outils dans une seule réponse, et la boucle ci-dessus les exécute l’un après l’autre. Ils sont indépendants, donc un
ThreadPoolExecutor ici vaut la peine dès que vos outils font de vraies E/S. Conservez les messages tool dans le même ordre que les appels qui les ont produits.usage montre que cela paie :
5. Laissez les erreurs remonter jusqu’au modèle
L’instinct est de lever une exception sur une mauvaise requête. Résistez-y. Une erreur est une information, et le modèle peut agir dessus. Demandez une table qui n’existe pas :run_query a renvoyé {"error": "OperationalError: no such table: purchases"} comme un résultat d’outil ordinaire au lieu de lever une exception, le modèle l’a lu, a appelé list_tables pour découvrir ce qui existait bel et bien, et s’est corrigé. Si l’exception s’était propagée, le script serait mort sur une faute de frappe.
C’est pourquoi chaque outil renvoie du JSON, y compris sur le chemin d’échec. La règle est simple : si une personne qui débogue votre outil voudrait voir le message, le modèle aussi.
6. Ce qu’il refuse de faire, et ce qu’il ne peut pas faire
Demandez à l’agent de détruire quelque chose :SELECT pour les clients espagnols, n’en a trouvé aucun parce que la colonne stocke ES et non Spain, et a signalé cela à la place :
run_query est la partie qui ne dépend pas d’un choix :
Cette deuxième ligne est la raison pour laquelle
run_query attrape sqlite3.Warning en plus de sqlite3.Error. Le pilote Python refuse les instructions empilées, mais il lève Warning pour cela, et Warning n’est pas une sous-classe de Error. N’attraper que sqlite3.Error laisse une instruction empilée s’échapper du gestionnaire et tuer la boucle au lieu de renvoyer un message que le modèle peut lire.Contrôler quand les outils sont utilisés
tool_choice décide de la latitude laissée au modèle :
"required" est plus brutal qu’il n’y paraît. Demander à cet agent What is 2 + 2? avec tool_choice réglé sur "required" le fait appeler list_tables, examiner une base dont il n’a que faire, puis répondre 4 au tour suivant. Avec "auto", il répond 4 immédiatement et n’appelle rien. Recourez à "required" quand un outil doit véritablement s’exécuter, par exemple pour journaliser une requête, et laissez-le tranquille sinon.
Régler l’agent
Étapes suivantes
La boucle que vous avez maintenant est la même que celle derrière la plupart des agents. Seuls les outils changent.- Remplacez les outils SQL par des appels HTTP et cela devient un agent d’API.
- Ajoutez Recherche et scraping Web comme outil et il peut consulter le Web en direct au milieu d’une réponse.
- Demandez un résultat typé plutôt que de la prose avec Réponses structurées.
- Voyez une version plus grande de ce motif dans l’Agent de recherche privé.
Appel de fonctions
Référence pour le tableau tools et tool_choice.
Réponses structurées
Contraindre la réponse finale à un schéma JSON.
Mise en cache des prompts
Garder la conversation qui grossit peu coûteuse.
Agent de recherche privé
La même boucle avec des outils Web et un planificateur.