スキーマの正規化
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)
- McpToolset がツールを検出し、未変更のまま生の
inputSchemaを保存します - モデルがリクエストを構築すると、各ツールのスキーマに対して
schema_adapter().normalize_schema()を呼び出します - 各プロバイダーには独自のアダプターがあり、その API が必要とする変換のみを適用します
- 冗長な正規化を避けるため、結果はコンテンツハッシュによってキャッシュされます
プロバイダーの動作
| 機能 | Gemini | OpenAI 厳格 | OpenAI | Anthropic | 汎用 |
|---|---|---|---|---|---|
$ref の解決 | ✅ インライン化 | ❌ 保持 | ❌ 保持 | ❌ 保持 | ❌ 保持 |
anyOf/oneOf | 折りたたむ | 保持 | 保持 | 保持 | 保持 |
allOf | マージ | 保持 | 保持 | 保持 | 保持 |
additionalProperties | 削除 | falseを設定 | 保持 | 保持 | 保持 |
| 型配列 | 折りたたむ | 保持 | 保持 | 保持 | 保持 |
$schema | 除去 | 除去 | 除去 | 除去 | 除去 |
if/then/else | 除去 | 除去 | 除去 | 除去 | 除去 |
const | → enum | → enum | → enum | 保持 | → 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();
変換パイプライン:
$refを解決(定義からインライン化し、深さ10で循環を中断)$schemaを削除anyOf/oneOfを折りたたみ → 最初の null ではないサブスキーマallOfのサブスキーマをマージ- 型配列を折りたたみ(
["string", "null"]→"string") if/then/elseを削除constを単一要素のenumに変換enum配列から null を削除- 暗黙の
type: "object"を追加 - サポートされていないキーワードを削除
- サポートされていない
formatの値を削除 - ネストの深さを5レベルに制限
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_keywords—if/then/elseを削除add_implicit_object_type—propertiesが存在する場合にtype: "object"を追加convert_const_to_enum—constを単一要素のenumに変換strip_unsupported_formats— 許可リストにない形式の値を削除strip_null_from_enum— 列挙型配列から null を削除truncate_tool_name— UTF-8 の境界で切り詰めresolve_refs— 定義から$ref参照をインライン化collapse_combiners—anyOf/oneOfを最初の null 以外の値に集約merge_all_of—allOfサブスキーマをマージ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を使用して自分で正規化してください。