systemprompt_models/brain/declarations/
synonyms.rs1use serde::{Deserialize, Serialize};
14
15#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
18#[serde(deny_unknown_fields, default)]
19pub struct SynonymsDoc {
20 pub groups: Vec<Vec<String>>,
21}
22
23#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
25pub enum SynonymsError {
26 #[error("synonym group {0} has fewer than two members")]
27 ShortGroup(usize),
28 #[error("synonym group {0} has an empty member")]
29 EmptyMember(usize),
30}
31
32impl SynonymsDoc {
33 pub fn validate(&self) -> Result<(), SynonymsError> {
34 for (index, group) in self.groups.iter().enumerate() {
35 if group.len() < 2 {
36 return Err(SynonymsError::ShortGroup(index));
37 }
38 if group.iter().any(|member| member.trim().is_empty()) {
39 return Err(SynonymsError::EmptyMember(index));
40 }
41 }
42 Ok(())
43 }
44
45 #[must_use]
46 pub fn expansions(&self, text: &str) -> Vec<String> {
47 let words = words_of(text);
48 let mut out: Vec<String> = Vec::new();
49 for group in &self.groups {
50 let present = group.iter().any(|member| contains_phrase(&words, member));
51 if !present {
52 continue;
53 }
54 for member in group {
55 if !out.iter().any(|seen| seen == member) {
56 out.push(member.clone());
57 }
58 }
59 }
60 out
61 }
62
63 #[must_use]
64 pub fn expansion_text(&self, text: &str) -> String {
65 self.expansions(text).join(" ")
66 }
67}
68
69fn words_of(text: &str) -> Vec<String> {
70 text.split(|c: char| !c.is_alphanumeric())
71 .filter(|word| !word.is_empty())
72 .map(str::to_lowercase)
73 .collect()
74}
75
76fn contains_phrase(words: &[String], phrase: &str) -> bool {
77 let needle = words_of(phrase);
78 if needle.is_empty() || needle.len() > words.len() {
79 return false;
80 }
81 words.windows(needle.len()).any(|window| window == needle)
82}