""" Input Agent - Parses user requirements for all model types """ import re from typing import List, Optional from src.models.schemas import ( TaskType, HardwareConstraint, UserRequirements, TranslationRequirements, TTSRequirements, STTRequirements, LLMRequirements, OCRRequirements, Language, VoiceType, ModelSize ) class InputAgent: """ Analyzes user's task description and extracts structured requirements for translation, TTS, STT, LLMs, and OCR. """ def __init__(self): # Map keywords to task types self.task_keywords = { # Translation TaskType.TRANSLATION: [ "translate", "translation", "convert language", "english to french", "en to es", "german", "spanish", "french" ], # TTS TaskType.TEXT_TO_SPEECH: [ "text to speech", "tts", "speech synthesis", "voice", "speak", "audio from text", "read aloud" ], # STT TaskType.SPEECH_TO_TEXT: [ "speech to text", "stt", "transcribe", "audio to text", "asr", "automatic speech recognition", "voice to text" ], # LLMs TaskType.TEXT_GENERATION: [ "generate", "write", "completion", "continue", "story", "llm", "large language model", "gpt", "llama" ], TaskType.CHAT: [ "chat", "conversation", "dialogue", "chatbot", "assistant" ], TaskType.INSTRUCTION_FOLLOWING: [ "instruction", "follow instruction", "task", "command" ], TaskType.CODE_GENERATION: [ "code", "programming", "python", "javascript", "function" ], TaskType.QUESTION_ANSWERING: [ "question answering", "qa", "answer question", "extract answer" ], TaskType.SUMMARIZATION: [ "summarize", "summary", "abstract", "condense", "tl;dr" ], # OCR TaskType.OCR: [ "ocr", "optical character recognition", "extract text from image", "read image", "scan document", "image to text", "text from photo" ], TaskType.DOCUMENT_UNDERSTANDING: [ "document understanding", "document qa", "document analysis", "form understanding", "invoice parsing" ] } def parse_requirements(self, task_description: str) -> UserRequirements: """ Parse user's task description and extract requirements. """ task_type = self._extract_task_type(task_description) hardware_constraints = self._extract_hardware_constraints(task_description) max_model_size = self._extract_model_size(task_description) # Parse task-specific requirements translation_reqs = None tts_reqs = None stt_reqs = None llm_reqs = None ocr_reqs = None if task_type == TaskType.TRANSLATION: translation_reqs = self._parse_translation_requirements(task_description) elif task_type == TaskType.TEXT_TO_SPEECH: tts_reqs = self._parse_tts_requirements(task_description) elif task_type == TaskType.SPEECH_TO_TEXT: stt_reqs = self._parse_stt_requirements(task_description) elif task_type in [TaskType.TEXT_GENERATION, TaskType.CHAT, TaskType.INSTRUCTION_FOLLOWING, TaskType.CODE_GENERATION, TaskType.QUESTION_ANSWERING, TaskType.SUMMARIZATION]: llm_reqs = self._parse_llm_requirements(task_description, task_type) elif task_type in [TaskType.OCR, TaskType.DOCUMENT_UNDERSTANDING]: ocr_reqs = self._parse_ocr_requirements(task_description) return UserRequirements( task_type=task_type, hardware_constraints=hardware_constraints, max_model_size_gb=max_model_size, translation_reqs=translation_reqs, tts_reqs=tts_reqs, stt_reqs=stt_reqs, llm_reqs=llm_reqs, ocr_reqs=ocr_reqs ) def _extract_task_type(self, description: str) -> TaskType: """Identify the task from description""" description_lower = description.lower() for task_type, keywords in self.task_keywords.items(): if any(keyword in description_lower for keyword in keywords): return task_type # Default to text generation if unsure return TaskType.TEXT_GENERATION def _parse_translation_requirements(self, description: str) -> TranslationRequirements: """Extract translation-specific requirements""" description_lower = description.lower() # Default values source_lang = Language.ENGLISH target_lang = Language.SPANISH # Try to extract language pairs patterns = [ r'(?:from\s+)?(\w+)\s+(?:to|in(?:to)?)\s+(\w+)', r'(\w+)[\s-]+to[\s-]+(\w+)', ] for pattern in patterns: match = re.search(pattern, description_lower) if match: lang1, lang2 = match.groups() source_lang = self._map_language(lang1) target_lang = self._map_language(lang2) break # Determine domain domain = None domains = ["medical", "legal", "technical", "financial", "literary"] for d in domains: if d in description_lower: domain = d break # Quality preference quality = "balanced" if "fast" in description_lower or "quick" in description_lower: quality = "speed" elif "high quality" in description_lower or "accurate" in description_lower: quality = "quality" return TranslationRequirements( source_language=source_lang, target_language=target_lang, domain=domain, quality_preference=quality ) def _parse_tts_requirements(self, description: str) -> TTSRequirements: """Extract TTS-specific requirements""" description_lower = description.lower() # Extract language language = Language.ENGLISH for lang in Language: if lang.value in description_lower or lang.name.lower() in description_lower: language = lang break # Extract voice type voice = VoiceType.NEUTRAL if "male" in description_lower: voice = VoiceType.MALE elif "female" in description_lower: voice = VoiceType.FEMALE # Check if multiple voices wanted multiple = "multiple voices" in description_lower or "different voices" in description_lower return TTSRequirements( language=language, voice_type=voice, wants_multiple_voices=multiple ) def _parse_stt_requirements(self, description: str) -> STTRequirements: """Extract STT-specific requirements""" description_lower = description.lower() # Extract language language = Language.ENGLISH for lang in Language: if lang.value in description_lower or lang.name.lower() in description_lower: language = lang break # Extract domain domain = None if "medical" in description_lower: domain = "medical" elif "telephone" in description_lower or "call" in description_lower: domain = "telephony" elif "meeting" in description_lower: domain = "meeting" # Check for advanced features timestamps = "timestamp" in description_lower or "word timing" in description_lower diarization = "speaker" in description_lower or "who said" in description_lower return STTRequirements( language=language, domain=domain, wants_word_timestamps=timestamps, wants_diarization=diarization ) def _parse_llm_requirements(self, description: str, task_type: TaskType) -> LLMRequirements: """Extract LLM-specific requirements""" description_lower = description.lower() # Determine model size preference model_size = ModelSize.MEDIUM if any(x in description_lower for x in ["tiny", "small", "lightweight", "fast"]): model_size = ModelSize.SMALL elif any(x in description_lower for x in ["large", "powerful", "best quality"]): model_size = ModelSize.LARGE elif any(x in description_lower for x in ["xlarge", "huge", "massive"]): model_size = ModelSize.XXLARGE # Context length context_length = 2048 # default context_match = re.search(r'(\d+)[kK]?\s*(context|token)', description_lower) if context_match: val = context_match.group(1) if 'k' in context_match.group(0).lower(): context_length = int(val) * 1024 else: context_length = int(val) # Check for specific capabilities wants_chat = any(x in description_lower for x in ["chat", "conversation", "dialogue"]) wants_code = any(x in description_lower for x in ["code", "programming", "python", "javascript"]) wants_instruction = any(x in description_lower for x in ["instruction", "task", "command"]) wants_function = any(x in description_lower for x in ["function calling", "tools", "actions"]) wants_multilingual = any(x in description_lower for x in ["multilingual", "multiple languages"]) # Check for quantization quantization = None if "4bit" in description_lower or "4-bit" in description_lower: quantization = "4bit" elif "8bit" in description_lower or "8-bit" in description_lower: quantization = "8bit" return LLMRequirements( model_size=model_size, context_length=context_length, wants_chat_template=wants_chat, wants_function_calling=wants_function, wants_code_generation=wants_code, wants_instruction_following=wants_instruction, wants_multilingual=wants_multilingual, quantization=quantization ) def _parse_ocr_requirements(self, description: str) -> OCRRequirements: """Extract OCR-specific requirements""" description_lower = description.lower() # Extract languages languages = [Language.ENGLISH] for lang in Language: if lang.value in description_lower or lang.name.lower() in description_lower: languages = [lang] break # Check for handwriting handwritten = "handwriting" in description_lower or "handwritten" in description_lower # Document type doc_type = None if "scanned" in description_lower: doc_type = "scanned" elif "photo" in description_lower or "photograph" in description_lower: doc_type = "photo" elif "document" in description_lower: doc_type = "document" # Check for advanced features layout = "layout" in description_lower or "paragraph" in description_lower tables = "table" in description_lower or "spreadsheet" in description_lower formulas = any(x in description_lower for x in ["formula", "equation", "math"]) return OCRRequirements( languages=languages, handwritten=handwritten, document_type=doc_type, wants_layout_analysis=layout, wants_table_extraction=tables, wants_formula_recognition=formulas ) def _map_language(self, lang_text: str) -> Language: """Map language name/code to Language enum""" lang_map = { "en": Language.ENGLISH, "english": Language.ENGLISH, "es": Language.SPANISH, "spanish": Language.SPANISH, "fr": Language.FRENCH, "french": Language.FRENCH, "de": Language.GERMAN, "german": Language.GERMAN, "it": Language.ITALIAN, "italian": Language.ITALIAN, "pt": Language.PORTUGUESE, "portuguese": Language.PORTUGUESE, "nl": Language.DUTCH, "dutch": Language.DUTCH, "ru": Language.RUSSIAN, "russian": Language.RUSSIAN, "zh": Language.CHINESE, "chinese": Language.CHINESE, "ja": Language.JAPANESE, "japanese": Language.JAPANESE, "ko": Language.KOREAN, "korean": Language.KOREAN, "ar": Language.ARABIC, "arabic": Language.ARABIC, "hi": Language.HINDI, "hindi": Language.HINDI } return lang_map.get(lang_text.lower(), Language.ENGLISH) def _extract_hardware_constraints(self, description: str) -> List[HardwareConstraint]: """Extract hardware constraints""" constraints = [] description_lower = description.lower() if any(word in description_lower for word in ["cpu", "no gpu", "without gpu"]): constraints.append(HardwareConstraint.CPU) if any(word in description_lower for word in ["4gb", "4 gb", "small gpu"]): constraints.append(HardwareConstraint.GPU_4GB) if any(word in description_lower for word in ["8gb", "8 gb", "medium gpu"]): constraints.append(HardwareConstraint.GPU_8GB) if any(word in description_lower for word in ["16gb", "16 gb"]): constraints.append(HardwareConstraint.GPU_16GB) if any(word in description_lower for word in ["24gb", "24 gb"]): constraints.append(HardwareConstraint.GPU_24GB) if any(word in description_lower for word in ["40gb", "40 gb", "a100"]): constraints.append(HardwareConstraint.GPU_40GB) if any(word in description_lower for word in ["80gb", "80 gb"]): constraints.append(HardwareConstraint.GPU_80GB) if "tpu" in description_lower: constraints.append(HardwareConstraint.TPU) return constraints if constraints else [HardwareConstraint.CPU] def _extract_model_size(self, description: str) -> Optional[float]: """Extract maximum model size constraint""" size_pattern = r'(\d+(?:\.\d+)?)\s*(gb|mb)' match = re.search(size_pattern, description.lower()) if match: size = float(match.group(1)) unit = match.group(2).lower() if unit == 'gb': return size elif unit == 'mb': return size / 1024 return None