наблюдаемость: измерять бюджет каталога MCP
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
use crank_core::{
|
||||
ToolCatalogAnalysis, ToolQualityCatalogTool, ToolQualitySeverity, analyze_tool_catalog,
|
||||
};
|
||||
use serde_json::json;
|
||||
|
||||
#[test]
|
||||
fn measures_the_actual_serialized_catalog_definition() {
|
||||
let definitions = vec![json!({
|
||||
"name": "get_exchange_rate",
|
||||
"title": "Получить курс",
|
||||
"description": "Возвращает актуальный курс выбранной валютной пары.",
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"base": {"type": "string"},
|
||||
"quote": {"type": "string"}
|
||||
},
|
||||
"required": ["base", "quote"]
|
||||
}
|
||||
})];
|
||||
|
||||
let analysis = analyze_tool_catalog(&[tool("get_exchange_rate")], &definitions).unwrap();
|
||||
let expected_bytes = serde_json::to_vec(&definitions[0]).unwrap().len();
|
||||
|
||||
assert_eq!(analysis.budget.tool_count, 1);
|
||||
assert_eq!(analysis.budget.serialized_bytes, expected_bytes);
|
||||
assert!(analysis.budget.estimated_context_tokens > 0);
|
||||
assert_eq!(
|
||||
analysis.budget.largest_tool_estimated_context_tokens,
|
||||
analysis.budget.estimated_context_tokens
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn warns_when_actual_catalog_exceeds_context_budget() {
|
||||
let tools = (0..9)
|
||||
.map(|index| tool(&format!("large_tool_{index}")))
|
||||
.collect::<Vec<_>>();
|
||||
let definitions = (0..9)
|
||||
.map(|index| {
|
||||
json!({
|
||||
"name": format!("large_tool_{index}"),
|
||||
"description": "x".repeat(1_500),
|
||||
"inputSchema": {"type": "object", "properties": {}}
|
||||
})
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let analysis = analyze_tool_catalog(&tools, &definitions).unwrap();
|
||||
|
||||
assert!(analysis.budget.exceeds_recommended_budget);
|
||||
assert!(has_warning(&analysis, "agent_catalog_context_budget_high"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_mismatch_between_catalog_tools_and_definitions() {
|
||||
let error = analyze_tool_catalog(&[tool("one")], &[]).unwrap_err();
|
||||
|
||||
assert_eq!(
|
||||
error.to_string(),
|
||||
"tool catalog and definitions length mismatch"
|
||||
);
|
||||
}
|
||||
|
||||
fn tool(name: &str) -> ToolQualityCatalogTool {
|
||||
ToolQualityCatalogTool {
|
||||
name: name.to_owned(),
|
||||
display_name: name.to_owned(),
|
||||
description: format!("Инструмент {name} выполняет одну конкретную операцию."),
|
||||
}
|
||||
}
|
||||
|
||||
fn has_warning(analysis: &ToolCatalogAnalysis, code: &str) -> bool {
|
||||
analysis
|
||||
.quality
|
||||
.findings
|
||||
.iter()
|
||||
.any(|finding| finding.code == code && finding.severity == ToolQualitySeverity::Warning)
|
||||
}
|
||||
Reference in New Issue
Block a user