تطبيع المخططات
يقوم 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)
- McpToolset يكتشف الأدوات ويخزّن
inputSchemaالخام الخاص بها دون تعديل - عند إنشاء النموذج لطلب، يستدعي
schema_adapter().normalize_schema()على مخطط كل أداة - يمتلك كل موفّر محوّلًا خاصًا به يطبّق التحويلات التي تتطلبها API فقط
- تُخزَّن النتائج مؤقتًا بحسب تجزئة المحتوى لتجنّب التطبيع المتكرر غير الضروري
سلوك الموفّر
| الميزة | Gemini | OpenAI Strict | OpenAI | Anthropic | Generic |
|---|---|---|---|---|---|
دقة $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 لاستدعاء الدوال 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← أول مخطط فرعي غير فارغ - دمج المخططات الفرعية لـ
allOf - طيّ مصفوفات الأنواع (
["string", "null"]←"string") - إزالة
if/then/else - تحويل
const←enumأحادي العنصر - إزالة القيمة الفارغة من مصفوفات
enum - إضافة
type: "object"ضمنيًا - إزالة الكلمات المفتاحية غير المدعومة
- إزالة قيم
formatغير المدعومة - فرض عمق التداخل (5 مستويات)
- إزالة
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— تزيل$schemastrip_conditional_keywords— تزيلif/then/elseadd_implicit_object_type— تضيفtype: "object"عند وجودpropertiesconvert_const_to_enum— تحولconstإلىenumذي عنصر واحدstrip_unsupported_formats— تزيل قيم التنسيق غير الموجودة في قائمة السماحstrip_null_from_enum— تزيل القيمة الفارغة من مصفوفات التعدادtruncate_tool_name— تقتطع عند حد UTF-8resolve_refs— تدرج مراجع$refمن التعريفاتcollapse_combiners— تطويanyOf/oneOfإلى أول قيمة غير فارغةmerge_all_of— تدمج المخططات الفرعيةallOfcollapse_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 لإرجاع مخططات منظّفة مسبقًا:
- لا حاجة إلى تغييرات في التعليمات البرمجية للاستخدام القياسي. تظل سمة
ToolsetAPI دون تغيير. - تُطبّع المخططات الآن في وقت الطلب بواسطة محول النموذج، وليس عند تسجيل الأداة.
- إذا كنت تستدعي
parameters_schema()وتتوقع مخرجات منسّقة وفق Gemini، فسيُرجع المخطط الخام الآن بدلًا من ذلك. استخدمSchemaAdapterالمناسب لتطبيعه بنفسك عند الحاجة.
السابق: ← MCP Tools | التالي: Sessions →