наблюдаемость: измерять бюджет каталога MCP
CI / Rust Checks (push) Successful in 12m5s
CI / UI Checks (push) Successful in 9s
CI / Deployment Manifests (push) Successful in 6s
CI / Frontend E2E (push) Failing after 30s
CI / Deploy (push) Has been skipped

This commit is contained in:
2026-07-21 01:59:09 +03:00
parent 0241d186ea
commit 63f8ee333f
10 changed files with 284 additions and 4 deletions
+1
View File
@@ -1,3 +1,4 @@
mod unit {
mod tool_catalog;
mod tool_quality;
}
@@ -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)
}