模式规范化

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 的函数调用 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. 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

针对非严格模式的最小安全修复:

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

每个缓存拥有一个适配器实例,因此针对不同提供商或适配器配置规范化的条目不会发生冲突。提供商客户端会在 generate_content() 期间自动使用这些缓存。

工具名称截断

所有适配器都会在有效的 UTF-8 字符边界处截断超过 64 字节的工具名称:

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 trait API 保持不变。
  • 现在,模式会在请求时由模型适配器进行规范化,而不是在工具注册时进行。
  • 如果你调用 parameters_schema() 并期望获得 Gemini 格式的输出,现在将返回原始模式。如有需要,请使用适当的 SchemaAdapter 自行进行规范化。

上一页← MCP 工具 | 下一页会话 →