1use do_memory_core::memory::{PatternSearchResult, SearchConfig};
4use do_memory_core::{SelfLearningMemory, TaskContext};
5use serde::{Deserialize, Serialize};
6use serde_json::Value;
7
8#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct SearchPatternsInput {
11 pub query: String,
13 pub domain: String,
15 #[serde(default)]
17 pub tags: Vec<String>,
18 #[serde(default = "default_limit")]
20 pub limit: usize,
21 #[serde(default = "default_min_relevance")]
23 pub min_relevance: f32,
24 #[serde(default)]
26 pub filter_by_domain: bool,
27}
28
29fn default_limit() -> usize {
30 5
31}
32
33fn default_min_relevance() -> f32 {
34 0.3
35}
36
37#[derive(Debug, Clone, Serialize, Deserialize)]
39pub struct SearchPatternsOutput {
40 pub results: Vec<PatternResult>,
42 pub total_searched: usize,
44 pub query: String,
46}
47
48#[derive(Debug, Clone, Serialize, Deserialize)]
50pub struct PatternResult {
51 pub id: String,
53 pub pattern_type: String,
55 pub description: String,
57 pub relevance_score: f32,
59 pub score_breakdown: ScoreBreakdownResult,
61 pub success_rate: f32,
63 pub domain: Option<String>,
65 pub times_applied: usize,
67}
68
69#[derive(Debug, Clone, Serialize, Deserialize)]
71pub struct ScoreBreakdownResult {
72 pub semantic_similarity: f32,
73 pub context_match: f32,
74 pub effectiveness: f32,
75 pub recency: f32,
76 pub success_rate: f32,
77}
78
79pub async fn execute(
81 memory: &SelfLearningMemory,
82 input: SearchPatternsInput,
83) -> anyhow::Result<Value> {
84 let context = TaskContext {
86 domain: input.domain.clone(),
87 language: None,
88 framework: None,
89 complexity: do_memory_core::ComplexityLevel::Moderate,
90 tags: input.tags.clone(),
91 };
92
93 let config = SearchConfig {
95 min_relevance: input.min_relevance,
96 filter_by_domain: input.filter_by_domain,
97 ..SearchConfig::default()
98 };
99
100 let results = memory
102 .search_patterns_with_config(&input.query, context, config, input.limit)
103 .await?;
104
105 let pattern_results: Vec<PatternResult> = results
107 .iter()
108 .map(pattern_search_result_to_pattern_result)
109 .collect();
110
111 let output = SearchPatternsOutput {
112 results: pattern_results,
113 total_searched: results.len(),
114 query: input.query.clone(),
115 };
116
117 Ok(serde_json::to_value(output)?)
118}
119
120fn pattern_search_result_to_pattern_result(result: &PatternSearchResult) -> PatternResult {
122 use do_memory_core::Pattern;
123
124 let pattern = &result.pattern;
125 let (id, pattern_type, description, domain) = match pattern {
126 Pattern::ToolSequence {
127 id, tools, context, ..
128 } => (
129 id.to_string(),
130 "tool_sequence".to_string(),
131 format!("Tool sequence: {}", tools.join(" → ")),
132 Some(context.domain.clone()),
133 ),
134 Pattern::DecisionPoint {
135 id,
136 condition,
137 action,
138 context,
139 ..
140 } => (
141 id.to_string(),
142 "decision_point".to_string(),
143 format!("Decision: {} → {}", condition, action),
144 Some(context.domain.clone()),
145 ),
146 Pattern::ErrorRecovery {
147 id,
148 error_type,
149 recovery_steps,
150 context,
151 ..
152 } => (
153 id.to_string(),
154 "error_recovery".to_string(),
155 format!(
156 "Error recovery for {}: {}",
157 error_type,
158 recovery_steps.join(", ")
159 ),
160 Some(context.domain.clone()),
161 ),
162 Pattern::ContextPattern {
163 id,
164 recommended_approach,
165 ..
166 } => (
167 id.to_string(),
168 "context_pattern".to_string(),
169 format!("Context pattern: {}", recommended_approach),
170 None,
171 ),
172 };
173
174 let effectiveness = pattern.effectiveness();
175
176 PatternResult {
177 id,
178 pattern_type,
179 description,
180 relevance_score: result.relevance_score,
181 score_breakdown: ScoreBreakdownResult {
182 semantic_similarity: result.score_breakdown.semantic_similarity,
183 context_match: result.score_breakdown.context_match,
184 effectiveness: result.score_breakdown.effectiveness,
185 recency: result.score_breakdown.recency,
186 success_rate: result.score_breakdown.success_rate,
187 },
188 success_rate: pattern.success_rate(),
189 domain,
190 times_applied: effectiveness.times_applied,
191 }
192}
193
194#[derive(Debug, Clone, Serialize, Deserialize)]
196pub struct RecommendPatternsInput {
197 pub task_description: String,
199 pub domain: String,
201 #[serde(default)]
203 pub tags: Vec<String>,
204 #[serde(default = "default_recommendation_limit")]
206 pub limit: usize,
207}
208
209fn default_recommendation_limit() -> usize {
210 3
211}
212
213pub async fn execute_recommend(
215 memory: &SelfLearningMemory,
216 input: RecommendPatternsInput,
217) -> anyhow::Result<Value> {
218 let context = TaskContext {
220 domain: input.domain.clone(),
221 language: None,
222 framework: None,
223 complexity: do_memory_core::ComplexityLevel::Moderate,
224 tags: input.tags.clone(),
225 };
226
227 let results = memory
229 .recommend_patterns_for_task(&input.task_description, context, input.limit)
230 .await?;
231
232 let pattern_results: Vec<PatternResult> = results
234 .iter()
235 .map(pattern_search_result_to_pattern_result)
236 .collect();
237
238 let output = SearchPatternsOutput {
239 results: pattern_results,
240 total_searched: results.len(),
241 query: input.task_description.clone(),
242 };
243
244 Ok(serde_json::to_value(output)?)
245}
246
247#[cfg(test)]
248mod tests {
249 use super::*;
250
251 #[test]
252 fn test_search_patterns_input_defaults() {
253 let input = SearchPatternsInput {
254 query: "test".to_string(),
255 domain: "test".to_string(),
256 tags: vec![],
257 limit: default_limit(),
258 min_relevance: default_min_relevance(),
259 filter_by_domain: false,
260 };
261
262 assert_eq!(input.limit, 5);
263 assert_eq!(input.min_relevance, 0.3);
264 }
265
266 #[test]
267 fn test_recommend_patterns_input_defaults() {
268 let input = RecommendPatternsInput {
269 task_description: "test".to_string(),
270 domain: "test".to_string(),
271 tags: vec![],
272 limit: default_recommendation_limit(),
273 };
274
275 assert_eq!(input.limit, 3);
276 }
277}