Normalización de Esquemas

ADK-Rust normaliza automáticamente los esquemas de herramientas MCP para cada proveedor LLM en el momento de la solicitud. Esto significa que las herramientas MCP funcionan sin problemas en Gemini, OpenAI, Anthropic y otros proveedores sin ajustes manuales del esquema.

Cómo Funciona

MCP Server → raw JSON Schema → McpToolset (stores verbatim)
                                      ↓
                          Model.generate_content()
                                      ↓
                          SchemaAdapter.normalize_schema()
                                      ↓
                          Provider API (Gemini/OpenAI/Anthropic)
  1. McpToolset descubre herramientas y almacena sus inputSchema sin modificaciones
  2. Cuando el modelo construye una solicitud, llama a schema_adapter().normalize_schema() en el esquema de cada herramienta
  3. Cada proveedor tiene su propio adaptador que aplica solo las transformaciones que su API requiere
  4. Los resultados se almacenan en caché por hash de contenido para evitar la normalización redundante

Comportamiento del Proveedor

CaracterísticaGeminiOpenAI EstrictoOpenAIAnthropicGeneric
$ref resolución✅ Inlines❌ Preserves❌ Preserves❌ Preserves❌ Preserves
anyOf/oneOfColapsaPreservaPreservaPreservaPreserva
allOfFusionaConservaConservaConservaConserva
additionalPropertiesEliminaEstablece falseConservaConservaConserva
Arreglos de tiposColapsaPreservaPreservaPreservaPreserva
$schemaTirasTirasTirasTirasTiras
if/then/elseTirasTirasTirasTirasTiras
constenumenumenumPreservaenum
No compatible formatEliminaEliminaEliminaConservaElimina
Límite de profundidad de anidamiento5 nivelesNingunoNingunoNingunoNinguno
exclusiveMin/MaxEliminaConservaConservaConservaConserva

El SchemaAdapter Trait

Todos los adaptadores implementan este 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;
}

Cada Llm implementación devuelve su adaptador a través de schema_adapter():

use adk_core::Llm;

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

Adaptadores Disponibles

GeminiSchemaAdapter

El adaptador más agresivo. Aplica todas las transformaciones destructivas requeridas por la llamada a función 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 transformación:

  1. Resuelve $ref (en línea desde definiciones, rompe ciclos a profundidad 10)
  2. Elimina $schema
  3. Colapsa anyOf/oneOf → primer sub-esquema no nulo
  4. Fusiona allOf sub-esquemas
  5. Colapsa arreglos de tipos (["string", "null"]"string")
  6. Elimina if/then/else
  7. Convierte constenum de un solo elemento
  8. Elimina nulo de los arreglos enum
  9. Añade type: "object" implícito
  10. Elimina palabras clave no soportadas
  11. Elimina valores format no soportados
  12. Aplica profundidad de anidamiento (5 niveles)
  13. Elimina definitions/$defs

OpenAiStrictSchemaAdapter

Preserva la estructura del esquema mientras añade additionalProperties: false para salidas estructuradas:

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

let adapter = OpenAiStrictSchemaAdapter;

OpenAiSchemaAdapter

Correcciones seguras mínimas para el modo no estricto:

use adk_model::openai::OpenAiSchemaAdapter;

let adapter = OpenAiSchemaAdapter;

AnthropicSchemaAdapter

Casi paso directo — Anthropic soporta la mayoría de las características del esquema JSON:

use adk_model::anthropic::AnthropicSchemaAdapter;

let adapter = AnthropicSchemaAdapter;

GenericSchemaAdapter

Default for unknown providers (Ollama, DeepSeek, etc.):

use adk_core::GenericSchemaAdapter;

let adapter = GenericSchemaAdapter;

Caché de Esquemas

Los esquemas normalizados se almacenan en caché por hash de contenido para evitar cálculos redundantes:

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();

La caché reside en las instancias del modelo y se utiliza automáticamente durante generate_content().

Truncamiento de Nombres de Herramientas

Todos los adaptadores truncan los nombres de herramientas que exceden los 64 bytes en los límites de caracteres válidos de 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());

Utilidades Compartidas

El módulo adk_core::schema_utils proporciona funciones de transformación componibles que los adaptadores utilizan internamente:

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"]);

Utilidades disponibles:

  • strip_schema_keyword — elimina $schema
  • strip_conditional_keywords — elimina if/then/else
  • add_implicit_object_type — añade type: "object" cuando properties existe
  • convert_const_to_enum — convierte const a enum de un solo elemento
  • strip_unsupported_formats — elimina valores de formato que no están en la lista de permitidos
  • strip_null_from_enum — elimina nulos de los arrays de enumeración
  • truncate_tool_name — trunca en el límite UTF-8
  • resolve_refs — inserta en línea las referencias $ref de las definiciones
  • collapse_combiners — colapsa anyOf/oneOf al primer no nulo
  • merge_all_of — fusiona los subesquemas allOf
  • collapse_type_arrays — colapsa ["string", "null"] a "string"
  • enforce_nesting_depth — reemplaza esquemas profundos con {"type": "object"}

Adaptadores Personalizados

Implementa SchemaAdapter para proveedores personalizados:

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
    }
}

Ejemplo

Ejecuta la demostración de normalización de esquemas para ver todos los adaptadores en acción:

cd examples/schema_normalization
cargo run

No se necesitan claves API — demuestra la lógica de normalización localmente.

Migración desde sanitize_schema

Si anteriormente dependía de McpToolset que devolvía esquemas pre-sanitizados:

  • No se necesitan cambios en el código para uso estándar. El trait Toolset API no ha cambiado.
  • Los esquemas ahora se normalizan en el momento de la solicitud por el adaptador del modelo, no en el registro de la herramienta.
  • Si estaba llamando a parameters_schema() y esperaba una salida con formato Gemini, ahora se devuelve el esquema sin procesar en su lugar. Utilice el SchemaAdapter apropiado para normalizarlo usted mismo si es necesario.

Anterior: ← MCP Herramientas | Siguiente: Sesiones →