agentic_evolve_core/matching/
semantic.rs1use crate::types::error::EvolveResult;
4use crate::types::match_result::{MatchContext, MatchResult, MatchScore};
5use crate::types::pattern::{FunctionSignature, Pattern};
6
7#[derive(Debug, Default)]
9pub struct SemanticMatcher;
10
11impl SemanticMatcher {
12 pub fn new() -> Self {
13 Self
14 }
15
16 pub fn find_matches(
17 &self,
18 signature: &FunctionSignature,
19 patterns: &[&Pattern],
20 _context: &MatchContext,
21 limit: usize,
22 ) -> EvolveResult<Vec<MatchResult>> {
23 let sig_tokens = tokenize_name(&signature.name);
24
25 let mut results: Vec<MatchResult> = patterns
26 .iter()
27 .map(|p| {
28 let score = self.score_semantic(p, &sig_tokens);
29 MatchResult {
30 pattern_id: p.id.clone(),
31 pattern: (*p).clone(),
32 score: MatchScore::from_single(score),
33 suggested_bindings: std::collections::HashMap::new(),
34 }
35 })
36 .filter(|r| r.score.combined > 0.0)
37 .collect();
38
39 results.sort_by(|a, b| {
40 b.score
41 .combined
42 .partial_cmp(&a.score.combined)
43 .unwrap_or(std::cmp::Ordering::Equal)
44 });
45 results.truncate(limit);
46 Ok(results)
47 }
48
49 fn score_semantic(&self, pattern: &Pattern, query_tokens: &[String]) -> f64 {
50 let pattern_tokens = tokenize_name(&pattern.name);
51 if query_tokens.is_empty() || pattern_tokens.is_empty() {
52 return 0.0;
53 }
54 let matches = query_tokens
55 .iter()
56 .filter(|qt| {
57 pattern_tokens
58 .iter()
59 .any(|pt| pt == *qt || is_semantic_match(pt, qt))
60 })
61 .count();
62 let max_len = query_tokens.len().max(pattern_tokens.len());
63 matches as f64 / max_len as f64
64 }
65}
66
67fn tokenize_name(name: &str) -> Vec<String> {
68 let mut tokens = Vec::new();
69 let mut current = String::new();
70
71 for ch in name.chars() {
72 if ch == '_' || ch == '-' || ch == '.' || ch == ' ' {
73 if !current.is_empty() {
74 tokens.push(current.to_lowercase());
75 current.clear();
76 }
77 } else if ch.is_uppercase() && !current.is_empty() {
78 tokens.push(current.to_lowercase());
79 current.clear();
80 current.push(ch);
81 } else {
82 current.push(ch);
83 }
84 }
85 if !current.is_empty() {
86 tokens.push(current.to_lowercase());
87 }
88 tokens
89}
90
91fn is_semantic_match(a: &str, b: &str) -> bool {
92 const SYNONYMS: &[&[&str]] = &[
93 &["get", "fetch", "retrieve", "read", "load", "find", "query"],
94 &["set", "put", "write", "store", "save", "update"],
95 &["delete", "remove", "drop", "destroy", "clear"],
96 &["create", "new", "build", "make", "init", "construct"],
97 &["list", "all", "enumerate", "iter"],
98 &["check", "validate", "verify", "test", "is", "has"],
99 &["parse", "decode", "deserialize", "from"],
100 &["format", "encode", "serialize", "to"],
101 &["send", "emit", "dispatch", "publish", "notify"],
102 &["receive", "handle", "process", "consume", "subscribe"],
103 ];
104
105 for group in SYNONYMS {
106 if group.contains(&a) && group.contains(&b) {
107 return true;
108 }
109 }
110 false
111}