use crate::Result;
use crate::embeddings::SemanticService;
use crate::pattern::Pattern;
use crate::types::TaskContext;
use serde::{Deserialize, Serialize};
use std::sync::Arc;
use tracing::debug;
use super::scoring::{SearchConfig, calculate_pattern_score};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PatternSearchResult {
pub pattern: Pattern,
pub relevance_score: f32,
pub score_breakdown: super::scoring::ScoreBreakdown,
}
pub async fn search_patterns_semantic(
query: &str,
patterns: Vec<Pattern>,
context: &TaskContext,
semantic_service: Option<&Arc<SemanticService>>,
config: SearchConfig,
limit: usize,
) -> Result<Vec<PatternSearchResult>> {
debug!(
query = %query,
pattern_count = patterns.len(),
limit = limit,
"Starting semantic pattern search"
);
let query_embedding = if let Some(service) = semantic_service {
match service.provider.embed_text(query).await {
Ok(embedding) => {
debug!(
"Generated query embedding with {} dimensions",
embedding.len()
);
embedding
}
Err(e) => {
debug!(
"Failed to generate query embedding: {}, using keyword-based scoring",
e
);
vec![]
}
}
} else {
debug!("No semantic service available, using keyword-based scoring");
vec![]
};
let mut results = Vec::new();
for pattern in patterns {
if config.filter_by_domain {
if let Some(pattern_ctx) = pattern.context() {
if pattern_ctx.domain != context.domain {
continue;
}
}
}
let score_breakdown = calculate_pattern_score(
&query_embedding,
&pattern,
context,
semantic_service,
&config,
)
.await?;
let relevance_score = super::scoring::combine_scores(&score_breakdown, &config);
if relevance_score >= config.min_relevance {
results.push(PatternSearchResult {
pattern,
relevance_score,
score_breakdown,
});
}
}
results.sort_by(|a, b| {
b.relevance_score
.partial_cmp(&a.relevance_score)
.unwrap_or(std::cmp::Ordering::Equal)
});
results.truncate(limit);
debug!(
results_count = results.len(),
"Semantic pattern search completed"
);
Ok(results)
}
pub async fn recommend_patterns_for_task(
task_description: &str,
context: TaskContext,
patterns: Vec<Pattern>,
semantic_service: Option<&Arc<SemanticService>>,
limit: usize,
) -> Result<Vec<PatternSearchResult>> {
debug!(
task = %task_description,
domain = %context.domain,
"Recommending patterns for task"
);
let config = SearchConfig {
min_relevance: 0.4,
semantic_weight: 0.35,
context_weight: 0.25,
effectiveness_weight: 0.25,
recency_weight: 0.1,
success_weight: 0.05,
filter_by_domain: true,
filter_by_task_type: false, };
search_patterns_semantic(
task_description,
patterns,
&context,
semantic_service,
config,
limit,
)
.await
}
pub async fn discover_analogous_patterns(
source_domain: &str,
target_context: TaskContext,
patterns: Vec<Pattern>,
semantic_service: Option<&Arc<SemanticService>>,
limit: usize,
) -> Result<Vec<PatternSearchResult>> {
debug!(
source_domain = %source_domain,
target_domain = %target_context.domain,
"Discovering analogous patterns"
);
let source_patterns: Vec<Pattern> = patterns
.into_iter()
.filter(|p| p.context().is_some_and(|ctx| ctx.domain == source_domain))
.collect();
debug!(
source_pattern_count = source_patterns.len(),
"Filtered patterns from source domain"
);
let config = SearchConfig {
min_relevance: 0.3,
semantic_weight: 0.5, context_weight: 0.1, effectiveness_weight: 0.2,
recency_weight: 0.1,
success_weight: 0.1,
filter_by_domain: false,
filter_by_task_type: false,
};
let query = format!(
"Apply patterns from {} to {}",
source_domain, target_context.domain
);
search_patterns_semantic(
&query,
source_patterns,
&target_context,
semantic_service,
config,
limit,
)
.await
}
#[cfg(test)]
mod tests {
use super::*;
use crate::types::ComplexityLevel;
use uuid::Uuid;
fn create_test_pattern(domain: &str, success_rate: f32) -> Pattern {
Pattern::ToolSequence {
id: Uuid::new_v4(),
tools: vec!["tool1".to_string(), "tool2".to_string()],
context: TaskContext {
domain: domain.to_string(),
language: Some("rust".to_string()),
framework: None,
complexity: ComplexityLevel::Moderate,
tags: vec!["rust".to_string()],
},
success_rate,
avg_latency: chrono::Duration::milliseconds(100),
occurrence_count: 5,
effectiveness: crate::pattern::PatternEffectiveness::new(),
}
}
#[tokio::test]
async fn test_search_patterns_semantic_no_service() {
let patterns = vec![
create_test_pattern("web-api", 0.9),
create_test_pattern("cli", 0.8),
];
let context = TaskContext {
domain: "web-api".to_string(),
language: None,
framework: None,
complexity: ComplexityLevel::Moderate,
tags: vec![],
};
let results = search_patterns_semantic(
"build REST API",
patterns,
&context,
None,
SearchConfig::default(),
5,
)
.await
.unwrap();
assert!(!results.is_empty());
}
}