Skip to main content
Glama
intent_classifier.py19.1 kB
"""Intent Classifier - Classifies user intent from natural language input""" import re from dataclasses import dataclass from enum import Enum from typing import Any, Dict, List, Optional, Tuple class IntentType(Enum): """Types of user intents""" CREATE = "create" MODIFY = "modify" DELETE = "delete" QUERY = "query" MEASURE = "measure" EXPORT = "export" IMPORT = "import" VIEW = "view" ANALYZE = "analyze" HELP = "help" CONFIGURE = "configure" UNKNOWN = "unknown" class ActionType(Enum): """Types of actions within intents""" # Creation actions PRIMITIVE = "primitive" SKETCH = "sketch" FEATURE = "feature" ASSEMBLY = "assembly" # Modification actions TRANSFORM = "transform" BOOLEAN = "boolean" FILLET_CHAMFER = "fillet_chamfer" PATTERN = "pattern" # Query actions PROPERTIES = "properties" SELECTION = "selection" DOCUMENT = "document" # Analysis actions GEOMETRY = "geometry" PHYSICS = "physics" INTERFERENCE = "interference" @dataclass class Intent: """Represents a classified intent""" type: IntentType action: Optional[ActionType] confidence: float entities: List[Dict[str, Any]] parameters: Dict[str, Any] original_text: str keywords_matched: List[str] class IntentClassifier: """ Classifies user intent from natural language input to determine what action the user wants to perform. """ def __init__(self): """Initialize the Intent Classifier""" self.intent_patterns = self._build_intent_patterns() self.entity_patterns = self._build_entity_patterns() self.parameter_extractors = self._build_parameter_extractors() # Intent keywords mapping self.intent_keywords = { IntentType.CREATE: [ "create", "make", "build", "add", "new", "generate", "construct", "draw", "design", "model", "produce", ], IntentType.MODIFY: [ "modify", "change", "edit", "update", "alter", "adjust", "transform", "move", "rotate", "scale", "resize", "extend", "trim", ], IntentType.DELETE: [ "delete", "remove", "erase", "clear", "destroy", "eliminate", "cut", "subtract", ], IntentType.QUERY: [ "what", "which", "where", "show", "find", "get", "list", "display", "tell", "info", "information", ], IntentType.MEASURE: [ "measure", "calculate", "compute", "distance", "length", "area", "volume", "angle", "dimension", "size", ], IntentType.EXPORT: [ "export", "save", "output", "write", "generate file", "convert to", ], IntentType.IMPORT: ["import", "load", "open", "read", "bring in"], IntentType.VIEW: [ "view", "look", "zoom", "pan", "orbit", "fit", "focus", "center", ], IntentType.ANALYZE: [ "analyze", "check", "verify", "validate", "inspect", "evaluate", "test", "examine", ], IntentType.HELP: [ "help", "how", "explain", "guide", "tutorial", "documentation", "what is", "how to", ], IntentType.CONFIGURE: [ "configure", "settings", "preferences", "setup", "options", "customize", "config", ], } # Action keywords self.action_keywords = { ActionType.PRIMITIVE: [ "box", "cube", "cylinder", "sphere", "cone", "torus", ], ActionType.SKETCH: ["sketch", "2d", "profile", "drawing"], ActionType.TRANSFORM: ["move", "translate", "rotate", "scale", "mirror"], ActionType.BOOLEAN: [ "union", "difference", "intersection", "combine", "subtract", ], ActionType.FILLET_CHAMFER: ["fillet", "round", "chamfer", "bevel"], ActionType.PATTERN: ["pattern", "array", "duplicate", "copy", "repeat"], } def classify(self, text: str, context: Optional[Dict] = None) -> Intent: """ Classify intent from user input text Args: text: User input text context: Optional context information Returns: Intent object with classification results """ text_lower = text.lower() # Extract entities first entities = self._extract_entities(text) # Determine intent type intent_type, intent_confidence, intent_keywords = self._classify_intent_type( text_lower ) # Determine action type action_type = self._classify_action_type(text_lower, intent_type) # Extract parameters parameters = self._extract_parameters(text, intent_type, action_type) # Enhance with context if context: self._enhance_with_context(intent_type, action_type, parameters, context) # Adjust confidence based on clarity confidence = self._calculate_confidence( text_lower, intent_type, action_type, entities, parameters ) return Intent( type=intent_type, action=action_type, confidence=confidence, entities=entities, parameters=parameters, original_text=text, keywords_matched=intent_keywords, ) def _build_intent_patterns(self) -> Dict[IntentType, List[re.Pattern]]: """Build regex patterns for intent detection""" patterns = {} # Create patterns patterns[IntentType.CREATE] = [ re.compile(r"\b(create|make|build|add)\s+(a\s+)?(\w+)", re.I), re.compile(r"\b(new|generate)\s+(\w+)", re.I), re.compile(r"\b(draw|design)\s+(a\s+)?(\w+)", re.I), ] patterns[IntentType.MODIFY] = [ re.compile(r"\b(modify|change|edit|update)\s+(\w+)", re.I), re.compile(r"\b(move|rotate|scale)\s+(the\s+)?(\w+)?", re.I), re.compile(r"\b(extend|trim|offset)\s+(\w+)", re.I), ] patterns[IntentType.DELETE] = [ re.compile(r"\b(delete|remove|erase)\s+(the\s+)?(\w+)?", re.I), re.compile(r"\b(clear|destroy)\s+(all\s+)?(\w+)?", re.I), ] patterns[IntentType.QUERY] = [ re.compile(r"\b(what|which|where)\s+(is|are)\s+(\w+)", re.I), re.compile(r"\b(show|display|list)\s+(me\s+)?(the\s+)?(\w+)?", re.I), re.compile(r"\b(get|find)\s+(the\s+)?(\w+)", re.I), ] patterns[IntentType.MEASURE] = [ re.compile( r"\b(measure|calculate)\s+(the\s+)?(distance|length|area|volume)", re.I ), re.compile(r"\b(what\s+is\s+the)\s+(distance|length|area|volume)", re.I), re.compile(r"\b(compute|find)\s+(the\s+)?(dimension|size)", re.I), ] return patterns def _build_entity_patterns(self) -> Dict[str, re.Pattern]: """Build patterns for entity extraction""" return { "object_type": re.compile( r"\b(box|cube|cylinder|sphere|cone|torus|sketch|part|assembly|body)\b", re.I, ), "dimension": re.compile(r"(\d+(?:\.\d+)?)\s*(mm|cm|m|inch|in|ft)?", re.I), "color": re.compile( r"\b(red|green|blue|yellow|orange|purple|black|white|gray|grey)\b", re.I ), "position": re.compile( r"at\s*\(?\s*(-?\d+(?:\.\d+)?)\s*,\s*(-?\d+(?:\.\d+)?)\s*,\s*(-?\d+(?:\.\d+)?)\s*\)?", re.I, ), "angle": re.compile( r"(\d+(?:\.\d+)?)\s*(degrees?|deg|radians?|rad|°)", re.I ), "count": re.compile(r"\b(\d+)\s+(times?|copies|instances?)\b", re.I), "axis": re.compile(r"\b(x|y|z)[\s-]?axis\b", re.I), "direction": re.compile( r"\b(up|down|left|right|forward|backward|north|south|east|west)\b", re.I ), "reference": re.compile( r"\b(selected|current|active|last|previous|all)\b", re.I ), } def _build_parameter_extractors(self) -> Dict[str, callable]: """Build parameter extraction functions""" def extract_dimensions(text): """Extract dimensional parameters""" dims = [] pattern = re.compile(r"(\d+(?:\.\d+)?)\s*(mm|cm|m|inch|in)?", re.I) for match in pattern.findall(text): value = float(match[0]) unit = match[1] if match[1] else "mm" dims.append({"value": value, "unit": unit}) return dims def extract_position(text): """Extract position parameters""" pattern = re.compile( r"at\s*\(?\s*(-?\d+(?:\.\d+)?)\s*,\s*(-?\d+(?:\.\d+)?)\s*,\s*(-?\d+(?:\.\d+)?)\s*\)?", re.I, ) match = pattern.search(text) if match: return { "x": float(match.group(1)), "y": float(match.group(2)), "z": float(match.group(3)), } return None return {"dimensions": extract_dimensions, "position": extract_position} def _classify_intent_type(self, text: str) -> Tuple[IntentType, float, List[str]]: """Classify the intent type from text""" intent_scores = {} matched_keywords = {} # Check keywords for intent_type, keywords in self.intent_keywords.items(): score = 0 matches = [] for keyword in keywords: if keyword in text: score += 1 matches.append(keyword) if score > 0: intent_scores[intent_type] = score matched_keywords[intent_type] = matches # Check patterns for intent_type, patterns in self.intent_patterns.items(): for pattern in patterns: if pattern.search(text): intent_scores[intent_type] = intent_scores.get(intent_type, 0) + 2 # Determine best match if intent_scores: best_intent = max(intent_scores.items(), key=lambda x: x[1]) confidence = min(best_intent[1] / 5.0, 1.0) # Normalize confidence keywords = matched_keywords.get(best_intent[0], []) return best_intent[0], confidence, keywords return IntentType.UNKNOWN, 0.0, [] def _classify_action_type( self, text: str, intent_type: IntentType ) -> Optional[ActionType]: """Classify the action type based on intent and text""" if intent_type == IntentType.CREATE: # Check for primitive shapes for keyword in self.action_keywords[ActionType.PRIMITIVE]: if keyword in text: return ActionType.PRIMITIVE # Check for sketch if "sketch" in text or "2d" in text: return ActionType.SKETCH elif intent_type == IntentType.MODIFY: # Check for transformations if any(word in text for word in ["move", "translate", "rotate", "scale"]): return ActionType.TRANSFORM # Check for boolean if any( word in text for word in ["union", "difference", "intersection", "subtract"] ): return ActionType.BOOLEAN # Check for fillet/chamfer if any(word in text for word in ["fillet", "round", "chamfer", "bevel"]): return ActionType.FILLET_CHAMFER return None def _extract_entities(self, text: str) -> List[Dict[str, Any]]: """Extract entities from text""" entities = [] for entity_type, pattern in self.entity_patterns.items(): matches = pattern.findall(text) for match in matches: entity = { "type": entity_type, "value": match, "start": text.find(str(match)), "end": text.find(str(match)) + len(str(match)), } entities.append(entity) return entities def _extract_parameters( self, text: str, intent_type: IntentType, action_type: Optional[ActionType] ) -> Dict[str, Any]: """Extract parameters based on intent and action""" parameters = {} # Extract dimensions if "dimensions" in self.parameter_extractors: dims = self.parameter_extractors["dimensions"](text) if dims: if len(dims) >= 3: parameters["length"] = dims[0]["value"] parameters["width"] = dims[1]["value"] parameters["height"] = dims[2]["value"] elif len(dims) == 2: parameters["length"] = dims[0]["value"] parameters["width"] = dims[1]["value"] elif len(dims) == 1: parameters["size"] = dims[0]["value"] # Extract position if "position" in self.parameter_extractors: pos = self.parameter_extractors["position"](text) if pos: parameters["position"] = pos # Extract specific parameters based on intent if intent_type == IntentType.CREATE and action_type == ActionType.PRIMITIVE: # Check for specific shape parameters if "radius" in text: radius_match = re.search( r"radius\s*(?:of\s*)?(\d+(?:\.\d+)?)", text, re.I ) if radius_match: parameters["radius"] = float(radius_match.group(1)) elif intent_type == IntentType.MODIFY and action_type == ActionType.TRANSFORM: # Extract transformation parameters angle_match = re.search(r"(\d+(?:\.\d+)?)\s*degrees?", text, re.I) if angle_match: parameters["angle"] = float(angle_match.group(1)) return parameters def _enhance_with_context( self, intent_type: IntentType, action_type: Optional[ActionType], parameters: Dict[str, Any], context: Dict, ): """Enhance classification with context information""" # If there's a selection and intent is modify/delete, assume operation on selection if context.get("selection", {}).get("has_selection"): if intent_type in [ IntentType.MODIFY, IntentType.DELETE, IntentType.MEASURE, ]: parameters["target"] = "selection" # If no object specified but there's only one object, assume that's the target if context.get("objects", {}).get("total_count") == 1: if "target" not in parameters: parameters["target"] = "current_object" def _calculate_confidence( self, text: str, intent_type: IntentType, action_type: Optional[ActionType], entities: List[Dict], parameters: Dict, ) -> float: """Calculate overall confidence score""" confidence = 0.5 # Base confidence # Boost confidence for clear intent if intent_type != IntentType.UNKNOWN: confidence += 0.2 # Boost for specific action if action_type is not None: confidence += 0.1 # Boost for entities found if entities: confidence += min(len(entities) * 0.05, 0.15) # Boost for parameters extracted if parameters: confidence += min(len(parameters) * 0.05, 0.15) # Penalty for very short or very long text text_length = len(text.split()) if text_length < 3 or text_length > 50: confidence *= 0.8 return min(confidence, 1.0) def explain_classification(self, intent: Intent) -> str: """Generate human-readable explanation of the classification""" explanation = f"I understood that you want to {intent.type.value}" if intent.action: explanation += f" (specifically: {intent.action.value})" if intent.entities: objects = [ e["value"] for e in intent.entities if e["type"] == "object_type" ] if objects: explanation += f" involving {', '.join(objects)}" if intent.parameters: param_strs = [] for key, value in intent.parameters.items(): if key == "position": param_strs.append( f"at position ({value['x']}, {value['y']}, {value['z']})" ) elif key in ["length", "width", "height", "radius"]: param_strs.append(f"{key}: {value}") if param_strs: explanation += f" with {', '.join(param_strs)}" explanation += f" (confidence: {intent.confidence:.0%})" return explanation def get_intent_suggestions(self, partial_text: str) -> List[str]: """Get intent suggestions for partial input""" suggestions = [] for intent_type, keywords in self.intent_keywords.items(): for keyword in keywords: if keyword.startswith(partial_text.lower()): suggestions.append(keyword) return list(set(suggestions))[:10]

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/jango-blockchained/mcp-freecad'

If you have feedback or need assistance with the MCP directory API, please join our Discord server