Warn about broad tool response projections
Deploy / deploy (push) Successful in 1m40s
CI / Rust Checks (push) Failing after 5m3s
CI / UI Checks (push) Has been skipped
CI / Frontend E2E (push) Has been skipped
CI / Deployment Manifests (push) Has been skipped

This commit is contained in:
github-ops
2026-06-20 21:05:30 +00:00
parent 5970db5449
commit ce773e6196
10 changed files with 328 additions and 8 deletions
+43
View File
@@ -7,6 +7,7 @@ const MIN_DESCRIPTION_CHARS: usize = 40;
const GENERIC_TOOL_NAMES: &[&str] = &["call_api", "manage", "execute", "request"];
const MULTI_ACTION_FIELD_NAMES: &[&str] = &["action", "mode", "operation", "type"];
const ENUM_LIKE_FIELD_NAMES: &[&str] = &["mode", "type", "status", "category"];
const MANY_PROJECTED_FIELDS_THRESHOLD: usize = 12;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
@@ -58,6 +59,17 @@ pub struct ToolQualitySchemaNode {
pub enum_values: Vec<String>,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct ToolQualityMappingRule {
pub source: String,
pub target: String,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Default)]
pub struct ToolQualityMappingSet {
pub rules: Vec<ToolQualityMappingRule>,
}
impl ToolQualityReport {
pub fn new(findings: Vec<ToolQualityFinding>) -> Self {
let blocking = findings
@@ -175,6 +187,37 @@ pub fn analyze_tool_schema_quality(
ToolQualityReport::new(findings)
}
pub fn analyze_tool_response_projection_quality(
output_mapping: &ToolQualityMappingSet,
) -> ToolQualityReport {
let mut findings = Vec::new();
for (index, rule) in output_mapping.rules.iter().enumerate() {
let source = rule.source.trim();
if source == "$.response.body" || source == "$.response.data" {
findings.push(finding(
ToolQualitySeverity::Warning,
"response_projection_full_body",
"Результат инструмента отдает агенту весь ответ API.",
Some("Выберите только поля, которые нужны агенту для ответа пользователю или следующего действия."),
Some(&format!("output_mapping.rules.{index}.source")),
));
}
}
if output_mapping.rules.len() >= MANY_PROJECTED_FIELDS_THRESHOLD {
findings.push(finding(
ToolQualitySeverity::Info,
"response_projection_many_fields",
"В результат инструмента попадает много полей.",
Some("Проверьте, что каждое поле действительно нужно агенту. Лишние данные увеличивают риск ошибок и галлюцинаций."),
Some("output_mapping.rules"),
));
}
ToolQualityReport::new(findings)
}
fn is_safe_tool_name(value: &str) -> bool {
let mut chars = value.chars();
let Some(first) = chars.next() else {