use do_memory_core::memory::{PatternSearchResult, SearchConfig};
use do_memory_core::{SelfLearningMemory, TaskContext};
use serde::{Deserialize, Serialize};
use serde_json::Value;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SearchPatternsInput {
pub query: String,
pub domain: String,
#[serde(default)]
pub tags: Vec<String>,
#[serde(default = "default_limit")]
pub limit: usize,
#[serde(default = "default_min_relevance")]
pub min_relevance: f32,
#[serde(default)]
pub filter_by_domain: bool,
}
fn default_limit() -> usize {
5
}
fn default_min_relevance() -> f32 {
0.3
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SearchPatternsOutput {
pub results: Vec<PatternResult>,
pub total_searched: usize,
pub query: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PatternResult {
pub id: String,
pub pattern_type: String,
pub description: String,
pub relevance_score: f32,
pub score_breakdown: ScoreBreakdownResult,
pub success_rate: f32,
pub domain: Option<String>,
pub times_applied: usize,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ScoreBreakdownResult {
pub semantic_similarity: f32,
pub context_match: f32,
pub effectiveness: f32,
pub recency: f32,
pub success_rate: f32,
}
pub async fn execute(
memory: &SelfLearningMemory,
input: SearchPatternsInput,
) -> anyhow::Result<Value> {
let context = TaskContext {
domain: input.domain.clone(),
language: None,
framework: None,
complexity: do_memory_core::ComplexityLevel::Moderate,
tags: input.tags.clone(),
};
let config = SearchConfig {
min_relevance: input.min_relevance,
filter_by_domain: input.filter_by_domain,
..SearchConfig::default()
};
let results = memory
.search_patterns_with_config(&input.query, context, config, input.limit)
.await?;
let pattern_results: Vec<PatternResult> = results
.iter()
.map(pattern_search_result_to_pattern_result)
.collect();
let output = SearchPatternsOutput {
results: pattern_results,
total_searched: results.len(),
query: input.query.clone(),
};
Ok(serde_json::to_value(output)?)
}
fn pattern_search_result_to_pattern_result(result: &PatternSearchResult) -> PatternResult {
use do_memory_core::Pattern;
let pattern = &result.pattern;
let (id, pattern_type, description, domain) = match pattern {
Pattern::ToolSequence {
id, tools, context, ..
} => (
id.to_string(),
"tool_sequence".to_string(),
format!("Tool sequence: {}", tools.join(" → ")),
Some(context.domain.clone()),
),
Pattern::DecisionPoint {
id,
condition,
action,
context,
..
} => (
id.to_string(),
"decision_point".to_string(),
format!("Decision: {} → {}", condition, action),
Some(context.domain.clone()),
),
Pattern::ErrorRecovery {
id,
error_type,
recovery_steps,
context,
..
} => (
id.to_string(),
"error_recovery".to_string(),
format!(
"Error recovery for {}: {}",
error_type,
recovery_steps.join(", ")
),
Some(context.domain.clone()),
),
Pattern::ContextPattern {
id,
recommended_approach,
..
} => (
id.to_string(),
"context_pattern".to_string(),
format!("Context pattern: {}", recommended_approach),
None,
),
};
let effectiveness = pattern.effectiveness();
PatternResult {
id,
pattern_type,
description,
relevance_score: result.relevance_score,
score_breakdown: ScoreBreakdownResult {
semantic_similarity: result.score_breakdown.semantic_similarity,
context_match: result.score_breakdown.context_match,
effectiveness: result.score_breakdown.effectiveness,
recency: result.score_breakdown.recency,
success_rate: result.score_breakdown.success_rate,
},
success_rate: pattern.success_rate(),
domain,
times_applied: effectiveness.times_applied,
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RecommendPatternsInput {
pub task_description: String,
pub domain: String,
#[serde(default)]
pub tags: Vec<String>,
#[serde(default = "default_recommendation_limit")]
pub limit: usize,
}
fn default_recommendation_limit() -> usize {
3
}
pub async fn execute_recommend(
memory: &SelfLearningMemory,
input: RecommendPatternsInput,
) -> anyhow::Result<Value> {
let context = TaskContext {
domain: input.domain.clone(),
language: None,
framework: None,
complexity: do_memory_core::ComplexityLevel::Moderate,
tags: input.tags.clone(),
};
let results = memory
.recommend_patterns_for_task(&input.task_description, context, input.limit)
.await?;
let pattern_results: Vec<PatternResult> = results
.iter()
.map(pattern_search_result_to_pattern_result)
.collect();
let output = SearchPatternsOutput {
results: pattern_results,
total_searched: results.len(),
query: input.task_description.clone(),
};
Ok(serde_json::to_value(output)?)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_search_patterns_input_defaults() {
let input = SearchPatternsInput {
query: "test".to_string(),
domain: "test".to_string(),
tags: vec![],
limit: default_limit(),
min_relevance: default_min_relevance(),
filter_by_domain: false,
};
assert_eq!(input.limit, 5);
assert_eq!(input.min_relevance, 0.3);
}
#[test]
fn test_recommend_patterns_input_defaults() {
let input = RecommendPatternsInput {
task_description: "test".to_string(),
domain: "test".to_string(),
tags: vec![],
limit: default_recommendation_limit(),
};
assert_eq!(input.limit, 3);
}
}