スキーマの正規化

ADK-Rust は、リクエスト時に各 LLM プロバイダー向けの MCP ツールスキーマを自動的に正規化します。これにより、MCP ツールは、手動でスキーマを調整しなくても、Gemini、OpenAI、Anthropic、その他のプロバイダー間でシームレスに動作します。

仕組み

MCP Server → raw JSON Schema → McpToolset (stores verbatim)
                                      ↓
                          Model.generate_content()
                                      ↓
                          SchemaAdapter.normalize_schema()
                                      ↓
                          Provider API (Gemini/OpenAI/Anthropic)
  1. McpToolset がツールを検出し、未変更のまま生の inputSchema を保存します
  2. モデルがリクエストを構築すると、各ツールのスキーマに対して schema_adapter().normalize_schema() を呼び出します
  3. 各プロバイダーには独自のアダプターがあり、その API が必要とする変換のみを適用します
  4. 冗長な正規化を避けるため、結果はコンテンツハッシュによってキャッシュされます

プロバイダーの動作

機能GeminiOpenAI 厳格OpenAIAnthropic汎用
$ref の解決✅ インライン化❌ 保持❌ 保持❌ 保持❌ 保持
anyOf/oneOf折りたたむ保持保持保持保持
allOfマージ保持保持保持保持
additionalProperties削除falseを設定保持保持保持
型配列折りたたむ保持保持保持保持
$schema除去除去除去除去除去
if/then/else除去除去除去除去除去
constenumenumenum保持enum
未対応 format除去除去除去保持除去
ネストの深さの上限5 レベルなしなしなしなし
exclusiveMin/Max削除保持保持保持保持

SchemaAdapterトレイト

すべてのアダプターは、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;
}

Llm実装は、schema_adapter()を介して自身のアダプターを返します。

use adk_core::Llm;

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

利用可能なアダプター

GeminiSchemaAdapter

最も積極的なアダプターです。Gemini の function-calling 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();

変換パイプライン:

  1. $refを解決(定義からインライン化し、深さ10で循環を中断)
  2. $schemaを削除
  3. anyOf/oneOfを折りたたみ → 最初の null ではないサブスキーマ
  4. allOfのサブスキーマをマージ
  5. 型配列を折りたたみ(["string", "null"]"string"
  6. if/then/elseを削除
  7. constを単一要素のenumに変換
  8. enum配列から null を削除
  9. 暗黙のtype: "object"を追加
  10. サポートされていないキーワードを削除
  11. サポートされていないformatの値を削除
  12. ネストの深さを5レベルに制限
  13. definitions/$defsを削除

OpenAiStrictSchemaAdapter

構造化出力のためにadditionalProperties: falseを追加しつつ、スキーマ構造を保持します。

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

let adapter = OpenAiStrictSchemaAdapter;

OpenAiSchemaAdapter

非 strict モード向けの、最小限の安全な修正です。

use adk_model::openai::OpenAiSchemaAdapter;

let adapter = OpenAiSchemaAdapter;

AnthropicSchemaAdapter

ほぼパススルーです — Anthropic はJSON Schema の機能の大部分をサポートします。

use adk_model::anthropic::AnthropicSchemaAdapter;

let adapter = AnthropicSchemaAdapter;

GenericSchemaAdapter

未知のプロバイダー(Ollama、DeepSeekなど)向けのデフォルトです。

use adk_core::GenericSchemaAdapter;

let adapter = GenericSchemaAdapter;

スキーマのキャッシュ

正規化されたスキーマは、冗長な計算を避けるため、コンテンツハッシュによってキャッシュされます。

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

各キャッシュは1つのアダプターインスタンスを所有するため、異なるプロバイダーやアダプター設定向けに正規化されたエントリが衝突することはありません。プロバイダークライアントは、generate_content()中にこれらのキャッシュを自動的に使用します。

ツール名の切り詰め

すべてのアダプターは、64バイトを超えるツール名を、有効な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());

共通ユーティリティ

adk_core::schema_utilsモジュールは、アダプターが内部で使用する合成可能な変換関数を提供します。

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

利用可能なユーティリティ:

  • strip_schema_keyword$schemaを削除
  • strip_conditional_keywordsif/then/elseを削除
  • add_implicit_object_typepropertiesが存在する場合にtype: "object"を追加
  • convert_const_to_enumconstを単一要素のenumに変換
  • strip_unsupported_formats — 許可リストにない形式の値を削除
  • strip_null_from_enum — 列挙型配列から null を削除
  • truncate_tool_name — UTF-8 の境界で切り詰め
  • resolve_refs — 定義から$ref参照をインライン化
  • collapse_combinersanyOf/oneOfを最初の null 以外の値に集約
  • merge_all_ofallOfサブスキーマをマージ
  • collapse_type_arrays["string", "null"]"string"に集約
  • enforce_nesting_depth — 深いスキーマを{"type": "object"}に置換

カスタムアダプター

カスタムプロバイダー向けにSchemaAdapterを実装します。

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

スキーマ正規化デモを実行して、すべてのアダプターの動作を確認します。

cd examples/schema_normalization
cargo run

APIキーは不要です — 正規化ロジックをローカルで実行する方法を示します。

sanitize_schemaからの移行

以前、McpToolsetがサニタイズ済みのスキーマを返すことに依存していた場合:

  • 標準的な使用方法では、コードの変更は不要ですToolsetトレイトのAPIは変更されていません。
  • スキーマはツール登録時ではなく、モデルアダプターによってリクエスト時に正規化されるようになりました。
  • parameters_schema()を呼び出し、Gemini 形式の出力を想定していた場合、現在は未加工のスキーマが返されます。必要に応じて、適切なSchemaAdapterを使用して自分で正規化してください。

前へ: ← MCPツール | 次へ: セッション →

スキーマの正規化 - ADK-Rust ドキュメント | ADK-Rust