use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ThinkingAnalysis {
pub length: usize,
pub word_count: usize,
pub reasoning_steps: usize,
pub questions_count: usize,
pub complexity_score: f64,
pub patterns: Vec<ReasoningPattern>,
pub thinking_time_ms: Option<u64>,
pub insights: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ReasoningPattern {
pub pattern_type: PatternType,
pub count: usize,
pub confidence: f64,
pub example: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum PatternType {
Sequential,
Causal,
Comparative,
Hypothetical,
Analytical,
ProblemSolving,
SelfCorrection,
Uncertainty,
}
pub fn analyze_thinking_content(content: &str) -> ThinkingAnalysis {
let length = content.len();
let word_count = content.split_whitespace().count();
let patterns = identify_reasoning_patterns(content);
let reasoning_steps = count_reasoning_steps(content);
let questions_count = content.matches('?').count();
let complexity_score = calculate_complexity_score(content, &patterns);
let insights = extract_insights(content);
ThinkingAnalysis {
length,
word_count,
reasoning_steps,
questions_count,
complexity_score,
patterns,
thinking_time_ms: None, insights,
}
}
fn identify_reasoning_patterns(content: &str) -> Vec<ReasoningPattern> {
let mut patterns = Vec::new();
let content_lower = content.to_lowercase();
let sequential_indicators = [
"step 1",
"step 2",
"step 3",
"first",
"second",
"third",
"then",
"next",
"finally",
"initially",
"subsequently",
"lastly",
];
let sequential_count = count_pattern_occurrences(&content_lower, &sequential_indicators);
if sequential_count > 0 {
patterns.push(ReasoningPattern {
pattern_type: PatternType::Sequential,
count: sequential_count,
confidence: calculate_pattern_confidence(sequential_count, content.len()),
example: find_pattern_example(content, &sequential_indicators),
});
}
let causal_indicators = [
"because",
"therefore",
"thus",
"hence",
"so",
"as a result",
"consequently",
"due to",
"since",
"given that",
];
let causal_count = count_pattern_occurrences(&content_lower, &causal_indicators);
if causal_count > 0 {
patterns.push(ReasoningPattern {
pattern_type: PatternType::Causal,
count: causal_count,
confidence: calculate_pattern_confidence(causal_count, content.len()),
example: find_pattern_example(content, &causal_indicators),
});
}
let hypothetical_indicators = [
"if",
"suppose",
"assume",
"what if",
"imagine",
"consider",
"let's say",
"hypothetically",
"in case",
];
let hypothetical_count = count_pattern_occurrences(&content_lower, &hypothetical_indicators);
if hypothetical_count > 0 {
patterns.push(ReasoningPattern {
pattern_type: PatternType::Hypothetical,
count: hypothetical_count,
confidence: calculate_pattern_confidence(hypothetical_count, content.len()),
example: find_pattern_example(content, &hypothetical_indicators),
});
}
let correction_indicators = [
"actually",
"wait",
"let me reconsider",
"on second thought",
"correction",
"i mean",
"rather",
"instead",
"my mistake",
];
let correction_count = count_pattern_occurrences(&content_lower, &correction_indicators);
if correction_count > 0 {
patterns.push(ReasoningPattern {
pattern_type: PatternType::SelfCorrection,
count: correction_count,
confidence: calculate_pattern_confidence(correction_count, content.len()),
example: find_pattern_example(content, &correction_indicators),
});
}
let problem_solving_indicators = [
"solution", "approach", "method", "strategy", "way to", "how to", "solve", "resolve",
"address", "tackle",
];
let problem_solving_count =
count_pattern_occurrences(&content_lower, &problem_solving_indicators);
if problem_solving_count > 0 {
patterns.push(ReasoningPattern {
pattern_type: PatternType::ProblemSolving,
count: problem_solving_count,
confidence: calculate_pattern_confidence(problem_solving_count, content.len()),
example: find_pattern_example(content, &problem_solving_indicators),
});
}
patterns
}
fn count_pattern_occurrences(content: &str, indicators: &[&str]) -> usize {
indicators
.iter()
.map(|&indicator| content.matches(indicator).count())
.sum()
}
fn calculate_pattern_confidence(count: usize, content_length: usize) -> f64 {
let frequency = count as f64 / (content_length as f64 / 100.0); (frequency * 10.0).min(1.0) }
fn find_pattern_example(content: &str, indicators: &[&str]) -> Option<String> {
for &indicator in indicators {
if let Some(pos) = content.to_lowercase().find(indicator) {
let start = pos.saturating_sub(20);
let end = (pos + indicator.len() + 30).min(content.len());
return Some(content[start..end].to_string());
}
}
None
}
fn count_reasoning_steps(content: &str) -> usize {
let step_patterns = [
r"step \d+",
r"^\d+\.",
r"first",
r"second",
r"third",
r"then",
r"next",
r"finally",
];
let content_lower = content.to_lowercase();
step_patterns
.iter()
.map(|_pattern| {
content_lower.matches("step").count()
+ content_lower.matches("first").count()
+ content_lower.matches("then").count()
+ content_lower.matches("next").count()
})
.sum::<usize>()
.min(20) }
fn calculate_complexity_score(content: &str, patterns: &[ReasoningPattern]) -> f64 {
let length_factor = (content.len() as f64 / 1000.0).min(1.0); let pattern_factor = (patterns.len() as f64 / 5.0).min(1.0); let question_factor = (content.matches('?').count() as f64 / 10.0).min(1.0);
(length_factor + pattern_factor + question_factor) / 3.0
}
fn extract_insights(content: &str) -> Vec<String> {
let mut insights = Vec::new();
let conclusion_patterns = [
"in conclusion",
"therefore",
"the answer is",
"the solution is",
"this means",
"we can conclude",
"the result is",
];
for pattern in conclusion_patterns.iter() {
if let Some(pos) = content.to_lowercase().find(pattern) {
let start = pos;
let end = content[pos..]
.find('.')
.map(|i| pos + i + 1)
.unwrap_or(content.len());
if end > start {
insights.push(content[start..end].trim().to_string());
}
}
}
insights.truncate(3);
insights
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_basic_analysis() {
let content = "First, I need to understand the problem. Then, I'll consider different approaches. Finally, I'll choose the best solution.";
let analysis = analyze_thinking_content(content);
assert!(analysis.reasoning_steps > 0);
assert!(analysis.complexity_score > 0.0);
assert!(!analysis.patterns.is_empty());
}
#[test]
fn test_pattern_identification() {
let content = "Because of this, therefore we can conclude that the answer is correct.";
let analysis = analyze_thinking_content(content);
let causal_patterns: Vec<_> = analysis
.patterns
.iter()
.filter(|p| matches!(p.pattern_type, PatternType::Causal))
.collect();
assert!(!causal_patterns.is_empty());
}
#[test]
fn test_complexity_scoring() {
let simple_content = "The answer is 42.";
let complex_content = "First, let me analyze this step by step. If we consider the various factors, then we need to evaluate each option. Actually, wait - let me reconsider this approach. What if we try a different method?";
let simple_analysis = analyze_thinking_content(simple_content);
let complex_analysis = analyze_thinking_content(complex_content);
assert!(complex_analysis.complexity_score > simple_analysis.complexity_score);
}
}