lean_ctx_server/tools/
ctx_feedback.rs1use lean_ctx::core::llm_feedback::{LlmFeedbackEvent, LlmFeedbackStore};
3
4pub fn record(event: &LlmFeedbackEvent) -> Result<String, String> {
5 LlmFeedbackStore::record(event.clone())?;
6 Ok("feedback recorded".to_string())
7}
8
9pub fn status() -> String {
10 let s = LlmFeedbackStore::status();
11 format!(
12 "ctx_feedback status\n path: {}\n bytes: {}\n retention: max_events={} max_bytes={}",
13 s.path.display(),
14 s.bytes,
15 s.max_events,
16 s.max_bytes
17 )
18}
19
20pub fn report(limit: usize) -> String {
21 let s = LlmFeedbackStore::summarize(limit);
22 if s.total_events == 0 {
23 return "No LLM feedback recorded yet.".to_string();
24 }
25
26 let mut lines = vec![
27 format!("LLM Feedback Report (last {limit} events)"),
28 format!(" total_events: {}", s.total_events),
29 format!(" avg_output_ratio: {:.2}", s.avg_output_ratio),
30 format!(" max_output_tokens: {}", s.max_output_tokens),
31 format!(" max_output_ratio: {:.2}", s.max_output_ratio),
32 ];
33 if let Some(ms) = s.avg_latency_ms {
34 lines.push(format!(" avg_latency_ms: {ms:.0}"));
35 }
36
37 if !s.by_model.is_empty() {
38 lines.push(" by_model:".to_string());
39 for (model, m) in s.by_model.iter().take(10) {
40 let mut row = format!(
41 " {model}: n={} avg_ratio={:.2} max_out={}",
42 m.events, m.avg_output_ratio, m.max_output_tokens
43 );
44 if let Some(ms) = m.avg_latency_ms {
45 row.push_str(&format!(" avg_ms={ms:.0}"));
46 }
47 lines.push(row);
48 }
49 }
50
51 lines.join("\n")
52}
53
54pub fn json(limit: usize) -> String {
55 let status = LlmFeedbackStore::status();
56 let summary = LlmFeedbackStore::summarize(limit);
57 let recent = LlmFeedbackStore::recent(limit.min(200));
58 serde_json::json!({
59 "status": status,
60 "summary": summary,
61 "recent": recent,
62 })
63 .to_string()
64}
65
66pub fn reset() -> String {
67 match LlmFeedbackStore::reset() {
68 Ok(()) => "LLM feedback has been reset.".to_string(),
69 Err(e) => format!("Error resetting feedback: {e}"),
70 }
71}