Normalisation des schémas

ADK-Rust normalise automatiquement les schĂ©mas d’outils de MCP pour chaque fournisseur LLM au moment de la requĂȘte. Cela signifie que les outils MCP fonctionnent de maniĂšre transparente avec Gemini, OpenAI, Anthropic et d’autres fournisseurs, sans ajustement manuel des schĂ©mas.

Fonctionnement

MCP Server → raw JSON Schema → McpToolset (stores verbatim)
                                      ↓
                          Model.generate_content()
                                      ↓
                          SchemaAdapter.normalize_schema()
                                      ↓
                          Provider API (Gemini/OpenAI/Anthropic)
  1. McpToolset détecte les outils et stocke leur inputSchema brut sans modification
  2. Lorsque le modĂšle construit une requĂȘte, il appelle schema_adapter().normalize_schema() sur le schĂ©ma de chaque outil
  3. Chaque fournisseur possĂšde son propre adaptateur, qui applique uniquement les transformations requises par son API
  4. Les rĂ©sultats sont mis en cache Ă  l’aide d’un hachage du contenu afin d’éviter toute normalisation redondante

Comportement des fournisseurs

FonctionnalitéGeminiOpenAI StrictOpenAIAnthropicGénérique
RĂ©solution de $ref✅ IntĂšgre❌ 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
const→ enum→ enum→ enumPrĂ©serve→ enum
format non pris en chargeSupprimeSupprimeSupprimePréserveSupprime
Limite de profondeur d'imbrication5 niveauxAucuneAucuneAucuneAucune
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 renvoie son adaptateur via schema_adapter() :

use adk_core::Llm;

let model = GeminiModel::new(&api_key, "gemini-3.7-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 API :

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Ă©soudre $ref (intĂ©gration depuis les dĂ©finitions, arrĂȘt des cycles Ă  la profondeur 10)
  2. Supprimer $schema
  3. RĂ©duire anyOf/oneOf → premier sous-schĂ©ma non nul
  4. Fusionner les sous-schémas allOf
  5. RĂ©duire les tableaux de types (["string", "null"] → "string")
  6. Supprimer if/then/else
  7. Convertir const → enum Ă  un seul Ă©lĂ©ment
  8. Supprimer null des tableaux enum
  9. Ajouter implicitement type: "object"
  10. Supprimer les mots-clés non pris en charge
  11. Supprimer les valeurs format non prises en charge
  12. Imposer une profondeur d’imbrication (5 niveaux)
  13. Supprimer 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

Corrections minimales sûres pour le mode non strict :

use adk_model::openai::OpenAiSchemaAdapter;

let adapter = OpenAiSchemaAdapter;

AnthropicSchemaAdapter

Presque transparent — Anthropic prend en charge la plupart des fonctionnalitĂ©s de schĂ©ma JSON :

use adk_model::anthropic::AnthropicSchemaAdapter;

let adapter = AnthropicSchemaAdapter;

GenericSchemaAdapter

Valeur 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 Ă  l’aide d’un hachage du contenu afin d’éviter les calculs redondants :

use adk_core::{GenericSchemaAdapter, SchemaCache};
use serde_json::json;
use std::sync::Arc;

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

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

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

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

Chaque cache possĂšde une instance d’adaptateur, de sorte que les entrĂ©es normalisĂ©es pour diffĂ©rents fournisseurs ou configurations d’adaptateur ne puissent pas entrer en collision. Les clients des fournisseurs utilisent automatiquement ces caches lors de generate_content().

Troncature des noms d’outils

Tous les adaptateurs tronquent les noms d’outils dĂ©passant 64 octets Ă  des limites valides de caractĂšres UTF-8 :

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" lorsque properties existe
  • convert_const_to_enum — convertit const en enum Ă  Ă©lĂ©ment unique
  • strip_unsupported_formats — supprime les valeurs de format qui ne figurent pas dans la liste d’autorisation
  • strip_null_from_enum — supprime null des tableaux d’énumĂ©ration
  • truncate_tool_name — tronque Ă  la limite UTF-8
  • resolve_refs — intĂšgre les rĂ©fĂ©rences $ref depuis les dĂ©finitions
  • collapse_combiners — rĂ©duit anyOf/oneOf au premier Ă©lĂ©ment 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émonstration de normalisation des schémas pour voir tous les adaptateurs en action :

cd examples/schema_normalization
cargo run

Aucune clĂ© API n’est nĂ©cessaire — cette dĂ©monstration prĂ©sente la logique de normalisation localement.

Migration depuis sanitize_schema

Si vous vous appuyiez auparavant sur McpToolset pour renvoyer des schémas pré-nettoyés :

  • Aucune modification du code n’est nĂ©cessaire pour l’utilisation standard. Le trait Toolset API reste inchangĂ©.
  • Les schĂ©mas sont dĂ©sormais 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() en vous attendant Ă  obtenir une sortie au format Gemini, le schĂ©ma brut est dĂ©sormais renvoyĂ© Ă  la place. Utilisez le SchemaAdapter appropriĂ© pour le normaliser vous-mĂȘme si nĂ©cessaire.

PrĂ©cĂ©dent : ← MCP Outils | Suivant : Sessions →

Normalisation des schémas - Documentation ADK-Rust | ADK-Rust