use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::time::{SystemTime, UNIX_EPOCH};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConversationSummary {
pub id: String,
pub timestamp: u64,
pub session_duration_seconds: u64,
pub total_turns: usize,
pub key_decisions: Vec<KeyDecision>,
pub completed_tasks: Vec<TaskSummary>,
pub error_patterns: Vec<ErrorPattern>,
pub context_recommendations: Vec<String>,
pub summary_text: String,
pub compression_ratio: f64,
pub confidence_score: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct KeyDecision {
pub turn_number: usize,
pub decision_type: DecisionType,
pub description: String,
pub rationale: String,
pub outcome: Option<String>,
pub importance_score: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum DecisionType {
ToolExecution,
ResponseGeneration,
ContextCompression,
ErrorRecovery,
WorkflowChange,
}
impl std::fmt::Display for DecisionType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let description = match self {
DecisionType::ToolExecution => "Tool Execution",
DecisionType::ResponseGeneration => "Response Generation",
DecisionType::ContextCompression => "Context Compression",
DecisionType::ErrorRecovery => "Error Recovery",
DecisionType::WorkflowChange => "Workflow Change",
};
write!(f, "{}", description)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TaskSummary {
pub task_type: String,
pub description: String,
pub success: bool,
pub duration_seconds: Option<u64>,
pub tools_used: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ErrorPattern {
pub error_type: String,
pub frequency: usize,
pub description: String,
pub recommended_solution: String,
}
pub struct ConversationSummarizer {
summaries: Vec<ConversationSummary>,
summarization_threshold: usize, max_summary_length: usize,
compression_target_ratio: f64,
}
impl ConversationSummarizer {
pub fn new() -> Self {
Self {
summaries: Vec::new(),
summarization_threshold: 20, max_summary_length: 2000, compression_target_ratio: 0.3, }
}
pub fn should_summarize(
&self,
conversation_length: usize,
context_size: usize,
context_limit: usize,
) -> bool {
let approaching_limit = context_size > (context_limit * 80 / 100); let long_conversation = conversation_length >= self.summarization_threshold;
let has_errors = context_size > (context_limit * 60 / 100);
approaching_limit || long_conversation || has_errors
}
pub fn generate_summary(
&mut self,
conversation_turns: &[ConversationTurn],
decision_history: &[DecisionInfo],
error_history: &[ErrorInfo],
session_start_time: u64,
) -> Result<ConversationSummary, SummarizationError> {
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs();
let session_duration = now - session_start_time;
let total_turns = conversation_turns.len();
let key_decisions = self.extract_key_decisions(decision_history, conversation_turns);
let completed_tasks = self.extract_completed_tasks(conversation_turns);
let error_patterns = self.analyze_error_patterns(error_history);
let context_recommendations = self.generate_context_recommendations(
conversation_turns.len(),
error_history.len(),
session_duration,
);
let summary_text = self.generate_summary_text(
&key_decisions,
&completed_tasks,
&error_patterns,
session_duration,
total_turns,
);
let original_length = self.calculate_conversation_length(conversation_turns);
let compression_ratio = if original_length > 0 {
summary_text.len() as f64 / original_length as f64
} else {
1.0
};
let confidence_score = self.calculate_confidence_score(
key_decisions.len(),
completed_tasks.len(),
error_patterns.len(),
compression_ratio,
);
let summary_id = format!("summary_{}", now);
let summary = ConversationSummary {
id: summary_id,
timestamp: now,
session_duration_seconds: session_duration,
total_turns,
key_decisions,
completed_tasks,
error_patterns,
context_recommendations,
summary_text,
compression_ratio,
confidence_score,
};
self.summaries.push(summary.clone());
Ok(summary)
}
fn extract_key_decisions(
&self,
decision_history: &[DecisionInfo],
conversation_turns: &[ConversationTurn],
) -> Vec<KeyDecision> {
let mut key_decisions = Vec::new();
for (_i, decision) in decision_history.iter().enumerate() {
let decision_type = match decision.action_type.as_str() {
"tool_call" => DecisionType::ToolExecution,
"response" => DecisionType::ResponseGeneration,
"context_compression" => DecisionType::ContextCompression,
"error_recovery" => DecisionType::ErrorRecovery,
_ => DecisionType::WorkflowChange,
};
let importance_score = self.calculate_decision_importance(decision, conversation_turns);
if importance_score > 0.6 {
key_decisions.push(KeyDecision {
turn_number: decision.turn_number,
decision_type,
description: decision.description.clone(),
rationale: decision.reasoning.clone(),
outcome: decision.outcome.clone(),
importance_score,
});
}
}
key_decisions.sort_by(|a, b| b.importance_score.partial_cmp(&a.importance_score).unwrap());
key_decisions.truncate(10);
key_decisions
}
fn extract_completed_tasks(&self, conversation_turns: &[ConversationTurn]) -> Vec<TaskSummary> {
let mut tasks = Vec::new();
for turn in conversation_turns {
if let Some(task_info) = &turn.task_info {
if task_info.completed {
tasks.push(TaskSummary {
task_type: task_info.task_type.clone(),
description: task_info.description.clone(),
success: task_info.success,
duration_seconds: task_info.duration_seconds,
tools_used: task_info.tools_used.clone(),
});
}
}
}
tasks
}
fn analyze_error_patterns(&self, error_history: &[ErrorInfo]) -> Vec<ErrorPattern> {
let mut error_counts = HashMap::new();
for error in error_history {
*error_counts.entry(error.error_type.clone()).or_insert(0) += 1;
}
let mut patterns = Vec::new();
for (error_type, frequency) in error_counts {
if frequency >= 2 {
let description = format!("{} error occurred {} times", error_type, frequency);
let recommended_solution = self.get_error_solution(&error_type);
patterns.push(ErrorPattern {
error_type,
frequency,
description,
recommended_solution,
});
}
}
patterns.sort_by(|a, b| b.frequency.cmp(&a.frequency));
patterns
}
fn generate_context_recommendations(
&self,
turn_count: usize,
error_count: usize,
session_duration: u64,
) -> Vec<String> {
let mut recommendations = Vec::new();
if turn_count > 50 {
recommendations.push(
"Consider summarizing the conversation to maintain context efficiency".to_string(),
);
}
if error_count > 5 {
recommendations.push(
"High error rate detected - review error patterns and consider context compression"
.to_string(),
);
}
if session_duration > 1800 {
recommendations.push(
"Long-running session detected - consider breaking into smaller tasks".to_string(),
);
}
if recommendations.is_empty() {
recommendations.push("Conversation is proceeding normally".to_string());
}
recommendations
}
fn generate_summary_text(
&self,
key_decisions: &[KeyDecision],
completed_tasks: &[TaskSummary],
error_patterns: &[ErrorPattern],
session_duration: u64,
total_turns: usize,
) -> String {
let mut summary = format!(
"Conversation Summary ({} turns, {} seconds):\n\n",
total_turns, session_duration
);
if !key_decisions.is_empty() {
summary.push_str("Key Decisions Made:\n");
for decision in key_decisions.iter().take(5) {
summary.push_str(&format!(
"• Turn {}: {} - {}\n",
decision.turn_number, decision.decision_type, decision.description
));
}
summary.push('\n');
}
if !completed_tasks.is_empty() {
summary.push_str("Completed Tasks:\n");
for task in completed_tasks {
let status = if task.success {
"[Success]"
} else {
"[Failure]"
};
summary.push_str(&format!(
"{} {} ({})\n",
status, task.description, task.task_type
));
}
summary.push('\n');
}
if !error_patterns.is_empty() {
summary.push_str("Error Patterns:\n");
for pattern in error_patterns {
summary.push_str(&format!(
"• {}: {} ({} occurrences)\n",
pattern.error_type, pattern.description, pattern.frequency
));
}
summary.push('\n');
}
if summary.len() > self.max_summary_length {
summary.truncate(self.max_summary_length - 3);
summary.push_str("...");
}
summary
}
fn calculate_decision_importance(
&self,
decision: &DecisionInfo,
conversation_turns: &[ConversationTurn],
) -> f64 {
let mut score = 0.5;
match decision.action_type.as_str() {
"tool_call" => score += 0.3,
"context_compression" => score += 0.4,
"error_recovery" => score += 0.2,
_ => {}
}
if let Some(outcome) = &decision.outcome {
if outcome.contains("success") || outcome.contains("completed") {
score += 0.2;
}
}
let progress_ratio = decision.turn_number as f64 / conversation_turns.len() as f64;
score += progress_ratio * 0.1;
score.min(1.0)
}
fn calculate_conversation_length(&self, conversation_turns: &[ConversationTurn]) -> usize {
conversation_turns
.iter()
.map(|turn| turn.content.len())
.sum()
}
fn calculate_confidence_score(
&self,
decision_count: usize,
task_count: usize,
error_count: usize,
compression_ratio: f64,
) -> f64 {
let mut confidence = 0.7;
confidence += decision_count.min(10) as f64 * 0.02;
confidence += task_count.min(10) as f64 * 0.03;
confidence -= error_count.min(10) as f64 * 0.05;
let ratio_distance = (compression_ratio - self.compression_target_ratio).abs();
confidence -= ratio_distance * 0.5;
confidence.max(0.1).min(1.0)
}
fn get_error_solution(&self, error_type: &str) -> String {
match error_type {
"tool_execution" => "Review tool parameters and ensure correct file paths".to_string(),
"api_call" => "Check API key and consider implementing retry logic".to_string(),
"context_compression" => {
"Monitor context size and implement proactive compression".to_string()
}
_ => "Investigate error details and consider context preservation".to_string(),
}
}
pub fn get_summaries(&self) -> &[ConversationSummary] {
&self.summaries
}
pub fn get_latest_summary(&self) -> Option<&ConversationSummary> {
self.summaries.last()
}
}
#[derive(Debug, Clone)]
pub struct ConversationTurn {
pub turn_number: usize,
pub content: String,
pub role: String,
pub task_info: Option<TaskInfo>,
}
#[derive(Debug, Clone)]
pub struct TaskInfo {
pub task_type: String,
pub description: String,
pub completed: bool,
pub success: bool,
pub duration_seconds: Option<u64>,
pub tools_used: Vec<String>,
}
#[derive(Debug, Clone)]
pub struct DecisionInfo {
pub turn_number: usize,
pub action_type: String,
pub description: String,
pub reasoning: String,
pub outcome: Option<String>,
}
#[derive(Debug, Clone)]
pub struct ErrorInfo {
pub error_type: String,
pub message: String,
pub turn_number: usize,
pub recoverable: bool,
}
#[derive(Debug, Clone)]
pub enum SummarizationError {
InsufficientData,
ProcessingError(String),
}
impl std::fmt::Display for SummarizationError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
SummarizationError::InsufficientData => {
write!(f, "Insufficient data for summarization")
}
SummarizationError::ProcessingError(msg) => write!(f, "Processing error: {}", msg),
}
}
}
impl std::error::Error for SummarizationError {}
impl Default for ConversationSummarizer {
fn default() -> Self {
Self::new()
}
}