Tutoriel

Comment un Apple AI Agent connecte App, API et outils ? Quel rôle joue JSON ?

L'Agent système passe par App Intents, le modèle dans l'App par les Tool de Foundation Models. JSON n'est pas décoratif : c'est la forme partagée des contrats d'arguments, de la sortie structurée et des charges HTTP.

« Apple AI Agent » est souvent traité comme un nom de produit. En production, vous rencontrez au moins deux chaînes d'appel totalement différentes : Apple Intelligence choisit une App et une action pour l'utilisateur, ou votre App exécute un modèle on-device qui décide quels outils appeler. Les deux touchent App, API et outils, mais qui possède la session, qui génère les arguments et où JSON apparaît ne sont pas les mêmes. Séparez ces couches, sinon les Schema, les logs et le débogage n'ont aucun point d'appui.

D'abord : qui exécute le modèle ?

Commencez par qui a initié cette inférence. Si la personne parle à Siri ou à Apple Intelligence au niveau système, le modèle tourne côté système (sur l'appareil ou Private Cloud Compute). Votre App n'est que la capacité routée. Vous exposez App Intents : actions typées, entités et paramètres. L'Agent système décide quand appeler, votre code exécute perform. Entrée officielle : Documentation App Intents.

Si l'utilisateur est déjà dans votre App et que vous démarrez un LanguageModelSession de Foundation Models, c'est une autre chaîne : votre processus pilote le modèle et vous injectez les outils. Le système ne choisira pas « quelle App ouvrir », car la session est déjà dans l'App. Les outils peuvent lire les contacts, le calendrier, appeler votre propre API réseau, puis écrire les résultats dans le transcript. Notes du framework : Foundation Models. Pour l'appel d'outils au niveau session, voir WWDC25 Meet the Foundation Models framework.

Un troisième schéma devient courant : un hôte externe (Claude, ChatGPT, Agent maison) appelle votre service via MCP ou HTTPS classique. Le modèle ne tourne pas sur la pile Apple, mais la charge utile est presque toujours JSON. Les mêmes fonctions métier peuvent servir App Intents, Foundation Models Tool et HTTP — seuls les adaptateurs diffèrent.

Chaîne d'appel Qui exécute le modèle Ce que l'App fournit
Agent système Apple Intelligence App Intents / App Entity
Agent dans l'App Votre LanguageModelSession Protocole Tool et arguments @Generable
Agent externe Hôte tiers JSON-RPC ou REST JSON

Ne réduisez pas les trois chaînes à un seul diagramme « Agent universel ». En débogage, identifiez d'abord la chaîne : échec de routage système → déclarations Intent et types de paramètres ; appels d'outils anarchiques dans l'App → name, description et forme générable des Arguments ; API externe incohérente → HTTP JSON et Schema. Tout mélanger, et on croit que « le modèle fait n'importe quoi ».

App Intents : comment l'Agent système atteint votre App

Pour l'Agent système, une App n'est pas « ouvrir et regarder » — c'est un catalogue de capacités découvrables. Vous déclarez noms d'action, descriptions en langage naturel, types de paramètres et résultats avec AppIntent. Spotlight, Shortcuts, Siri et Apple Intelligence partagent ce catalogue. Quand quelqu'un dit « marquer cette facture comme payée », le système doit mapper l'énoncé à votre MarkInvoicePaid, pas laisser le modèle taper dans l'interface.

Les paramètres sont le point de jonction. Le système transforme la parole en valeurs typées : enums, dates, références AppEntity. En Swift vous voyez des structs, pas de prose. Entre processus ou frameworks, cette structure a toujours besoin d'une forme sérialisable — débogage, logs et rejeu serveur finissent en JSON ou liste de propriétés équivalente. Concevez les paramètres Intent comme un ensemble de champs « écrivable en objet JSON » et les adaptateurs API coûtent bien moins cher.

Compromis courant : l'Intent doit-il toucher le réseau ? Les actions courtes peuvent se terminer dans perform. Dès qu'il y a auth, pagination ou idempotence, perform ne fait que « valider les paramètres + appeler votre service métier » ; le service envoie la requête JSON. L'Agent système n'a pas besoin de votre chemin REST — il a besoin de la sémantique succès/échec de l'Intent. Vous avez besoin du chemin, car facturation, audit et retry vivent à la couche API.

Foundation Models : comment le modèle dans l'App appelle les outils

La connexion Agent dans l'App ressemble à « donner au modèle une fiche de fonctions ». Vous implémentez Tool: donnez-lui name, description, plus un @Generable Arguments. Le framework l'écrit dans le prompt pour que le modèle décide quand appeler. À l'appel, il génère d'abord les arguments, puis le framework exécute call(arguments:) et insère la valeur de retour (souvent String ou type générable) dans le transcript. Le modèle rédige la réponse finale. Vous ne lui demandez pas d'inventer des URL — de choisir parmi les outils que vous avez autorisés.

La génération d'arguments passe par la sortie structurée, pas par « répondez en JSON ». @Generable et les Schema dynamiques fixent champs, enums et objets imbriqués au décodage. En Swift vous obtenez des instances typées ; pour persister, logger ou passer à URLSession, encodez en JSON ensuite. Apple résume ces capacités en language understanding, structured output et tool calling — voir la vue d'ensemble Foundation Models.

struct FindOrders: Tool {
  let name = "findOrders"
  let description = "Find recent orders by customer email and status."

  @Generable
  struct Arguments {
    var email: String
    var status: String
    var limit: Int
  }

  func call(arguments: Arguments) async throws -> String {
    // Call your domain API, then return a compact summary.
    return "3 orders, latest is paid"
  }
}

Dans cet extrait, le modèle n'invente pas les noms de champs. Il doit remplir email, status, limit. La description décide s'il est sélectionné : trop large et il s'active pour tout ; trop étroite et il reste silencieux quand vous en avez besoin. Limitez la sortie d'outil — verser tout le JSON d'une commande dans le transcript brûle la fenêtre de contexte. Retournez un résumé, puis appelez un second outil pour un id si besoin.

Les outils peuvent s'enchaîner. Le modèle utilise la sortie du premier comme entrée du second ; le framework les exécute dans l'ordre. Garantissez l'idempotence dans la couche métier : marquer deux fois le même orderId payé ne doit pas facturer deux fois. La boucle Agent ne voit pas vos contraintes de base de données. Des arguments JSON valides ne garantissent pas un résultat métier correct.

Quel rôle joue JSON

Les types Swift sont le contrat à la compilation ; JSON est le contrat à l'exécution. Dès que l'Agent quitte le processus — appels backend, fichiers, autre modèle, fixtures de test — la forme doit devenir du texte indépendant du langage. JSON joue au moins trois rôles sur cette chaîne. En mélanger deux et vous obtenez « ça parse, mais tous les champs sont faux ».

1. Le format d'échange des arguments d'outils

Foundation Models représente les valeurs structurées en interne comme GeneratedContent. La vue de débogage la plus utile est souvent « à quoi ressemble cet Arguments encodé en JSON ». Noms, optionalité, tableau ou objet doivent rester stables. Loggez les arguments en JSON pour les aligner avec les logs d'accès API en production : le même customerId est-il bien arrivé au serveur ?

{
  "email": "ada@example.com",
  "status": "paid",
  "limit": 5
}

2. Le Schema de sortie structurée

Même sans outils, si vous voulez juste un résumé de facture, il faut un Schema : clés obligatoires, valeurs d'enum, montant en number ou string. Côté Apple on utilise @Generable ; les modèles cloud utilisent souvent JSON Schema. Quand les deux décrivent le même objet métier, partagez une même table de champs pour éviter que l'App dise totalCents pendant que l'API dit amount. Règles communes pour écrire des Schema : Understanding JSON Schema.

3. La charge de l'App vers l'API

Dès que le call d'un outil touche le réseau, JSON est le corps HTTP. L'Agent ne remplace pas la conception d'API : en-têtes d'auth, clés d'idempotence et objets d'erreur restent à vous. Le modèle ne remplit que les « paramètres métier » ; transport, pagination et limitation de débit restent du backend classique. Formez aussi les erreurs en JSON stable (code, message, retryable) pour que le modèle décide de changer d'outil ou d'expliquer l'échec.

Trois couches peuvent partager un même document Schema : paramètres Intent ⊂ Arguments outil ⊂ HTTP body. Les sous-ensembles simplifient les tests : une fixture JSON valide, puis API, puis Tool, puis adaptateur Intent. Un sur-ensemble (HTTP a trois champs internes de plus) convient aussi, mais ne montrez pas ces champs au modèle ou il commencera à « gentiment » remplir des clés que vous ne vouliez pas publier.

Brancher les API HTTP et MCP

Quand un outil dans l'App appelle REST, encodez explicitement. N'envoyez pas GeneratedContent tel quel en Data. Mappez vers votre modèle Codable, puis JSONEncoder. Vous contrôlez alors les raw values d'enum, formats de date et stratégie de clés (snake_case). Le modèle produit des valeurs métier ; l'encodeur gère les détails du protocole.

{
  "tool": "markInvoicePaid",
  "arguments": {
    "invoiceId": "inv_9f2",
    "paidAt": "2026-08-20T09:00:00Z"
  }
}

MCP transforme « nom d'outil + objet paramètres » en JSON-RPC. Pour les développeurs Apple, c'est juste un troisième adaptateur : le même markInvoicePaid(invoiceId:paidAt:), App Intent via perform, Foundation Models via Tool.call, MCP via tools/call. Ne réécrivez pas la logique métier pour MCP. Les Agents externes envoient plus souvent de mauvais types (nombres en chaînes), donc le serveur doit toujours valider — « c'est déjà du JSON » n'est pas une garantie.

Écrivez aussi les limites de confidentialité dans les descriptions d'outils. Un modèle on-device qui lit le calendrier n'est pas une licence pour POSTer des événements sur votre serveur. La sortie outil pour le modèle peut être un résumé local ; ne construisez du JSON d'upload que quand l'utilisateur veut clairement synchroniser. Révisez séparément la politique de sortie de données pour l'Agent système et l'Agent dans l'App, et rendez la source visible dans les logs.

Comment inspecter JSON en production

Pour intégrer un Agent, une autre consigne est moins utile que trois instantanés côte à côte : le JSON d'arguments généré par le modèle, le JSON HTTP envoyé et le JSON renvoyé par le serveur. Si les formes divergent, le bug est presque toujours dans la couche de mapping, pas « le modèle n'est pas assez intelligent ». Formatez, validez et comparez localement — plus rapide que de fixer des Optional dans la console Xcode.

Passez d'abord un échantillon de paramètres dans le navigateur : Formatage JSON pour confirmer le parse ; Validation JSON pour attraper virgules finales et mauvais types ; fixez les champs communs Intent / Tool / API avec JSON Schema ; puis JSON Diff pour comparer « sortie modèle » et « corps de requête réel ». La Structured Output cloud diffère en API, mais « contrat d'abord, parse ensuite » reste le même — voir Comment la Gemini API produit du JSON.

Gardez trois fichiers de fixtures : tool-args.valid.json, http-body.valid.json, http-error.json. En CI, validez les deux premiers avec le même Schema. Utilisez la fixture d'erreur pour vérifier que l'Agent dit à l'utilisateur un échec actionnable au lieu d'avaler retryable: true.

FAQ

App Intents et les Tool de Foundation Models peuvent-ils partager les mêmes paramètres ?

Partagez le modèle métier, mais ne supposez pas que le système peut exécuter directement votre protocole Tool. Les Intent servent à la découverte système et aux permissions ; les Tool au prompt de la session courante. Mettez un mapping entre les deux. Les fixtures JSON testent le modèle métier, pas un type de framework.

Pourquoi ne pas laisser le modèle émettre une requête HTTP complète ?

URL, en-têtes et signatures ne doivent pas être inventés par le modèle. Il remplit les champs métier ; le client envoie un modèle fixe. Sinon une hallucination frappe le mauvais environnement ou les mauvaises credentials.

JSON et @Generable entrent-ils en conflit ?

Non. @Generable est la génération et le décodage côté Swift ; JSON est la forme inter-langages et inter-réseau. Stabilisez d'abord une table de champs, puis générez macros Swift et JSON Schema.

Les valeurs de retour d'outil doivent-elles être du JSON ?

Pas forcément. Le modèle peut voir un court résumé texte ; le serveur doit recevoir du JSON. Ne mélangez pas les deux dans une seule chaîne à parser à la dure.

Conclusion et suite

Les Agents sur la pile Apple ne sont pas une prise unique. Ce sont trois fils composables : l'Agent système découvre et appelle l'App via App Intents ; le modèle dans l'App appelle votre code via Tool ; un hôte externe appelle le même service métier en JSON. JSON fait parler arguments, Schema et charges API la même forme. Les types couvrent la compilation, les Schema l'exécution, les contrôles métier la vérité.

Ensuite : listez trois à cinq actions métier. Pour chacune, écrivez un objet JSON minimal et un Schema, puis décidez s'il apparaît sur un Intent, un Tool ou HTTP. Stabilisez la forme avant d'ajouter des prompts et l'orchestration multi-outils.