Normalisation des schémas

ADK-Rust normalise automatiquement les schémas de Tool MCP pour chaque fournisseur de LLM au moment de la requête. Cela signifie que les Tools MCP fonctionnent de manière transparente avec Gemini, OpenAI, Anthropic et d'autres fournisseurs sans ajustement manuel du schéma.

Comment ça marche

MCP Server → raw JSON Schema → McpToolset (stores verbatim)
                                      ↓
                          Model.generate_content()
                                      ↓
                          SchemaAdapter.normalize_schema()
                                      ↓
                          Provider API (Gemini/OpenAI/Anthropic)
  1. McpToolset découvre les Tools et stocke leurs inputSchema bruts sans modification.
  2. Lorsque le modèle construit une requête, il appelle schema_adapter().normalize_schema() sur le schéma de chaque Tool.
  3. Chaque fournisseur a son propre adapter qui applique uniquement les transformations requises par son API.
  4. Les résultats sont mis en cache par hachage de contenu pour éviter une normalisation redondante.

Comportement du fournisseur

FonctionnalitéGeminiOpenAI StrictOpenAIAnthropicGénérique
résolution $ref✅ En ligne❌ Préserve❌ Préserve❌ Préserve❌ Préserve
anyOf/oneOfRéduitPréservePréservePréservePréserve
allOfFusionnePréservePréservePréservePréserve
additionalPropertiesSupprimeDéfinit falsePréservePréservePréserve
Tableaux de typesRéduitPréservePréservePréservePréserve
$schemaSupprimeSupprimeSupprimeSupprimeSupprime
if/then/elseSupprimeSupprimeSupprimeSupprimeSupprime
constenumenumenumPréserveenum
format non pris en chargeSupprimeSupprimeSupprimePréserveSupprime
Limite de profondeur d'imbrication5 niveauxAucunAucunAucunAucun
exclusiveMin/MaxSupprimePréservePréservePréservePréserve

Le trait SchemaAdapter

Tous les adaptateurs implémentent ce trait de adk-core:

use serde_json::Value;
use std::borrow::Cow;

pub trait SchemaAdapter: Send + Sync + std::fmt::Debug {
    /// Normalize a raw JSON Schema for this provider.
    fn normalize_schema(&self, schema: Value) -> Value;

    /// Normalize a tool name (default: truncate to 64 bytes at UTF-8 boundary).
    fn normalize_tool_name<'a>(&self, name: &'a str) -> Cow<'a, str>;

    /// Fallback schema when no parameters_schema is provided.
    fn empty_schema(&self) -> Value;
}

Chaque implémentation de Llm retourne son adaptateur via schema_adapter():

use adk_core::Llm;

let model = GeminiModel::new(&api_key, "gemini-2.5-flash")?;
let adapter = model.schema_adapter(); // Returns &GeminiSchemaAdapter

Adaptateurs disponibles

GeminiSchemaAdapter

L'adaptateur le plus agressif. Applique toutes les transformations destructives requises par l'API d'appel de fonctions de Gemini:

use adk_gemini::schema_adapter::GeminiSchemaAdapter;
use adk_core::SchemaAdapter;

// Standard Gemini API
let adapter = GeminiSchemaAdapter::new();

// Vertex AI (sets additionalProperties: false instead of removing)
let adapter = GeminiSchemaAdapter::vertex_ai();

Pipeline de transformation :

  1. Résout $ref (en ligne à partir des définitions, rompt les cycles à la profondeur 10)
  2. Supprime $schema
  3. Réduit anyOf/oneOf → premier sous-schéma non nul
  4. Fusionne les sous-schémas allOf
  5. Réduit les tableaux de types (["string", "null"]"string")
  6. Supprime if/then/else
  7. Convertit constenum à élément unique
  8. Supprime null des tableaux enum
  9. Ajoute type: "object" implicite
  10. Supprime les mots-clés non pris en charge
  11. Supprime les valeurs format non prises en charge
  12. Applique la profondeur d'imbrication (5 niveaux)
  13. Supprime definitions/$defs

OpenAiStrictSchemaAdapter

Préserve la structure du schéma tout en ajoutant additionalProperties: false pour les sorties structurées :

use adk_model::openai::OpenAiStrictSchemaAdapter;
use adk_core::SchemaAdapter;

let adapter = OpenAiStrictSchemaAdapter;

OpenAiSchemaAdapter

Correctifs minimaux sécurisés pour le mode non strict :

use adk_model::openai::OpenAiSchemaAdapter;

let adapter = OpenAiSchemaAdapter;

AnthropicSchemaAdapter

Quasi-transparent — Anthropic prend en charge la plupart des fonctionnalités de JSON Schema :

use adk_model::anthropic::AnthropicSchemaAdapter;

let adapter = AnthropicSchemaAdapter;

GenericSchemaAdapter

Par défaut pour les fournisseurs inconnus (Ollama, DeepSeek, etc.) :

use adk_core::GenericSchemaAdapter;

let adapter = GenericSchemaAdapter;

Mise en cache des schémas

Les schémas normalisés sont mis en cache par hachage de contenu afin d'éviter les calculs redondants :

use adk_core::{SchemaCache, GenericSchemaAdapter, SchemaAdapter};
use serde_json::json;

let cache = SchemaCache::new();
let adapter = GenericSchemaAdapter;
let schema = json!({"type": "object", "properties": {"name": {"type": "string"}}});

// First call normalizes and caches
let result = cache.get_or_normalize(&schema, &adapter);

// Subsequent calls return cached result (no re-normalization)
let cached = cache.get_or_normalize(&schema, &adapter);

// Invalidate when tools change
cache.clear();

Le cache réside sur les instances de modèle et est automatiquement utilisé pendant generate_content().

Troncation des noms d'outils

Tous les adaptateurs tronquent les noms d'outils dépassant 64 octets aux limites des caractères UTF-8 valides :

use adk_core::SchemaAdapter;
use adk_gemini::schema_adapter::GeminiSchemaAdapter;

let adapter = GeminiSchemaAdapter::new();

// Short names pass through unchanged
let name = adapter.normalize_tool_name("get_weather");
assert_eq!(name, "get_weather");

// Long names are truncated to 64 bytes
let long = "mcp_server_github_com_organization_repository_pull_request_review_comments";
let truncated = adapter.normalize_tool_name(long);
assert!(truncated.len() <= 64);

// Multi-byte characters are never split
let emoji_name = "🔧_tool_名前が長い";
let result = adapter.normalize_tool_name(emoji_name);
assert!(std::str::from_utf8(result.as_bytes()).is_ok());

Utilitaires partagés

Le module adk_core::schema_utils fournit des fonctions de transformation composables que les adaptateurs utilisent en interne :

use adk_core::schema_utils;
use serde_json::json;

let mut schema = json!({
    "$schema": "http://json-schema.org/draft-07/schema#",
    "type": "object",
    "properties": {
        "status": { "const": "active" },
        "email": { "type": "string", "format": "hostname" }
    },
    "if": { "properties": { "x": { "type": "number" } } },
    "then": { "required": ["x"] }
});

// Apply individual transforms
schema_utils::strip_schema_keyword(&mut schema);
schema_utils::strip_conditional_keywords(&mut schema);
schema_utils::convert_const_to_enum(&mut schema);
schema_utils::strip_unsupported_formats(&mut schema, &["date-time", "email", "uri"]);

Utilitaires disponibles :

  • strip_schema_keyword — supprime $schema
  • strip_conditional_keywords — supprime if/then/else
  • add_implicit_object_type — ajoute type: "object" quand properties existe
  • convert_const_to_enum — convertit const en enum à élément unique
  • strip_unsupported_formats — supprime les valeurs de format non autorisées
  • strip_null_from_enum — supprime le null des tableaux d'énumérations
  • truncate_tool_name — tronque à la limite UTF-8
  • resolve_refs — intègre les références $ref des définitions
  • collapse_combiners — réduit anyOf/oneOf au premier non-nul
  • merge_all_of — fusionne les sous-schémas allOf
  • collapse_type_arrays — réduit ["string", "null"] à "string"
  • enforce_nesting_depth — remplace les schémas profonds par {"type": "object"}

Adaptateurs personnalisés

Implémentez SchemaAdapter pour les fournisseurs personnalisés :

use adk_core::{SchemaAdapter, schema_utils};
use serde_json::Value;
use std::borrow::Cow;

#[derive(Debug)]
struct MyProviderAdapter;

impl SchemaAdapter for MyProviderAdapter {
    fn normalize_schema(&self, mut schema: Value) -> Value {
        // Apply only the transforms your provider needs
        schema_utils::strip_schema_keyword(&mut schema);
        schema_utils::strip_conditional_keywords(&mut schema);
        schema_utils::add_implicit_object_type(&mut schema);
        // Keep everything else as-is
        schema
    }
}

Exemple

Exécutez la démo de normalisation de schéma pour voir tous les adaptateurs en action :

cd examples/schema_normalization
cargo run

Aucune clé API n'est nécessaire — démontre la logique de normalisation localement.

Migration depuis sanitize_schema

Si vous vous appuyiez auparavant sur McpToolset renvoyant des schémas pré-sanitisés :

  • Aucune modification de code n'est nécessaire pour une utilisation standard. L'API du trait Toolset est inchangée.
  • Les schémas sont maintenant normalisés au moment de la requête par l'adaptateur de modèle, et non lors de l'enregistrement de l'outil.
  • Si vous appeliez parameters_schema() et attendiez une sortie au format Gemini, le schéma brut est maintenant renvoyé à la place. Utilisez l'SchemaAdapter approprié pour le normaliser vous-même si nécessaire.

Précédent: ← MCP Tools | Suivant: Sessions →