模式规范化
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 的函数调用 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数组中移除 null - 添加隐式
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 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— 移除$schemastrip_conditional_keywords— 移除if/then/elseadd_implicit_object_type— 在存在properties时添加type: "object"convert_const_to_enum— 将const转换为单元素enumstrip_unsupported_formats— 移除不在允许列表中的格式值strip_null_from_enum— 从枚举数组中移除 nulltruncate_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 返回预先清理的模式:
- 对于标准用法,无需更改代码。
Toolsettrait API 保持不变。 - 现在,模式会在请求时由模型适配器进行规范化,而不是在工具注册时进行。
- 如果你调用
parameters_schema()并期望获得 Gemini 格式的输出,现在将返回原始模式。如有需要,请使用适当的SchemaAdapter自行进行规范化。