Tutoriel - Utiliser les appels natifs sous autorité Touba
Ce tutoriel montre comment laisser un modèle proposer un appel de fonction natif sans lui confier l’exécution. Le programme Touba conserve ses contrats ; seule la représentation utilisée par le provider change.
À la fin, vous aurez un agent qui peut choisir et réutiliser ses skills avec l’interface native du SDK, tandis que le runtime continue de valider chaque décision.
Ce qui change, et ce qui ne change pas
| Élément | Comportement |
|---|---|
source .tb | inchangée ; aucune syntaxe spéciale n’est nécessaire |
| AIR et protocole runtime | inchangés |
| provider SDK | decisionInterface: "native" active les appels de fonctions du modèle |
| exécution | reste exclusivement réalisée par le runtime Touba |
| mode par défaut | structured tant que l’hôte ne choisit pas explicitement native |
L’interface native est utile lorsque le modèle choisi sait produire des appels
de fonctions stricts et des sorties structurées. Elle ne transforme pas une
skill, une function, une strategy ou un agent en tool Touba : elle
offre seulement une représentation commune pendant la décision.
Prérequis
- la commande
touba; - le SDK JavaScript
@touba/sdk; - Node.js pour charger le module provider ;
- un modèle qui prend en charge les appels de fonctions et les sorties structurées ;
- une clé fournisseur conservée dans l’environnement ou dans un secret manager hôte.
Installer l’archive SDK
Utiliser Node.js 24 et npm. Dans un terminal macOS/Linux :
mkdir -p native-calculator-demo
cd native-calculator-demo
mkdir -p providers vendor
npm init -y
node --input-type=module -e 'import {execFileSync} from "node:child_process"; import {copyFileSync} from "node:fs"; const v=JSON.parse(execFileSync("touba",["version","--json"],{encoding:"utf8"})); copyFileSync(v.distribution.sdk_archive,"vendor/touba-sdk.tgz");'
npm install --offline --ignore-scripts --save-exact ./vendor/touba-sdk.tgz
Étape 1 - Écrire le programme Touba
Créez native-calculator.tb :
module examples.native_calculator
skill Double
description "Doubles one integer and returns the result."
takes item as Int
gives Int
do
give item * 2
skill AddTen
description "Adds ten to one integer and returns the result."
takes item as Int
gives Int
do
give item + 10
agent Calculator
description "Selects declared arithmetic skills for the requested calculation."
takes start as Int
gives Int
responsibility "Starting from start, invoke Double on the initial value, invoke Double again on the observed result, then invoke AddTen on that result. Return the final observed integer."
uses Double
uses AddTen
callable_by strategy RunCalculation
strategy RunCalculation
description "Runs one governed calculation."
takes start as Int
gives Int
uses Calculator
do
Calculator
with start as start
into result
give result
Le périmètre de l’agent est déclaré par uses. Le provider ne pourra proposer
que Double ou AddTen. La règle callable_by continue de réserver l’entrée
dans Calculator à la strategy déclarée.
Compilez le programme :
touba compile native-calculator.tb --out native-calculator.air.json
Étape 2 - Activer l’interface native dans le provider
Créez providers/calculator.provider.mjs :
import { createOpenAiDecisionProvider } from "@touba/sdk";
export const provider_metadata = Object.freeze({
provider_kind: "sdk_ai",
provider_name: "calculator-provider",
provider_version: "1.0.0",
model_family: "openai",
prompt_template_id: "calculator-native-v1",
decision_schema_version: "touba-agent-decision-v1",
});
export const decideAgentCapability = createOpenAiDecisionProvider({
model: "gpt-4.1-nano",
decisionInterface: "native",
instructions: [
"Complete the requested calculation using observed data.",
"Use only capabilities authorized by the Touba runtime.",
"Return a final answer only after the required calculation is complete.",
].join(" "),
maxOutputTokens: 1000,
});
Les instructions contrôlées par l’application sont en anglais pour garder un protocole provider cohérent. Les entrées métier et les réponses peuvent rester dans la langue de l’utilisateur.
Pour un autre fournisseur compatible AI SDK, utilisez la même option avec
createAiSdkDecisionProvider :
Fournir un véritable objet modèle issu du provider installé à createAiSdkDecisionProvider({ model, decisionInterface: "native" }) ; voir le guide SDK.
Si le modèle refuse la température ou les appels forcés, ajouter respectivement
temperature: null ou nativeToolChoice: "auto" à cette configuration. Ce sont
des options de transport, pas des permissions : Touba refuse toujours une
sélection sans appel valide. Qualifier aussi les réglages de raisonnement du
connecteur et la limite de sortie avant d'exécuter un parcours payant.
Étape 3 - Lancer l’agent
Exposez la clé dans l’environnement, sans la placer dans le source, les inputs ou le fichier provider :
: "${TOUBA_AGENT_DECISION_OPENAI_API_KEY:=${OPENAI_API_KEY:?Set OPENAI_API_KEY in your shell or secret manager}}"
export TOUBA_AGENT_DECISION_OPENAI_API_KEY
Exécutez ensuite la strategy :
touba run native-calculator.air.json \
--entry strategy:examples.native_calculator/RunCalculation \
--agent-decision-provider providers/calculator.provider.mjs \
--agent-decision-provider-initial-mode capabilities \
--max-agent-steps 3 \
--max-provider-calls 8 \
--inputs-json '{"start":6}' \
--output json \
--trace-out native-calculator.trace.json > result.json
node --input-type=module -e 'import assert from "node:assert/strict"; import {readFileSync} from "node:fs"; const run=JSON.parse(readFileSync("result.json","utf8")); assert.equal(run.status,"success"); assert.equal(run.result,34); assert.deepEqual(run.trace.events.filter(e=>e.kind==="agent_capability_selected").map(e=>e.payload.selected_ref),["skill:examples.native_calculator/Double","skill:examples.native_calculator/Double","skill:examples.native_calculator/AddTen"]); console.log("NATIVE_CALCULATION_OK");'
Le mode initial capabilities évite un aller-retour quand vous savez que la
mission exige une capacité. Gardez le mode summary, qui est le défaut, si le
modèle doit pouvoir répondre sans consulter le catalogue.
Cette commande effectue des appels facturables au vrai fournisseur. Calcul attendu : 6 → 12 → 24 → 34. La même capacité est réutilisée. Une panne fournisseur ou un échec du modèle reste un échec de test ; ne pas substituer une réponse précalculée.
Cette séquence nécessite six appels fournisseur sans retry : trois sélections, deux orientations intermédiaires et une finalisation. La limite de huit laisse une marge de retry ; elle ne correspond pas à huit appels de skills.
Étape 4 - Comprendre le cycle réel
agent Touba
-> orientation structurée, ou catalogue initial explicitement demandé
-> catalogue autorisé construit par le runtime
-> fonctions natives dérivées des signatures
-> une opération ou un résultat typé proposé par le modèle
-> normalisation par le SDK, sans exécution
-> validation, puis exécution ou retour du résultat par le runtime
-> observation associée aux arguments de l’appel
-> décision suivante ou finalisation structurée
En phase catalogue, le SDK expose une fonction native par capacité autorisée,
ainsi que touba_finish pour un résultat final conforme au gives. Le modèle
doit proposer exactement un appel. Le SDK ne fournit pas de fonction
execute au fournisseur : un appel reçu n’a donc encore produit aucun effet.
Le runtime reçoit ensuite une décision Touba normale et vérifie la cible, les
arguments et l’autorité avant d’entrer dans la capacité. touba_finish devient
une décision answer_directly, sans effet métier ; la réponse doit respecter
le gives de l’agent. Une prose libre sans ce contrôle reste refusée.
Étape 5 - Réutiliser une capacité
Une capacité autorisée n’est pas consommée après son premier appel. L’agent peut
sélectionner Double plusieurs fois, avec les mêmes arguments ou avec des
arguments différents. Chaque invocation est validée et comptée séparément.
La réutilisation s’arrête lorsque la mission est terminée ou lorsqu’une limite s’applique, par exemple :
maxAgentStepspour les étapes agentiques ;max_provider_callspour les appels au provider ;max_tool_callspour les tools réellement dispatchés ;- les budgets de durée et d’exécution déclarés par le projet.
Le champ historique des capacités utilisées sert à décrire ce qui s’est passé. Il ne constitue pas une liste de capacités interdites pour la suite.
Étape 6 - Vérifier la frontière d’autorité
L'historique conversationnel est actif par défaut : le SDK conserve les messages
du connecteur entre les étapes de cette invocation. Les deux appels à Double
restent deux opérations distinctes, chacun avec son résultat runtime. Pour
observer sa taille, ajoutez history: { onEvent: event => console.info(event) }
à la configuration du provider. Ces événements ne contiennent pas les messages.
Pour borner la taille ou reprendre après redémarrage avec SQLite, consultez le
guide de l'historique conversationnel.
Le stockage ne remplace ni les checkpoints ni le journal des effets.
| Proposition du modèle | Contrôle appliqué par Touba |
|---|---|
| fonction native choisie | la cible doit appartenir à uses |
| arguments produits | la signature et les types sont revérifiés |
| appel d’un autre agent | callable_by est revérifié à chaque invocation |
| accès mémoire | scopes source, grants et resource bindings restent requis |
| usage d’un secret | seul le handle autorisé peut traverser ; la valeur résolue reste hors prompt |
| demande de validation | la décision appartient à l’humain, pas au modèle |
| effet externe | manifest, grants, identité d’effet et journal runtime restent applicables |
| reprise | les observations conservées ne redéclenchent pas les effets passés |
Un nom de fonction inconnu, plusieurs appels dans une même proposition, des arguments invalides ou une réponse fournisseur incomplète sont refusés avant l’exécution de la capacité.
Étape 7 - Inspecter le résultat
touba inspect native-calculator.trace.json
La trace permet de distinguer la décision, la capacité sélectionnée, la consommation des budgets, l’exécution et la réponse terminale. Elle ne doit pas contenir la clé fournisseur ni une valeur secrète résolue.
Choisir entre structured et native
Choisir structured | Choisir native |
|---|---|
| comportement par défaut, portable | modèle fiable sur les appels de fonctions stricts |
| provider ne prenant pas en charge les fonctions natives | meilleure adéquation avec l’interface native du fournisseur |
intégration existante fondée sur generateObject | catalogue riche avec signatures directement exposées |
Les deux interfaces utilisent le même protocole runtime et la même frontière
d’autorité. Changer d’interface ne demande pas de modifier le programme .tb.
Erreurs fréquentes
| Symptôme | Vérification |
|---|---|
| aucun appel natif n’est produit | vérifier le support des appels de fonctions par le modèle et le transport |
| décision refusée | vérifier uses, callable_by, les descriptions et les types d’arguments |
réponse IncompleteModelDecision | vérifier que la génération fournisseur est réellement terminée |
| limite atteinte avant la réponse | ajuster les budgets sans supprimer les contrôles nécessaires |
| observation indisponible en clair | vérifier si opaque_refs a été choisi volontairement |
Pour approfondir, lire le guide du SDK Touba et le concept de décision agentique gouvernée.