agent_trace/llm/
synthesis_engine.rs1use crate::llm::trace_insights::TraceDocument;
2
3pub trait SynthesisEngine: Send + Sync {
5 fn summarize_change(&self, path: &str, doc_type: &str, diff: &str) -> Result<String, String>;
6 fn synthesize_context(
7 &self,
8 documents: &[TraceDocument],
9 updates: &[String],
10 ) -> Result<String, String>;
11 fn update_running_summary(
12 &self,
13 previous: &str,
14 events: &str,
15 plan: &str,
16 ) -> Result<String, String>;
17 fn summarize_session(&self, session_id: &str, events: &[String]) -> Result<String, String>;
18 fn summarize_event_history(&self, events: &str) -> Result<String, String>;
19 fn backend_label(&self) -> &str;
20}
21
22pub struct DegradedBackend;
24
25impl DegradedBackend {
26 fn line_stats_summary(path: &str, diff: &str) -> String {
27 let added = diff.lines().filter(|l| l.starts_with('+')).count();
28 let removed = diff.lines().filter(|l| l.starts_with('-')).count();
29 format!("{path}: +{added} lines, -{removed} lines.")
30 }
31}
32
33impl SynthesisEngine for DegradedBackend {
34 fn summarize_change(&self, path: &str, _doc_type: &str, diff: &str) -> Result<String, String> {
35 Ok(Self::line_stats_summary(path, diff))
36 }
37
38 fn synthesize_context(
39 &self,
40 documents: &[TraceDocument],
41 _updates: &[String],
42 ) -> Result<String, String> {
43 let mut out =
44 String::from("# Project Context\n\n*(Synthesis degraded — configure `agent-trace model setup`)*\n\n## Documents\n\n");
45 for doc in documents {
46 out.push_str(&format!("- `{}` [{}]\n", doc.path, doc.doc_type));
47 }
48 Ok(out)
49 }
50
51 fn update_running_summary(
52 &self,
53 _previous: &str,
54 events: &str,
55 _plan: &str,
56 ) -> Result<String, String> {
57 let mut out = String::from(
58 "# Running Summary\n\n*(Synthesis degraded — configure `agent-trace model setup`)*\n\n## Recent Activity\n\n",
59 );
60 for line in events.lines().take(15) {
61 out.push_str(&format!("- {line}\n"));
62 }
63 Ok(out)
64 }
65
66 fn summarize_session(&self, session_id: &str, events: &[String]) -> Result<String, String> {
67 Ok(format!(
68 "Session {session_id}: {} event(s) recorded (degraded summary).",
69 events.len()
70 ))
71 }
72
73 fn summarize_event_history(&self, events: &str) -> Result<String, String> {
74 let n = events.lines().filter(|l| !l.trim().is_empty()).count();
75 Ok(format!(
76 "{n} earlier events recorded (degraded summary); see plan.md for phase checklist."
77 ))
78 }
79
80 fn backend_label(&self) -> &str {
81 "degraded"
82 }
83}
84
85#[cfg(test)]
86mod tests {
87 use super::*;
88 use crate::types::DocType;
89
90 #[test]
91 fn degraded_summarize_change_counts_lines() {
92 let b = DegradedBackend;
93 let s = b.summarize_change("a.md", "plan", "+a\n+b\n-c").unwrap();
94 assert!(s.contains("+2"));
95 assert!(s.contains("-1"));
96 }
97
98 #[test]
99 fn degraded_context_lists_documents() {
100 let b = DegradedBackend;
101 let docs = vec![TraceDocument {
102 path: "plan.md".into(),
103 doc_type: DocType::Plan,
104 content_snippet: "goals".into(),
105 }];
106 let out = b.synthesize_context(&docs, &[]).unwrap();
107 assert!(out.contains("plan.md"));
108 }
109}