8.4 Schema设计艺术 🎨
"好的Schema就像一份清晰的说明书,让用户一看就知道怎么用。"
想象一下,你买了一个复杂的电子产品,但说明书写得云里雾里。你会有什么感受?MCP工具的Schema就是这份"说明书",它决定了用户体验的好坏。
Schema的重要性 🎯
Schema不仅仅是技术规范,更是用户体验的第一道门槛。一个好的Schema能让AI模型更好地理解和使用你的工具,就像给盲人指路的盲道一样重要。
Schema的三大作用
- 参数规范 📋 - 定义工具需要什么参数
- 类型验证 ✅ - 确保参数类型正确
- 用户指导 📖 - 帮助AI模型理解如何使用工具
清晰的参数描述 📝
描述要像讲故事一样生动
typescript
// ❌ 糟糕的描述
const badSchema = {
type: "object",
properties: {
q: { type: "string" }, // 什么是q?
n: { type: "integer" }, // n代表什么?
f: { type: "boolean" } // f是什么意思?
}
};
// ✅ 优秀的描述
const goodSchema = {
type: "object",
properties: {
query: {
type: "string",
description: "搜索关键词。支持中英文,可以是产品名称、品牌或描述性词语",
examples: ["iPhone 15", "红色连衣裙", "咖啡机"],
minLength: 1,
maxLength: 200
},
maxResults: {
type: "integer",
description: "返回结果的最大数量。建议10-50之间,过大可能影响响应速度",
minimum: 1,
maximum: 100,
default: 10
},
includeOutOfStock: {
type: "boolean",
description: "是否包含缺货商品。true=包含缺货商品,false=仅显示有库存商品",
default: false
}
},
required: ["query"]
};
描述的黄金法则
- 说人话 🗣️ - 避免技术黑话,用用户能理解的语言
- 举例子 💡 - 提供具体的使用示例
- 讲限制 ⚠️ - 明确说明参数的限制和约束
- 给默认 🎯 - 为可选参数提供合理的默认值
python
# 🌟 实战示例:电商搜索工具的Schema设计
def create_product_search_schema():
return {
"type": "object",
"description": "在电商平台搜索商品,支持多种筛选条件",
"properties": {
"keyword": {
"type": "string",
"description": "搜索关键词。可以是:1)商品名称(如'MacBook Pro')2)品牌名(如'Apple')3)类别(如'笔记本电脑')4)描述性词语(如'轻薄办公本')",
"examples": [
"iPhone 15 Pro",
"Nike运动鞋",
"咖啡机",
"轻薄笔记本",
"婴儿奶粉"
],
"minLength": 1,
"maxLength": 100
},
"category": {
"type": "string",
"description": "商品类别筛选。留空表示搜索所有类别",
"enum": [
"electronics", # 电子产品
"clothing", # 服装
"home_garden", # 家居园艺
"sports", # 运动户外
"books", # 图书音像
"beauty", # 美妆个护
"food", # 食品饮料
"baby", # 母婴用品
"automotive", # 汽车用品
"health" # 健康保健
],
"enumDescriptions": [
"电子产品:手机、电脑、家电等",
"服装:男装、女装、童装、鞋靴等",
"家居园艺:家具、装饰、园艺工具等",
"运动户外:健身器材、户外装备等",
"图书音像:书籍、音乐、影视等",
"美妆个护:化妆品、护肤品、个人护理等",
"食品饮料:零食、饮料、生鲜等",
"母婴用品:奶粉、玩具、童车等",
"汽车用品:汽车配件、装饰等",
"健康保健:保健品、医疗器械等"
]
},
"priceRange": {
"type": "object",
"description": "价格范围筛选(人民币),不设置表示不限价格",
"properties": {
"min": {
"type": "number",
"description": "最低价格(元)",
"minimum": 0,
"example": 100
},
"max": {
"type": "number",
"description": "最高价格(元)",
"minimum": 0,
"example": 5000
}
},
"additionalProperties": False
},
"sortBy": {
"type": "string",
"description": "结果排序方式",
"enum": ["relevance", "price_asc", "price_desc", "sales", "rating", "newest"],
"enumDescriptions": [
"relevance - 相关度排序(推荐)",
"price_asc - 价格从低到高",
"price_desc - 价格从高到低",
"sales - 销量排序",
"rating - 评分排序",
"newest - 最新上架"
],
"default": "relevance"
},
"limit": {
"type": "integer",
"description": "返回商品数量。建议20-50,数量过多会影响AI处理效率",
"minimum": 1,
"maximum": 100,
"default": 20
},
"includeOutOfStock": {
"type": "boolean",
"description": "是否包含缺货商品",
"default": False
},
"region": {
"type": "string",
"description": "配送地区,影响商品可用性和价格",
"examples": ["北京", "上海", "广州", "深圳"],
"default": "全国"
}
},
"required": ["keyword"],
"additionalProperties": False
}
验证约束设计 🛡️
多层次的验证策略
验证约束就像是多道安全门,层层把关确保数据质量。
java
// 🌟 全面的验证约束示例
public class FileOperationSchema {
public static Map<String, Object> createSchema() {
Map<String, Object> schema = new HashMap<>();
schema.put("type", "object");
schema.put("description", "文件操作工具,支持读取、写入、删除等操作");
Map<String, Object> properties = new HashMap<>();
// 操作类型 - 枚举约束
Map<String, Object> operation = new HashMap<>();
operation.put("type", "string");
operation.put("description", "要执行的文件操作类型");
operation.put("enum", Arrays.asList("read", "write", "append", "delete", "list", "copy", "move"));
Map<String, String> enumDescriptions = new HashMap<>();
enumDescriptions.put("read", "读取文件内容");
enumDescriptions.put("write", "写入内容到文件(覆盖)");
enumDescriptions.put("append", "追加内容到文件末尾");
enumDescriptions.put("delete", "删除文件");
enumDescriptions.put("list", "列出目录内容");
enumDescriptions.put("copy", "复制文件");
enumDescriptions.put("move", "移动/重命名文件");
operation.put("enumDescriptions", enumDescriptions);
// 文件路径 - 字符串格式约束
Map<String, Object> path = new HashMap<>();
path.put("type", "string");
path.put("description", "目标文件或目录的路径。必须是绝对路径,且在允许的目录范围内");
path.put("pattern", "^(/[^/\\0]+)+/?$|^[A-Za-z]:\\\\(?:[^\\\\/:*?\"<>|\\0]+\\\\)*[^\\\\/:*?\"<>|\\0]*$");
path.put("minLength", 1);
path.put("maxLength", 500);
path.put("examples", Arrays.asList(
"/home/user/documents/file.txt",
"/tmp/data.json",
"C:\\Users\\Username\\Documents\\file.txt"
));
// 文件内容 - 大小约束
Map<String, Object> content = new HashMap<>();
content.put("type", "string");
content.put("description", "文件内容(仅用于write和append操作)");
content.put("maxLength", 1024 * 1024); // 1MB限制
// 编码格式 - 枚举约束
Map<String, Object> encoding = new HashMap<>();
encoding.put("type", "string");
encoding.put("description", "文件编码格式");
encoding.put("enum", Arrays.asList("utf-8", "gbk", "ascii", "utf-16"));
encoding.put("default", "utf-8");
// 文件权限 - 自定义验证
Map<String, Object> permissions = new HashMap<>();
permissions.put("type", "string");
permissions.put("description", "文件权限(Unix格式,如755、644)");
permissions.put("pattern", "^[0-7]{3}$");
permissions.put("examples", Arrays.asList("755", "644", "600"));
// 目标路径(用于copy/move操作)
Map<String, Object> targetPath = new HashMap<>();
targetPath.put("type", "string");
targetPath.put("description", "目标路径(仅用于copy和move操作)");
targetPath.put("pattern", "^(/[^/\\0]+)+/?$|^[A-Za-z]:\\\\(?:[^\\\\/:*?\"<>|\\0]+\\\\)*[^\\\\/:*?\"<>|\\0]*$");
// 覆盖确认
Map<String, Object> overwrite = new HashMap<>();
overwrite.put("type", "boolean");
overwrite.put("description", "如果目标文件已存在,是否覆盖");
overwrite.put("default", false);
// 递归操作
Map<String, Object> recursive = new HashMap<>();
recursive.put("type", "boolean");
recursive.put("description", "是否递归处理目录(用于delete和list操作)");
recursive.put("default", false);
// 组装属性
properties.put("operation", operation);
properties.put("path", path);
properties.put("content", content);
properties.put("encoding", encoding);
properties.put("permissions", permissions);
properties.put("targetPath", targetPath);
properties.put("overwrite", overwrite);
properties.put("recursive", recursive);
schema.put("properties", properties);
// 必需字段
schema.put("required", Arrays.asList("operation", "path"));
// 条件验证 - 使用JSON Schema的条件逻辑
List<Map<String, Object>> allOf = new ArrayList<>();
// 如果operation是write或append,则content是必需的
Map<String, Object> writeCondition = new HashMap<>();
Map<String, Object> writeIf = new HashMap<>();
writeIf.put("properties", Map.of("operation", Map.of("enum", Arrays.asList("write", "append"))));
Map<String, Object> writeThen = new HashMap<>();
writeThen.put("required", Arrays.asList("content"));
writeCondition.put("if", writeIf);
writeCondition.put("then", writeThen);
allOf.add(writeCondition);
// 如果operation是copy或move,则targetPath是必需的
Map<String, Object> copyCondition = new HashMap<>();
Map<String, Object> copyIf = new HashMap<>();
copyIf.put("properties", Map.of("operation", Map.of("enum", Arrays.asList("copy", "move"))));
Map<String, Object> copyThen = new HashMap<>();
copyThen.put("required", Arrays.asList("targetPath"));
copyCondition.put("if", copyIf);
copyCondition.put("then", copyThen);
allOf.add(copyCondition);
schema.put("allOf", allOf);
return schema;
}
}
// 使用自定义验证器增强验证逻辑
public class FileOperationValidator extends JsonSchemaValidator {
private final Set<String> allowedDirectories = Set.of(
"/tmp/mcp/",
"/var/mcp/data/",
"/home/mcp/workspace/"
);
@Override
public ValidationResult validate(Map<String, Object> parameters) {
// 先执行基础JSON Schema验证
ValidationResult baseResult = super.validate(parameters);
if (!baseResult.isValid()) {
return baseResult;
}
List<String> errors = new ArrayList<>();
// 自定义验证:路径安全检查
String path = (String) parameters.get("path");
if (!isPathSafe(path)) {
errors.add("文件路径不安全或不在允许的目录范围内");
}
// 自定义验证:内容大小检查(更精确)
if (parameters.containsKey("content")) {
String content = (String) parameters.get("content");
if (content != null && content.getBytes(StandardCharsets.UTF_8).length > 1024 * 1024) {
errors.add("文件内容超过1MB限制");
}
}
// 自定义验证:操作权限检查
String operation = (String) parameters.get("operation");
if ("delete".equals(operation) && !hasDeletePermission(path)) {
errors.add("没有删除该文件的权限");
}
return new ValidationResult(errors.isEmpty(), errors);
}
private boolean isPathSafe(String path) {
try {
Path normalizedPath = Paths.get(path).normalize();
String normalizedStr = normalizedPath.toString();
// 检查路径遍历攻击
if (normalizedStr.contains("..")) {
return false;
}
// 检查是否在允许的目录内
return allowedDirectories.stream()
.anyMatch(allowedDir -> normalizedStr.startsWith(allowedDir));
} catch (Exception e) {
return false;
}
}
private boolean hasDeletePermission(String path) {
// 实现删除权限检查逻辑
// 这里可以检查文件所有者、权限位等
return !path.contains("/system/") && !path.contains("/etc/");
}
}
一致的返回结构 🔄
标准化的响应格式
一致的返回结构就像是统一的包装盒,让用户(AI模型)能够预期和处理结果。
python
# 🌟 标准化的响应结构设计
from dataclasses import dataclass
from typing import Any, Dict, List, Optional, Union
from enum import Enum
import json
from datetime import datetime
class ResponseStatus(Enum):
SUCCESS = "success"
PARTIAL_SUCCESS = "partial_success"
ERROR = "error"
WARNING = "warning"
@dataclass
class ResponseMetadata:
"""响应元数据"""
timestamp: str
execution_time_ms: int
request_id: str
tool_version: str
def to_dict(self) -> Dict[str, Any]:
return {
"timestamp": self.timestamp,
"execution_time_ms": self.execution_time_ms,
"request_id": self.request_id,
"tool_version": self.tool_version
}
@dataclass
class ResponseError:
"""错误信息"""
code: str
message: str
details: Optional[Dict[str, Any]] = None
def to_dict(self) -> Dict[str, Any]:
result = {
"code": self.code,
"message": self.message
}
if self.details:
result["details"] = self.details
return result
class StandardToolResponse:
"""标准化的工具响应"""
def __init__(
self,
status: ResponseStatus,
data: Any = None,
error: Optional[ResponseError] = None,
warnings: List[str] = None,
metadata: Optional[ResponseMetadata] = None
):
self.status = status
self.data = data
self.error = error
self.warnings = warnings or []
self.metadata = metadata or self._generate_default_metadata()
def _generate_default_metadata(self) -> ResponseMetadata:
return ResponseMetadata(
timestamp=datetime.utcnow().isoformat() + "Z",
execution_time_ms=0,
request_id="",
tool_version="1.0.0"
)
def to_dict(self) -> Dict[str, Any]:
"""转换为字典格式"""
result = {
"status": self.status.value,
"metadata": self.metadata.to_dict()
}
if self.data is not None:
result["data"] = self.data
if self.error:
result["error"] = self.error.to_dict()
if self.warnings:
result["warnings"] = self.warnings
return result
def to_json(self, indent: int = None) -> str:
"""转换为JSON字符串"""
return json.dumps(self.to_dict(), indent=indent, ensure_ascii=False)
@classmethod
def success(
cls,
data: Any,
warnings: List[str] = None,
metadata: Optional[ResponseMetadata] = None
) -> 'StandardToolResponse':
"""创建成功响应"""
return cls(
status=ResponseStatus.SUCCESS,
data=data,
warnings=warnings,
metadata=metadata
)
@classmethod
def error(
cls,
error_code: str,
error_message: str,
error_details: Optional[Dict[str, Any]] = None,
metadata: Optional[ResponseMetadata] = None
) -> 'StandardToolResponse':
"""创建错误响应"""
return cls(
status=ResponseStatus.ERROR,
error=ResponseError(
code=error_code,
message=error_message,
details=error_details
),
metadata=metadata
)
@classmethod
def partial_success(
cls,
data: Any,
warnings: List[str],
metadata: Optional[ResponseMetadata] = None
) -> 'StandardToolResponse':
"""创建部分成功响应"""
return cls(
status=ResponseStatus.PARTIAL_SUCCESS,
data=data,
warnings=warnings,
metadata=metadata
)
# 具体工具实现示例
class WeatherTool:
def __init__(self):
self.tool_version = "2.1.0"
async def execute(self, parameters: Dict[str, Any]) -> StandardToolResponse:
start_time = datetime.now()
request_id = self._generate_request_id()
try:
# 参数验证
location = parameters.get('location')
if not location:
return StandardToolResponse.error(
error_code="MISSING_PARAMETER",
error_message="location参数是必需的",
metadata=self._create_metadata(start_time, request_id)
)
# 执行天气查询
weather_data = await self._fetch_weather_data(location)
# 检查数据完整性
warnings = []
if not weather_data.get('forecast'):
warnings.append("未能获取预报数据,仅返回当前天气")
# 格式化响应数据
formatted_data = {
"location": weather_data['location'],
"current": {
"temperature": weather_data['current']['temp'],
"description": weather_data['current']['desc'],
"humidity": weather_data['current']['humidity'],
"wind_speed": weather_data['current']['wind_speed'],
"last_updated": weather_data['current']['updated_at']
}
}
# 添加预报数据(如果有)
if weather_data.get('forecast'):
formatted_data["forecast"] = [
{
"date": day['date'],
"high_temp": day['high'],
"low_temp": day['low'],
"description": day['desc'],
"chance_of_rain": day.get('rain_chance', 0)
}
for day in weather_data['forecast']
]
# 返回响应
if warnings:
return StandardToolResponse.partial_success(
data=formatted_data,
warnings=warnings,
metadata=self._create_metadata(start_time, request_id)
)
else:
return StandardToolResponse.success(
data=formatted_data,
metadata=self._create_metadata(start_time, request_id)
)
except LocationNotFoundError as e:
return StandardToolResponse.error(
error_code="LOCATION_NOT_FOUND",
error_message=f"未找到位置:{location}",
error_details={"searched_location": location, "suggestions": e.suggestions},
metadata=self._create_metadata(start_time, request_id)
)
except WeatherServiceError as e:
return StandardToolResponse.error(
error_code="SERVICE_UNAVAILABLE",
error_message="天气服务暂时不可用,请稍后重试",
error_details={"service_error": str(e), "retry_after_seconds": 60},
metadata=self._create_metadata(start_time, request_id)
)
except Exception as e:
return StandardToolResponse.error(
error_code="INTERNAL_ERROR",
error_message="内部错误,请联系支持团队",
error_details={"error_type": type(e).__name__},
metadata=self._create_metadata(start_time, request_id)
)
def _create_metadata(self, start_time: datetime, request_id: str) -> ResponseMetadata:
execution_time = (datetime.now() - start_time).total_seconds() * 1000
return ResponseMetadata(
timestamp=datetime.utcnow().isoformat() + "Z",
execution_time_ms=int(execution_time),
request_id=request_id,
tool_version=self.tool_version
)
def _generate_request_id(self) -> str:
import uuid
return str(uuid.uuid4())
# 使用示例
async def demo_weather_tool():
weather_tool = WeatherTool()
# 成功情况
response = await weather_tool.execute({"location": "北京"})
print("成功响应:")
print(response.to_json(indent=2))
# 错误情况
response = await weather_tool.execute({}) # 缺少location参数
print("\n错误响应:")
print(response.to_json(indent=2))
# 部分成功情况
response = await weather_tool.execute({"location": "偏远小镇"}) # 只有当前天气,没有预报
print("\n部分成功响应:")
print(response.to_json(indent=2))
# 输出示例:
"""
成功响应:
{
"status": "success",
"data": {
"location": "北京",
"current": {
"temperature": 15,
"description": "晴朗",
"humidity": 45,
"wind_speed": 12,
"last_updated": "2024-03-15T14:30:00Z"
},
"forecast": [
{
"date": "2024-03-16",
"high_temp": 18,
"low_temp": 8,
"description": "多云",
"chance_of_rain": 20
}
]
},
"metadata": {
"timestamp": "2024-03-15T14:30:15Z",
"execution_time_ms": 245,
"request_id": "550e8400-e29b-41d4-a716-446655440000",
"tool_version": "2.1.0"
}
}
错误响应:
{
"status": "error",
"error": {
"code": "MISSING_PARAMETER",
"message": "location参数是必需的"
},
"metadata": {
"timestamp": "2024-03-15T14:30:16Z",
"execution_time_ms": 2,
"request_id": "660f9511-f3ac-52e5-b827-556766551111",
"tool_version": "2.1.0"
}
}
"""
Schema版本控制 📈
优雅地处理Schema演进
Schema版本控制就像是软件版本管理,需要考虑向前兼容和平滑升级。
typescript
// 🌟 Schema版本控制实现
interface SchemaVersion {
version: string;
releaseDate: string;
changes: string[];
deprecated?: string[];
breaking?: boolean;
}
class VersionedSchema {
private schemas: Map<string, any> = new Map();
private versions: SchemaVersion[] = [];
private currentVersion: string;
constructor(currentVersion: string) {
this.currentVersion = currentVersion;
}
// 注册Schema版本
registerVersion(version: string, schema: any, versionInfo: SchemaVersion) {
this.schemas.set(version, schema);
this.versions.push(versionInfo);
this.versions.sort((a, b) => this.compareVersions(a.version, b.version));
}
// 获取Schema(支持版本请求)
getSchema(requestedVersion?: string): any {
const version = requestedVersion || this.currentVersion;
if (!this.schemas.has(version)) {
throw new Error(`Schema版本 ${version} 不存在`);
}
const schema = this.schemas.get(version);
// 添加版本信息到Schema
return {
...schema,
$schema: "http://json-schema.org/draft-07/schema#",
$version: version,
$versionInfo: this.getVersionInfo(version)
};
}
// 验证参数(自动处理版本兼容性)
async validateParameters(
parameters: any,
requestedVersion?: string
): Promise<{
isValid: boolean;
errors: string[];
warnings: string[];
migratedParameters?: any;
}> {
const version = requestedVersion || this.currentVersion;
const schema = this.getSchema(version);
// 基础验证
const validationResult = this.validateAgainstSchema(parameters, schema);
if (!validationResult.isValid) {
return validationResult;
}
// 版本迁移检查
if (version !== this.currentVersion) {
const migrationResult = await this.migrateParameters(
parameters,
version,
this.currentVersion
);
return {
...validationResult,
warnings: [
...validationResult.warnings,
`参数已从版本 ${version} 迁移到 ${this.currentVersion}`,
...migrationResult.warnings
],
migratedParameters: migrationResult.parameters
};
}
return validationResult;
}
// 参数迁移
private async migrateParameters(
parameters: any,
fromVersion: string,
toVersion: string
): Promise<{
parameters: any;
warnings: string[];
}> {
const warnings: string[] = [];
let migratedParams = { ...parameters };
// 获取版本间的迁移路径
const migrationPath = this.getMigrationPath(fromVersion, toVersion);
for (const migration of migrationPath) {
const result = await this.applyMigration(migratedParams, migration);
migratedParams = result.parameters;
warnings.push(...result.warnings);
}
return { parameters: migratedParams, warnings };
}
private compareVersions(v1: string, v2: string): number {
const parts1 = v1.split('.').map(Number);
const parts2 = v2.split('.').map(Number);
for (let i = 0; i < Math.max(parts1.length, parts2.length); i++) {
const part1 = parts1[i] || 0;
const part2 = parts2[i] || 0;
if (part1 !== part2) {
return part1 - part2;
}
}
return 0;
}
}
// 实际使用示例:搜索工具的Schema演进
class SearchToolSchema extends VersionedSchema {
constructor() {
super("2.1.0");
this.initializeVersions();
}
private initializeVersions() {
// 版本 1.0.0 - 初始版本
this.registerVersion("1.0.0", {
type: "object",
properties: {
query: {
type: "string",
description: "搜索关键词"
},
limit: {
type: "integer",
minimum: 1,
maximum: 50,
default: 10
}
},
required: ["query"]
}, {
version: "1.0.0",
releaseDate: "2024-01-01",
changes: ["初始发布"]
});
// 版本 1.1.0 - 添加过滤功能
this.registerVersion("1.1.0", {
type: "object",
properties: {
query: {
type: "string",
description: "搜索关键词",
minLength: 1,
maxLength: 200
},
limit: {
type: "integer",
minimum: 1,
maximum: 50,
default: 10
},
filters: {
type: "object",
description: "搜索过滤条件",
properties: {
category: { type: "string" },
dateRange: { type: "string" }
}
}
},
required: ["query"]
}, {
version: "1.1.0",
releaseDate: "2024-02-01",
changes: [
"添加filters参数支持类别和日期范围过滤",
"为query参数添加长度限制"
]
});
// 版本 2.0.0 - 重大更新
this.registerVersion("2.0.0", {
type: "object",
properties: {
searchTerms: { // 重命名:query -> searchTerms
type: "string",
description: "搜索关键词或短语",
minLength: 1,
maxLength: 500
},
maxResults: { // 重命名:limit -> maxResults
type: "integer",
minimum: 1,
maximum: 100,
default: 20
},
filterCriteria: { // 重命名:filters -> filterCriteria
type: "object",
description: "高级搜索过滤条件",
properties: {
category: {
type: "string",
enum: ["all", "products", "articles", "images", "videos"]
},
timeRange: { // 重命名:dateRange -> timeRange
type: "string",
enum: ["any", "day", "week", "month", "year"]
},
language: { // 新增
type: "string",
default: "auto"
}
}
},
sortBy: { // 新增
type: "string",
enum: ["relevance", "date", "popularity"],
default: "relevance"
}
},
required: ["searchTerms"]
}, {
version: "2.0.0",
releaseDate: "2024-03-01",
changes: [
"重命名query为searchTerms以更清晰",
"重命名limit为maxResults",
"重构filters为filterCriteria",
"添加language和sortBy参数",
"提高maxResults上限到100"
],
deprecated: ["query", "limit", "filters"],
breaking: true
});
// 版本 2.1.0 - 当前版本
this.registerVersion("2.1.0", {
type: "object",
properties: {
searchTerms: {
type: "string",
description: "搜索关键词或短语。支持布尔操作符(AND, OR, NOT)",
minLength: 1,
maxLength: 500,
examples: [
"人工智能",
"机器学习 AND 深度学习",
"Python OR JavaScript"
]
},
maxResults: {
type: "integer",
minimum: 1,
maximum: 100,
default: 20
},
filterCriteria: {
type: "object",
properties: {
category: {
type: "string",
enum: ["all", "products", "articles", "images", "videos", "news"], // 新增news
default: "all"
},
timeRange: {
type: "string",
enum: ["any", "hour", "day", "week", "month", "year"], // 新增hour
default: "any"
},
language: {
type: "string",
enum: ["auto", "zh", "en", "ja", "ko"], // 扩展语言支持
default: "auto"
},
region: { // 新增
type: "string",
description: "搜索地区限制",
examples: ["CN", "US", "JP"]
}
}
},
sortBy: {
type: "string",
enum: ["relevance", "date", "popularity", "rating"], // 新增rating
default: "relevance"
},
includeMetadata: { // 新增
type: "boolean",
description: "是否包含搜索元数据",
default: false
}
},
required: ["searchTerms"]
}, {
version: "2.1.0",
releaseDate: "2024-04-01",
changes: [
"searchTerms支持布尔操作符",
"category新增news选项",
"timeRange新增hour选项",
"扩展language支持更多语言",
"新增region和includeMetadata参数",
"sortBy新增rating选项"
]
});
}
}
// 使用示例
const searchSchema = new SearchToolSchema();
// 获取最新Schema
const latestSchema = searchSchema.getSchema();
console.log("最新Schema:", JSON.stringify(latestSchema, null, 2));
// 验证旧版本参数
const oldParams = {
query: "人工智能", // 旧参数名
limit: 15, // 旧参数名
filters: { // 旧参数名
category: "articles"
}
};
const validationResult = await searchSchema.validateParameters(oldParams, "1.1.0");
console.log("验证结果:", validationResult);
小结
Schema设计的艺术在于平衡几个关键要素:
🎨 设计原则
- 清晰描述 - 用户友好的参数说明和示例
- 合理约束 - 多层次的验证保护系统安全
- 一致结构 - 标准化的响应格式提升用户体验
- 版本控制 - 优雅地处理Schema演进和兼容性
💡 实践要点
- 站在用户角度思考Schema设计
- 提供充分的示例和文档
- 实施全面的参数验证
- 建立标准化的响应格式
- 规划好版本升级策略
🎯 设计哲学:好的Schema不是约束,而是指导。它应该让用户(AI模型)更容易、更准确地使用你的工具。
下一节:错误处理策略 - 学习如何优雅地处理各种异常情况