diff --git a/crates/zeph-core/src/agent/streaming.rs b/crates/zeph-core/src/agent/streaming.rs index d1fd0adb..c0a35394 100644 --- a/crates/zeph-core/src/agent/streaming.rs +++ b/crates/zeph-core/src/agent/streaming.rs @@ -262,10 +262,29 @@ impl Agent { if let Some(ref fs) = output.filter_stats { let saved = fs.estimated_tokens_saved() as u64; let raw = (fs.raw_chars / 4) as u64; + let confidence = fs.confidence; + let was_filtered = fs.filtered_chars < fs.raw_chars; self.update_metrics(|m| { m.filter_raw_tokens += raw; m.filter_saved_tokens += saved; m.filter_applications += 1; + m.filter_total_commands += 1; + if was_filtered { + m.filter_filtered_commands += 1; + } + if let Some(c) = confidence { + match c { + zeph_tools::FilterConfidence::Full => { + m.filter_confidence_full += 1; + } + zeph_tools::FilterConfidence::Partial => { + m.filter_confidence_partial += 1; + } + zeph_tools::FilterConfidence::Fallback => { + m.filter_confidence_fallback += 1; + } + } + } }); } if output.summary.trim().is_empty() { diff --git a/crates/zeph-core/src/metrics.rs b/crates/zeph-core/src/metrics.rs index 212b234d..52667bd1 100644 --- a/crates/zeph-core/src/metrics.rs +++ b/crates/zeph-core/src/metrics.rs @@ -29,6 +29,11 @@ pub struct MetricsSnapshot { pub filter_raw_tokens: u64, pub filter_saved_tokens: u64, pub filter_applications: u64, + pub filter_total_commands: u64, + pub filter_filtered_commands: u64, + pub filter_confidence_full: u64, + pub filter_confidence_partial: u64, + pub filter_confidence_fallback: u64, } pub struct MetricsCollector { @@ -111,6 +116,26 @@ mod tests { assert_eq!(s.filter_applications, 2); } + #[test] + fn filter_confidence_and_command_metrics() { + let (collector, rx) = MetricsCollector::new(); + collector.update(|m| { + m.filter_total_commands += 1; + m.filter_filtered_commands += 1; + m.filter_confidence_full += 1; + }); + collector.update(|m| { + m.filter_total_commands += 1; + m.filter_confidence_partial += 1; + }); + let s = rx.borrow(); + assert_eq!(s.filter_total_commands, 2); + assert_eq!(s.filter_filtered_commands, 1); + assert_eq!(s.filter_confidence_full, 1); + assert_eq!(s.filter_confidence_partial, 1); + assert_eq!(s.filter_confidence_fallback, 0); + } + #[test] fn summaries_count_tracks_summarizations() { let (collector, rx) = MetricsCollector::new(); diff --git a/crates/zeph-tools/src/executor.rs b/crates/zeph-tools/src/executor.rs index 11a46683..afcda1d3 100644 --- a/crates/zeph-tools/src/executor.rs +++ b/crates/zeph-tools/src/executor.rs @@ -13,6 +13,7 @@ pub struct ToolCall { pub struct FilterStats { pub raw_chars: usize, pub filtered_chars: usize, + pub confidence: Option, } impl FilterStats { @@ -272,6 +273,7 @@ mod tests { let fs = FilterStats { raw_chars: 1000, filtered_chars: 200, + ..Default::default() }; assert!((fs.savings_pct() - 80.0).abs() < 0.01); } @@ -287,6 +289,7 @@ mod tests { let fs = FilterStats { raw_chars: 1000, filtered_chars: 200, + ..Default::default() }; assert_eq!(fs.estimated_tokens_saved(), 200); // (1000 - 200) / 4 } diff --git a/crates/zeph-tools/src/filter/mod.rs b/crates/zeph-tools/src/filter/mod.rs index c5f2deb9..e3bb5256 100644 --- a/crates/zeph-tools/src/filter/mod.rs +++ b/crates/zeph-tools/src/filter/mod.rs @@ -137,7 +137,8 @@ impl<'a> FilterPipeline<'a> { } } -fn worse_confidence(a: FilterConfidence, b: FilterConfidence) -> FilterConfidence { +#[must_use] +pub fn worse_confidence(a: FilterConfidence, b: FilterConfidence) -> FilterConfidence { match (a, b) { (FilterConfidence::Fallback, _) | (_, FilterConfidence::Fallback) => { FilterConfidence::Fallback diff --git a/crates/zeph-tools/src/shell.rs b/crates/zeph-tools/src/shell.rs index 5392cf2a..da5321fd 100644 --- a/crates/zeph-tools/src/shell.rs +++ b/crates/zeph-tools/src/shell.rs @@ -231,6 +231,10 @@ impl ShellExecutor { cumulative_filter_stats.get_or_insert_with(FilterStats::default); stats.raw_chars += fr.raw_chars; stats.filtered_chars += fr.filtered_chars; + stats.confidence = Some(match (stats.confidence, fr.confidence) { + (Some(prev), cur) => crate::filter::worse_confidence(prev, cur), + (None, cur) => cur, + }); fr.output } None => sanitized, diff --git a/crates/zeph-tui/src/widgets/resources.rs b/crates/zeph-tui/src/widgets/resources.rs index 1be0b0cc..d86e1739 100644 --- a/crates/zeph-tui/src/widgets/resources.rs +++ b/crates/zeph-tui/src/widgets/resources.rs @@ -28,6 +28,16 @@ pub fn render(metrics: &MetricsSnapshot, frame: &mut Frame, area: Rect) { ))); } if metrics.filter_applications > 0 { + #[allow(clippy::cast_precision_loss)] + let hit_pct = if metrics.filter_total_commands > 0 { + metrics.filter_filtered_commands as f64 / metrics.filter_total_commands as f64 * 100.0 + } else { + 0.0 + }; + res_lines.push(Line::from(format!( + " Filter: {}/{} commands ({hit_pct:.0}% hit rate)", + metrics.filter_filtered_commands, metrics.filter_total_commands, + ))); #[allow(clippy::cast_precision_loss)] let pct = if metrics.filter_raw_tokens > 0 { metrics.filter_saved_tokens as f64 / metrics.filter_raw_tokens as f64 * 100.0 @@ -38,6 +48,12 @@ pub fn render(metrics: &MetricsSnapshot, frame: &mut Frame, area: Rect) { " Filter saved: {} tok ({pct:.0}%)", metrics.filter_saved_tokens, ))); + res_lines.push(Line::from(format!( + " Confidence: F/{} P/{} B/{}", + metrics.filter_confidence_full, + metrics.filter_confidence_partial, + metrics.filter_confidence_fallback, + ))); } let resources = Paragraph::new(res_lines).block( Block::default()