Nouveau dans Symfony 8.2 (en développement). Un agent IA appelle les outils d'une application à partir d'un schéma JSON de leurs arguments : MCP, les API d'OpenAI ou d'Anthropic le demandent tous. Écrit à la main, ce schéma dérive dès qu'on touche au code : un paramètre ajouté manque, une borne change d'un côté et pas de l'autre, et l'agent envoie des arguments que l'outil refuse.
Le composant JsonSchema
Le nouveau composant JsonSchema lit le schéma sur le code PHP. generateForParameters() part des paramètres d'une méthode :
use Symfony\Component\JsonSchema\Attribute\JsonSchemaConstraint; use Symfony\Component\JsonSchema\SchemaGeneratorInterface; final class ConvertirMontant { /** * @param int $centimes Le montant à convertir, en centimes * @param string $devise La devise cible */ public function __invoke( #[JsonSchemaConstraint(minimum: 0)] int $centimes, string $devise = 'EUR', ): int { // … } } $parametres = new \ReflectionMethod(ConvertirMontant::class, '__invoke')->getParameters(); $schema = $generateur->generateForParameters($parametres); // SchemaGeneratorInterface, injecté
{ "type": "object", "properties": { "centimes": {"type": "integer", "description": "Le montant à convertir, en centimes", "minimum": 0}, "devise": {"type": "string", "default": "EUR", "description": "La devise cible"} }, "required": ["centimes"] }
Le type, null compris, et la valeur par défaut viennent de la signature ; un paramètre sans valeur par défaut est obligatoire. La description vient des @param de la PHPDoc. Les contraintes de validation d'une classe (#[Assert\Email], #[Assert\NotBlank]…) deviennent des mots-clés du schéma, et #[JsonSchemaConstraint] ajoute ceux qu'aucune contrainte ne donne. Sur un paramètre de méthode, seul #[JsonSchemaConstraint] est lu : un #[Assert\Range] y serait ignoré.
Un objet en argument est décrit une fois dans $defs, puis désigné par un $ref. Tous les clients d'agents ne suivent pas ces références : ->flatten() recopie chaque définition à sa place.
Le générateur sait aussi décrire une classe entière (generate()), et le pont avec le Serializer respecte les groupes et les noms de propriétés.
À faire
GET /outils décrit à un agent les deux outils de la médiathèque, avec un schéma écrit à la main en septembre. Depuis, RechercherLivres a pris un paramètre $langue, et le catalogue renvoie jusqu'à 100 livres au lieu de 50.
Générez les schémas depuis les méthodes __invoke() des outils, aplatis. Bornez $limite de 1 à 100 et n'acceptez pour $langue qu'un code de deux lettres minuscules (^[a-z]{2}$).