Normalización de esquemas

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

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 las herramientas y almacena sus elementos inputSchema sin modificaciones
  2. Cuando el modelo crea una solicitud, llama a schema_adapter().normalize_schema() en el esquema de cada herramienta
  3. Cada proveedor tiene su propio adaptador que aplica únicamente las transformaciones que requiere su API
  4. Los resultados se almacenan en caché mediante un hash de contenido para evitar normalizaciones redundantes

Comportamiento según el proveedor

CaracterísticaGeminiOpenAI EstrictoOpenAIAnthropicGenérico
Resolución de $ref✅ Integra❌ Conserva❌ Conserva❌ Conserva❌ Conserva
anyOf/oneOfCombinaConservaConservaConservaConserva
allOfFusionaConservaConservaConservaConserva
additionalPropertiesEliminaEstablece falseConservaConservaConserva
Matrices de tiposColapsaConservaConservaConservaConserva
$schemaEliminaEliminaEliminaEliminaElimina
if/then/elseEliminaEliminaEliminaEliminaElimina
constenumenumenumConservaenum
format no compatibleEliminaEliminaEliminaConservaElimina
Límite de profundidad de anidamiento5 nivelesNingunoNingunoNingunoNinguno
exclusiveMin/MaxEliminaConservaConservaConservaConserva

El rasgo SchemaAdapter

Todos los adaptadores implementan este rasgo 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 implementación de Llm devuelve su adaptador mediante schema_adapter():

use adk_core::Llm;

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

Flujo de transformación:

  1. Resolver $ref (insertar definiciones en línea y romper ciclos en la profundidad 10)
  2. Eliminar $schema
  3. Contraer anyOf/oneOf → primer subesquema no nulo
  4. Combinar los subesquemas de allOf
  5. Contraer matrices de tipos (["string", "null"]"string")
  6. Eliminar if/then/else
  7. Convertir constenum de un solo elemento
  8. Eliminar los valores nulos de las matrices enum
  9. Añadir type: "object" implícito
  10. Eliminar palabras clave no compatibles
  11. Eliminar los valores format no compatibles
  12. Aplicar un límite a la profundidad de anidamiento (5 niveles)
  13. Eliminar definitions/$defs

OpenAiStrictSchemaAdapter

Conserva la estructura del esquema y añade additionalProperties: false para las 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 de paso directo: Anthropic admite la mayoría de las funciones del esquema JSON:

use adk_model::anthropic::AnthropicSchemaAdapter;

let adapter = AnthropicSchemaAdapter;

GenericSchemaAdapter

Predeterminado para proveedores desconocidos (Ollama, DeepSeek, etc.):

use adk_core::GenericSchemaAdapter;

let adapter = GenericSchemaAdapter;

Almacenamiento en caché de esquemas

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

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

Cada caché posee una instancia de adaptador, por lo que las entradas normalizadas para distintos proveedores o configuraciones de adaptador no pueden colisionar. Los clientes de proveedores utilizan estas cachés automáticamente durante generate_content().

Truncamiento de nombres de herramientas

Todos los adaptadores truncan los nombres de herramientas que superan los 64 bytes en límites válidos de caracteres 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 existe properties
  • convert_const_to_enum — convierte const en enum de un solo elemento
  • strip_unsupported_formats — elimina los valores de formato que no están en la lista de permitidos
  • strip_null_from_enum — elimina null de las matrices 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 — reduce anyOf/oneOf al primer valor que no sea null
  • merge_all_of — combina los subesquemas allOf
  • collapse_type_arrays — reduce ["string", "null"] a "string"
  • enforce_nesting_depth — reemplaza los esquemas profundos por {"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; esto demuestra la lógica de normalización localmente.

Migración desde sanitize_schema

Si anteriormente dependías de que McpToolset devolviera esquemas previamente saneados:

  • No se necesitan cambios de código para el uso estándar. El trait Toolset API no ha cambiado.
  • Ahora los esquemas se normalizan en el momento de la solicitud mediante el adaptador del modelo, no durante el registro de la herramienta.
  • Si llamabas a parameters_schema() y esperabas una salida con formato de Gemini, ahora se devuelve el esquema sin procesar. Usa el SchemaAdapter apropiado para normalizarlo por tu cuenta si es necesario.

Anterior: ← MCP Herramientas | Siguiente: Sesiones →

Normalización de esquemas - Documentación ADK-Rust | ADK-Rust