Schema-Normalisierung

ADK-Rust normalisiert automatisch MCP tool schemas für jeden LLM-Anbieter zum Zeitpunkt der Anfrage. Das bedeutet, dass MCP tools nahtlos über Gemini, OpenAI, Anthropic und andere Anbieter hinweg funktionieren, ohne manuelle Schema-Anpassung.

Funktionsweise

MCP Server → raw JSON Schema → McpToolset (stores verbatim)
                                      ↓
                          Model.generate_content()
                                      ↓
                          SchemaAdapter.normalize_schema()
                                      ↓
                          Provider API (Gemini/OpenAI/Anthropic)
  1. McpToolset entdeckt tools und speichert ihre rohen inputSchema ohne Modifikation
  2. Wenn das Modell eine Anfrage erstellt, ruft es schema_adapter().normalize_schema() auf das Schema jedes tools auf
  3. Jeder Anbieter hat seinen eigenen Adapter, der nur die Transformationen anwendet, die seine API benötigt
  4. Ergebnisse werden nach Inhalts-Hash zwischengespeichert, um redundante Normalisierung zu vermeiden

Anbieterverhalten

MerkmalGeminiOpenAI StrictOpenAIAnthropicGeneric
$ref Auflösung✅ Inline-Elemente❌ Behält bei❌ Behält bei❌ Behält bei❌ Behält bei
anyOf/oneOfKollabiertBewahrtBewahrtBewahrtBewahrt
allOfZusammenführtBewahrtBewahrtBewahrtBewahrt
additionalPropertiesEntferntSetzt falseBewahrtBewahrtBewahrt
Typ-ArraysKollabiertBewahrtBewahrtBewahrtBewahrt
$schemaStreifenStreifenStreifenStreifenStreifen
if/then/elseStreifenStreifenStreifenStreifenStreifen
constenumenumenumBewahrtenum
Nicht unterstützt 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 Llm Implementierung gibt ihren Adapter über schema_adapter() zurück:

use adk_core::Llm;

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

Verfügbare Adapter

GeminiSchemaAdapter

Der aggressivste Adapter. Wendet alle destruktiven Transformationen an, die von Geminis function-calling API benötigt werden:

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

Transformations-Pipeline:

  1. $ref auflösen (inline aus Definitionen, Zyklen bei Tiefe 10 unterbrechen)
  2. $schema entfernen
  3. anyOf/oneOf kollabieren → erstes nicht-null Sub-Schema
  4. allOf Sub-Schemas zusammenführen
  5. Typ-Arrays kollabieren (["string", "null"]"string")
  6. if/then/else entfernen
  7. const → Einzelelement enum konvertieren
  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 Schema-Struktur 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-strengen Modus:

use adk_model::openai::OpenAiSchemaAdapter;

let adapter = OpenAiSchemaAdapter;

AnthropicSchemaAdapter

Nahezu unverändert — Anthropic unterstützt die meisten JSON Schema-Funktionen:

use adk_model::anthropic::AnthropicSchemaAdapter;

let adapter = AnthropicSchemaAdapter;

GenericSchemaAdapter

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

use adk_core::GenericSchemaAdapter;

let adapter = GenericSchemaAdapter;

Schema-Caching

Normalisierte Schemas werden anhand des Inhaltshashs zwischengespeichert, um redundante Berechnungen zu vermeiden:

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

Der Cache befindet sich auf Modellinstanzen und wird während generate_content() automatisch verwendet.

Kürzung von Tool-Namen

Alle Adapter kürzen Tool-Namen, die 64 Bytes überschreiten, an gültigen UTF-8-Zeichengrenzen:

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 adk_core::schema_utils Modul 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 existiert
  • convert_const_to_enum — konvertiert const in ein ein-elementiges enum
  • strip_unsupported_formats — entfernt Formatwerte, die nicht in der Zulassungsliste sind
  • strip_null_from_enum — entfernt null aus Enum-Arrays
  • truncate_tool_name — kürzt an der UTF-8-Grenze
  • resolve_refs — inlined $ref-Referenzen aus Definitionen
  • collapse_combiners — kollabiert anyOf/oneOf zum ersten Nicht-Null-Wert
  • merge_all_of — führt allOf-Sub-Schemas zusammen
  • collapse_type_arrays — kollabiert ["string", "null"] zu "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 Schema-Normalisierungs-Demo aus, um alle Adapter in Aktion zu sehen:

cd examples/schema_normalization
cargo run

Keine API-Schlüssel erforderlich — demonstriert die Normalisierungslogik lokal.

Migration von sanitize_schema

Wenn Sie sich zuvor auf McpToolset verlassen haben, um vor-bereinigte Schemas zurückzugeben:

  • Keine Code-Änderungen erforderlich für die Standardnutzung. Die Toolset trait API ist unverändert.
  • Schemas werden jetzt zur Anfragezeit vom Modelladapter normalisiert, nicht bei der Tool-Registrierung.
  • Wenn Sie parameters_schema() aufgerufen und Gemini-formatierten Output erwartet haben, wird stattdessen jetzt das rohe Schema zurückgegeben. Verwenden Sie das entsprechende SchemaAdapter, um es bei Bedarf selbst zu normalisieren.

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