Schema-Normalisierung

ADK-Rust normalisiert MCP-Tool-Schemas für jeden LLM-Provider zum Zeitpunkt der Anfrage automatisch. Das bedeutet, dass MCP-Tools nahtlos über Gemini, OpenAI, Anthropic und andere Provider hinweg funktionieren, ohne dass das Schema manuell angepasst werden muss.

Funktionsweise

MCP Server → raw JSON Schema → McpToolset (stores verbatim)
                                      ↓
                          Model.generate_content()
                                      ↓
                          SchemaAdapter.normalize_schema()
                                      ↓
                          Provider API (Gemini/OpenAI/Anthropic)
  1. McpToolset ermittelt Tools und speichert deren unverändertes rohes inputSchema
  2. Wenn das Modell eine Anfrage erstellt, ruft es schema_adapter().normalize_schema() für das Schema jedes Tools auf
  3. Jeder Provider verfügt über einen eigenen Adapter, der nur die von seinem API benötigten Transformationen anwendet
  4. Die Ergebnisse werden anhand eines Inhalts-Hashes zwischengespeichert, um redundante Normalisierungen zu vermeiden

Providerverhalten

FunktionGeminiOpenAI StrictOpenAIAnthropicAllgemein
$ref Auflösung✅ Fügt inline ein❌ Behält bei❌ Behält bei❌ Behält bei❌ Behält bei
anyOf/oneOfFasst zusammenBehält beiBehält beiBehält beiBehält bei
allOfFührt zusammenBewahrtBewahrtBewahrtBewahrt
additionalPropertiesEntferntSetzt falseBewahrtBewahrtBewahrt
Typ-ArraysFasst zusammenBewahrtBewahrtBewahrtBewahrt
$schemaEntferntEntferntEntferntEntferntEntfernt
if/then/elseEntferntEntferntEntferntEntferntEntfernt
constenumenumenumBehält beienum
Nicht unterstütztes formatEntferntEntferntEntferntBehält beiEntfernt
Verschachtelungstiefenlimit5 EbenenKeineKeineKeineKeine
exclusiveMin/MaxEntferntBewahrtBewahrtBewahrtBewahrt

Der SchemaAdapter-Trait

Alle Adapter implementieren diesen Trait aus 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;
}

Jede Implementierung von Llm gibt ihren Adapter über schema_adapter() zurück:

use adk_core::Llm;

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

Verfügbare Adapter

GeminiSchemaAdapter

Der aggressivste Adapter. Wendet alle destruktiven Transformationen an, die für Geminis Function-Calling-API erforderlich sind:

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

Transformationspipeline:

  1. $ref auflösen (inline aus Definitionen, Zyklen ab Tiefe 10 unterbrechen)
  2. $schema entfernen
  3. anyOf/oneOf auf das erste Sub-Schema ungleich null reduzieren
  4. allOf-Sub-Schemas zusammenführen
  5. Typ-Arrays reduzieren (["string", "null"]"string")
  6. if/then/else entfernen
  7. const in ein ein-elementiges enum umwandeln
  8. Null aus enum-Arrays entfernen
  9. Implizites type: "object" hinzufügen
  10. Nicht unterstützte Schlüsselwörter entfernen
  11. Nicht unterstützte format-Werte entfernen
  12. Verschachtelungstiefe erzwingen (5 Ebenen)
  13. definitions/$defs entfernen

OpenAiStrictSchemaAdapter

Bewahrt die Struktur des Schemas und fügt additionalProperties: false für strukturierte Ausgaben hinzu:

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

let adapter = OpenAiStrictSchemaAdapter;

OpenAiSchemaAdapter

Minimale sichere Korrekturen für den nicht-strikten Modus:

use adk_model::openai::OpenAiSchemaAdapter;

let adapter = OpenAiSchemaAdapter;

AnthropicSchemaAdapter

Nahezu unverändert – Anthropic unterstützt die meisten JSON-Schemafunktionen:

use adk_model::anthropic::AnthropicSchemaAdapter;

let adapter = AnthropicSchemaAdapter;

GenericSchemaAdapter

Standard für unbekannte Anbieter (Ollama, DeepSeek usw.):

use adk_core::GenericSchemaAdapter;

let adapter = GenericSchemaAdapter;

Schema-Caching

Normalisierte Schemas werden anhand eines Inhalts-Hashes zwischengespeichert, um redundante Berechnungen zu vermeiden:

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

Jeder Cache besitzt eine eigene Adapterinstanz, sodass für verschiedene Anbieter oder Adapterkonfigurationen normalisierte Einträge nicht kollidieren können. Anbieter-Clients verwenden diese Caches automatisch während generate_content().

Kürzen von Toolnamen

Alle Adapter kürzen Toolnamen, die 64 Bytes überschreiten, an gültigen UTF-8-Zeichen-Grenzen:

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

Gemeinsame Dienstprogramme

Das Modul adk_core::schema_utils stellt zusammensetzbare Transformationsfunktionen bereit, die Adapter intern verwenden:

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

Verfügbare Dienstprogramme:

  • strip_schema_keyword — entfernt $schema
  • strip_conditional_keywords — entfernt if/then/else
  • add_implicit_object_type — fügt type: "object" hinzu, wenn properties vorhanden ist
  • convert_const_to_enum — konvertiert const in ein enum mit einem Element
  • strip_unsupported_formats — entfernt Formatwerte, die nicht in der Zulassungsliste enthalten sind
  • strip_null_from_enum — entfernt null aus Enum-Arrays
  • truncate_tool_name — kürzt an der Grenze UTF-8
  • resolve_refs — bettet $ref-Referenzen aus Definitionen ein
  • collapse_combiners — reduziert anyOf/oneOf auf den ersten Nicht-null-Wert
  • merge_all_of — führt allOf-Unterschemas zusammen
  • collapse_type_arrays — reduziert ["string", "null"] auf "string"
  • enforce_nesting_depth — ersetzt tiefe Schemas durch {"type": "object"}

Benutzerdefinierte Adapter

Implementieren Sie SchemaAdapter für benutzerdefinierte Anbieter:

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

Beispiel

Führen Sie die Demo zur Schemanormalisierung aus, um alle Adapter in Aktion zu sehen:

cd examples/schema_normalization
cargo run

Es werden keine API-Schlüssel benötigt — die Normalisierungslogik wird lokal demonstriert.

Migration von sanitize_schema

Wenn Sie sich zuvor darauf verlassen haben, dass McpToolset vorab bereinigte Schemas zurückgibt:

  • Für die Standardverwendung sind keine Codeänderungen erforderlich. Das Toolset-Trait API ist unverändert.
  • Schemas werden jetzt zum Zeitpunkt der Anfrage durch den Modelladapter normalisiert, nicht bei der Toolregistrierung.
  • Wenn Sie parameters_schema() aufgerufen und eine Gemini-formatierte Ausgabe erwartet haben, wird jetzt stattdessen das unformatierte Schema zurückgegeben. Verwenden Sie bei Bedarf das entsprechende SchemaAdapter, um es selbst zu normalisieren.

Zurück: ← MCP Tools | Weiter: Sitzungen →