oxirs_chat/rag/
query_processing.rs

1//! Query constraint processing and analysis utilities
2//!
3//! Provides query analysis, constraint extraction, and processing utilities for RAG queries.
4
5use super::*;
6
7/// Query processor for analyzing and extracting constraints from queries
8pub struct QueryProcessor;
9
10impl Default for QueryProcessor {
11    fn default() -> Self {
12        Self::new()
13    }
14}
15
16impl QueryProcessor {
17    pub fn new() -> Self {
18        Self
19    }
20
21    /// Extract constraints from query based on entities
22    pub async fn extract_constraints(
23        &self,
24        query: &str,
25        entities: &[ExtractedEntity],
26    ) -> Result<Vec<QueryConstraint>> {
27        let mut constraints = Vec::new();
28        let query_lower = query.to_lowercase();
29
30        // Temporal constraints
31        let temporal_patterns = [
32            (
33                r"(?:in|during|from|since|before|after)\s+(\d{4})",
34                ConstraintType::Temporal,
35                "year",
36            ),
37            (
38                r"(?:today|yesterday|tomorrow|now|recent)",
39                ConstraintType::Temporal,
40                "relative_time",
41            ),
42        ];
43
44        for (pattern, constraint_type, operator) in temporal_patterns {
45            let regex = Regex::new(pattern)?;
46            for cap in regex.captures_iter(&query_lower) {
47                if let Some(value) = cap.get(1) {
48                    constraints.push(QueryConstraint {
49                        constraint_type,
50                        value: value.as_str().to_string(),
51                        operator: operator.to_string(),
52                    });
53                } else if cap.get(0).is_some() {
54                    constraints.push(QueryConstraint {
55                        constraint_type,
56                        value: cap.get(0).unwrap().as_str().to_string(),
57                        operator: operator.to_string(),
58                    });
59                }
60            }
61        }
62
63        // Type constraints
64        if query_lower.contains("type")
65            || query_lower.contains("kind")
66            || query_lower.contains("class")
67        {
68            constraints.push(QueryConstraint {
69                constraint_type: ConstraintType::Type,
70                value: "type_constraint".to_string(),
71                operator: "equals".to_string(),
72            });
73        }
74
75        // Value constraints (numeric, comparison)
76        let value_patterns = [
77            (r"(?:greater than|more than|>\s*)(\d+)", "greater_than"),
78            (r"(?:less than|fewer than|<\s*)(\d+)", "less_than"),
79            (r"(?:equals?|is|=\s*)(\d+)", "equals"),
80        ];
81
82        for (pattern, operator) in value_patterns {
83            let regex = Regex::new(pattern)?;
84            for cap in regex.captures_iter(&query_lower) {
85                if let Some(value) = cap.get(1) {
86                    constraints.push(QueryConstraint {
87                        constraint_type: ConstraintType::Value,
88                        value: value.as_str().to_string(),
89                        operator: operator.to_string(),
90                    });
91                }
92            }
93        }
94
95        // Entity-based constraints
96        for entity in entities {
97            match entity.entity_type {
98                EntityType::Person => {
99                    constraints.push(QueryConstraint {
100                        constraint_type: ConstraintType::Entity,
101                        value: entity.text.clone(),
102                        operator: "person_filter".to_string(),
103                    });
104                }
105                EntityType::Location => {
106                    constraints.push(QueryConstraint {
107                        constraint_type: ConstraintType::Spatial,
108                        value: entity.text.clone(),
109                        operator: "location_filter".to_string(),
110                    });
111                }
112                _ => {}
113            }
114        }
115
116        debug!("Extracted {} constraints from query", constraints.len());
117        Ok(constraints)
118    }
119
120    /// Analyze query intent and complexity
121    pub fn analyze_query_intent(&self, query: &str) -> QueryIntent {
122        let query_lower = query.to_lowercase();
123
124        if query_lower.contains("how many") || query_lower.contains("count") {
125            QueryIntent::Counting
126        } else if query_lower.contains("what is") || query_lower.contains("define") {
127            QueryIntent::Definition
128        } else if query_lower.contains("compare") || query_lower.contains("difference") {
129            QueryIntent::Comparison
130        } else if query_lower.contains("list") || query_lower.contains("show all") {
131            QueryIntent::Listing
132        } else if query_lower.contains("why") || query_lower.contains("because") {
133            QueryIntent::Explanation
134        } else {
135            QueryIntent::General
136        }
137    }
138
139    /// Calculate query complexity score
140    pub fn calculate_query_complexity(&self, query: &str) -> f64 {
141        let word_count = query.split_whitespace().count();
142        let unique_words = query.split_whitespace().collect::<HashSet<_>>().len();
143        let question_words = ["what", "how", "why", "when", "where", "who", "which"];
144        let query_lower = query.to_lowercase();
145
146        let question_word_count = question_words
147            .iter()
148            .filter(|word| query_lower.contains(*word))
149            .count();
150
151        let complexity = (word_count as f64 * 0.05)
152            + (unique_words as f64 * 0.1)
153            + (question_word_count as f64 * 0.2);
154
155        complexity.min(1.0)
156    }
157}
158
159/// Query constraint for filtering and processing
160#[derive(Debug, Clone)]
161pub struct QueryConstraint {
162    pub constraint_type: ConstraintType,
163    pub value: String,
164    pub operator: String,
165}
166
167/// Types of constraints that can be extracted from queries
168#[derive(Debug, Clone, Copy)]
169pub enum ConstraintType {
170    Temporal,
171    Spatial,
172    Type,
173    Value,
174    Entity,
175    Relationship,
176}
177
178/// Query intent classification
179#[derive(Debug, Clone, PartialEq, Hash, serde::Serialize, serde::Deserialize)]
180pub enum QueryIntent {
181    Definition,
182    Comparison,
183    Counting,
184    Listing,
185    Explanation,
186    General,
187}
188
189use super::graph_traversal::{EntityType, ExtractedEntity};