تطبيع المخططات

يقوم ADK-Rust بتطبيع مخططات أدوات MCP تلقائيًا لكل موفّر LLM وقت الطلب. وهذا يعني أن أدوات 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 StrictOpenAIAnthropicGeneric
دقة $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 لاستدعاء الدوال 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 ← أول مخطط فرعي غير فارغ
  4. دمج المخططات الفرعية لـ allOf
  5. طيّ مصفوفات الأنواع (["string", "null"]"string")
  6. إزالة if/then/else
  7. تحويل constenum أحادي العنصر
  8. إزالة القيمة الفارغة من مصفوفات enum
  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

إصلاحات آمنة وبسيطة للوضع غير الصارم:

use adk_model::openai::OpenAiSchemaAdapter;

let adapter = OpenAiSchemaAdapter;

AnthropicSchemaAdapter

يمرّر البيانات تقريبًا كما هي — يدعم Anthropic معظم ميزات مخطط JSON:

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

يمتلك كل مخزن مؤقت مثيلًا واحدًا من المحوّل، ولذلك لا يمكن أن تتصادم الإدخالات المُطبَّعة لمزوّدي الخدمة المختلفين أو تكوينات المحوّلات المختلفة. تستخدم عملاء مزوّدي الخدمة هذه المخازن المؤقتة تلقائيًا أثناء 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 — تضيف type: "object" عند وجود properties
  • convert_const_to_enum — تحول const إلى enum ذي عنصر واحد
  • strip_unsupported_formats — تزيل قيم التنسيق غير الموجودة في قائمة السماح
  • strip_null_from_enum — تزيل القيمة الفارغة من مصفوفات التعداد
  • truncate_tool_name — تقتطع عند حد UTF-8
  • resolve_refs — تدرج مراجع $ref من التعريفات
  • collapse_combiners — تطوي anyOf/oneOf إلى أول قيمة غير فارغة
  • 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 المناسب لتطبيعه بنفسك عند الحاجة.

السابق: ← MCP Tools | التالي: Sessions →