agentic_evolve_core/query/
intent.rs1use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
9pub enum ExtractionIntent {
10 Exists,
12 #[default]
14 IdsOnly,
15 Summary,
17 Fields,
19 Full,
21}
22
23impl ExtractionIntent {
24 pub fn estimated_tokens(&self) -> u64 {
33 match self {
34 Self::Exists => 1,
35 Self::IdsOnly => 5,
36 Self::Summary => 20,
37 Self::Fields => 50,
38 Self::Full => 100,
39 }
40 }
41
42 pub fn is_full(&self) -> bool {
44 matches!(self, Self::Full)
45 }
46
47 pub fn is_minimal(&self) -> bool {
49 matches!(self, Self::Exists | Self::IdsOnly)
50 }
51}
52
53pub trait Scopeable {
55 type Id: Clone + Serialize + for<'de> Deserialize<'de>;
57
58 fn id(&self) -> Self::Id;
60
61 fn summary(&self) -> String;
63
64 fn full_token_estimate(&self) -> u64;
66}
67
68pub fn apply_intent<T: Scopeable + Clone + Serialize>(
70 items: &[T],
71 intent: ExtractionIntent,
72) -> ScopedResult<T> {
73 match intent {
74 ExtractionIntent::Exists => ScopedResult::Exists(!items.is_empty()),
75 ExtractionIntent::IdsOnly => ScopedResult::Ids(items.iter().map(|i| i.id()).collect()),
76 ExtractionIntent::Summary => {
77 ScopedResult::Summaries(items.iter().map(|i| i.summary()).collect())
78 }
79 ExtractionIntent::Fields | ExtractionIntent::Full => ScopedResult::Full(items.to_vec()),
80 }
81}
82
83#[derive(Debug, Clone, Serialize, Deserialize)]
85#[serde(tag = "type", content = "data")]
86pub enum ScopedResult<T: Scopeable> {
87 Exists(bool),
89 Ids(Vec<T::Id>),
91 Summaries(Vec<String>),
93 Full(Vec<T>),
95}
96
97impl<T: Scopeable> ScopedResult<T> {
98 pub fn estimated_tokens(&self) -> u64 {
100 match self {
101 Self::Exists(_) => 1,
102 Self::Ids(ids) => ids.len() as u64 * 5,
103 Self::Summaries(s) => s.len() as u64 * 20,
104 Self::Full(items) => items.iter().map(|i| i.full_token_estimate()).sum(),
105 }
106 }
107
108 pub fn count(&self) -> usize {
110 match self {
111 Self::Exists(_) => 1,
112 Self::Ids(ids) => ids.len(),
113 Self::Summaries(s) => s.len(),
114 Self::Full(items) => items.len(),
115 }
116 }
117}
118
119#[cfg(test)]
120mod tests {
121 use super::*;
122
123 #[derive(Debug, Clone, Serialize, Deserialize)]
124 struct TestItem {
125 id: String,
126 name: String,
127 data: String,
128 }
129
130 impl Scopeable for TestItem {
131 type Id = String;
132
133 fn id(&self) -> String {
134 self.id.clone()
135 }
136
137 fn summary(&self) -> String {
138 format!("{}: {}", self.id, self.name)
139 }
140
141 fn full_token_estimate(&self) -> u64 {
142 100
143 }
144 }
145
146 fn sample_items() -> Vec<TestItem> {
147 vec![
148 TestItem {
149 id: "1".into(),
150 name: "alpha".into(),
151 data: "x".repeat(100),
152 },
153 TestItem {
154 id: "2".into(),
155 name: "beta".into(),
156 data: "y".repeat(100),
157 },
158 ]
159 }
160
161 #[test]
162 fn default_intent_is_ids_only() {
163 assert_eq!(ExtractionIntent::default(), ExtractionIntent::IdsOnly);
164 }
165
166 #[test]
167 fn estimated_tokens_ordering() {
168 assert!(
169 ExtractionIntent::Exists.estimated_tokens()
170 < ExtractionIntent::IdsOnly.estimated_tokens()
171 );
172 assert!(
173 ExtractionIntent::IdsOnly.estimated_tokens()
174 < ExtractionIntent::Summary.estimated_tokens()
175 );
176 assert!(
177 ExtractionIntent::Summary.estimated_tokens()
178 < ExtractionIntent::Fields.estimated_tokens()
179 );
180 assert!(
181 ExtractionIntent::Fields.estimated_tokens() < ExtractionIntent::Full.estimated_tokens()
182 );
183 }
184
185 #[test]
186 fn is_full_only_for_full() {
187 assert!(!ExtractionIntent::IdsOnly.is_full());
188 assert!(ExtractionIntent::Full.is_full());
189 }
190
191 #[test]
192 fn is_minimal_for_exists_and_ids() {
193 assert!(ExtractionIntent::Exists.is_minimal());
194 assert!(ExtractionIntent::IdsOnly.is_minimal());
195 assert!(!ExtractionIntent::Summary.is_minimal());
196 assert!(!ExtractionIntent::Full.is_minimal());
197 }
198
199 #[test]
200 fn apply_intent_exists() {
201 let items = sample_items();
202 let result = apply_intent(&items, ExtractionIntent::Exists);
203 match result {
204 ScopedResult::Exists(b) => assert!(b),
205 _ => panic!("Expected Exists variant"),
206 }
207 }
208
209 #[test]
210 fn apply_intent_ids_only() {
211 let items = sample_items();
212 let result = apply_intent(&items, ExtractionIntent::IdsOnly);
213 match result {
214 ScopedResult::Ids(ids) => {
215 assert_eq!(ids.len(), 2);
216 assert_eq!(ids[0], "1");
217 }
218 _ => panic!("Expected Ids variant"),
219 }
220 }
221
222 #[test]
223 fn apply_intent_full() {
224 let items = sample_items();
225 let result = apply_intent(&items, ExtractionIntent::Full);
226 assert_eq!(result.estimated_tokens(), 200);
227 }
228
229 #[test]
230 fn scoped_result_count() {
231 let items = sample_items();
232 let result = apply_intent(&items, ExtractionIntent::IdsOnly);
233 assert_eq!(result.count(), 2);
234 }
235
236 #[test]
237 fn ids_cheaper_than_full() {
238 let items = sample_items();
239 let ids_result = apply_intent(&items, ExtractionIntent::IdsOnly);
240 let full_result = apply_intent(&items, ExtractionIntent::Full);
241 assert!(ids_result.estimated_tokens() < full_result.estimated_tokens());
242 }
243}