Normalização de Esquemas

ADK-Rust normaliza automaticamente os esquemas de ferramentas de MCP para cada provedor LLM no momento da solicitação. Isso significa que as ferramentas MCP funcionam perfeitamente entre Gemini, OpenAI, Anthropic e outros provedores, sem ajustes manuais no esquema.

Como Funciona

MCP Server → raw JSON Schema → McpToolset (stores verbatim)
                                      ↓
                          Model.generate_content()
                                      ↓
                          SchemaAdapter.normalize_schema()
                                      ↓
                          Provider API (Gemini/OpenAI/Anthropic)
  1. McpToolset descobre as ferramentas e armazena seu inputSchema bruto sem modificações
  2. Quando o modelo cria uma solicitação, ele chama schema_adapter().normalize_schema() no esquema de cada ferramenta
  3. Cada provedor tem seu próprio adaptador, que aplica apenas as transformações exigidas por seu API
  4. Os resultados são armazenados em cache por hash de conteúdo para evitar normalizações redundantes

Comportamento por Provedor

RecursoGeminiOpenAI StrictOpenAIAnthropicGenérico
Resolução de $ref✅ Incorpora❌ Preserva❌ Preserva❌ Preserva❌ Preserva
anyOf/oneOfRecolhePreservaPreservaPreservaPreserva
allOfMesclaPreservaPreservaPreservaPreserva
additionalPropertiesRemoveDefine falsePreservaPreservaPreserva
Matrizes de tipoRecolhePreservaPreservaPreservaPreserva
$schemaRemoveRemoveRemoveRemoveRemove
if/then/elseRemoveRemoveRemoveRemoveRemove
constenumenumenumPreservaenum
format não compatívelRemoveRemoveRemovePreservaRemove
Limite de profundidade de aninhamento5 níveisNenhumNenhumNenhumNenhum
exclusiveMin/MaxRemovePreservaPreservaPreservaPreserva

O trait SchemaAdapter

Todos os adaptadores implementam 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 implementação de Llm retorna seu adaptador por meio de schema_adapter():

use adk_core::Llm;

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

Adaptadores disponíveis

GeminiSchemaAdapter

O adaptador mais agressivo. Aplica todas as transformações destrutivas exigidas pelo API de chamada de funções do Gemini:

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 transformação:

  1. Resolve $ref (incorporando definições, interrompendo ciclos na profundidade 10)
  2. Remove $schema
  3. Recolhe anyOf/oneOf → primeiro subesquema não nulo
  4. Mescla subesquemas allOf
  5. Recolhe arrays de tipos (["string", "null"]"string")
  6. Remove if/then/else
  7. Converte constenum de elemento único
  8. Remove null de arrays enum
  9. Adiciona type: "object" implícito
  10. Remove palavras-chave não compatíveis
  11. Remove valores format não compatíveis
  12. Impõe profundidade de aninhamento (5 níveis)
  13. Remove definitions/$defs

OpenAiStrictSchemaAdapter

Preserva a estrutura do esquema enquanto adiciona additionalProperties: false para saídas estruturadas:

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

let adapter = OpenAiStrictSchemaAdapter;

OpenAiSchemaAdapter

Correções mínimas e seguras para o modo não estrito:

use adk_model::openai::OpenAiSchemaAdapter;

let adapter = OpenAiSchemaAdapter;

AnthropicSchemaAdapter

Quase um repasse direto — o Anthropic é compatível com a maioria dos recursos de esquema JSON:

use adk_model::anthropic::AnthropicSchemaAdapter;

let adapter = AnthropicSchemaAdapter;

GenericSchemaAdapter

Padrão para provedores desconhecidos (Ollama, DeepSeek etc.):

use adk_core::GenericSchemaAdapter;

let adapter = GenericSchemaAdapter;

Cache de esquemas

Os esquemas normalizados são armazenados em cache por hash de conteúdo 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 cache possui uma instância de adaptador, portanto as entradas normalizadas para diferentes provedores ou configurações de adaptador não podem colidir. Os clientes dos provedores usam esses caches automaticamente durante generate_content().

Truncamento de nomes de ferramentas

Todos os adaptadores truncam nomes de ferramentas que excedem 64 bytes em limites válidos de caracteres 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());

Utilitários compartilhados

O módulo adk_core::schema_utils fornece funções de transformação componíveis que os adaptadores usam 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"]);

Utilitários disponíveis:

  • strip_schema_keyword — remove $schema
  • strip_conditional_keywords — remove if/then/else
  • add_implicit_object_type — adiciona type: "object" quando properties existe
  • convert_const_to_enum — converte const em enum de elemento único
  • strip_unsupported_formats — remove valores de formato que não estão na lista de permitidos
  • strip_null_from_enum — remove nulo de matrizes de enumeração
  • truncate_tool_name — trunca no limite UTF-8
  • resolve_refs — incorpora referências $ref de definições
  • collapse_combiners — reduz anyOf/oneOf ao primeiro valor não nulo
  • merge_all_of — mescla subesquemas allOf
  • collapse_type_arrays — reduz ["string", "null"] a "string"
  • enforce_nesting_depth — substitui esquemas profundos por {"type": "object"}

Adaptadores personalizados

Implemente SchemaAdapter para provedores 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
    }
}

Exemplo

Execute a demonstração de normalização de esquema para ver todos os adaptadores em ação:

cd examples/schema_normalization
cargo run

Nenhuma chave API é necessária — isso demonstra a lógica de normalização localmente.

Migração de sanitize_schema

Se anteriormente você dependia de McpToolset retornar esquemas pré-sanitizados:

  • Nenhuma alteração de código é necessária para o uso padrão. O trait Toolset API permanece inalterado.
  • Os esquemas agora são normalizados no momento da solicitação pelo adaptador do modelo, e não no registro da ferramenta.
  • Se você chamava parameters_schema() esperando uma saída formatada pelo Gemini, o esquema bruto agora é retornado. Use o SchemaAdapter apropriado para normalizá-lo por conta própria, se necessário.

Anterior: ← MCP Ferramentas | Próximo: Sessões →