import os import json from groq import Groq from openai import OpenAI # Load environment variables only if .env file exists (local development) try: from dotenv import load_dotenv load_dotenv() except ImportError: # dotenv not needed in production (Hugging Face Spaces) pass # Initialize Groq client # Security Note: API Key is loaded from environment variable GROQ_API_KEY # In Hugging Face Spaces, this will be loaded from Repository Secrets # client = Groq( # api_key=os.environ.get("GROQ_API_KEY"), # ) MODEL_NAME = "llama-3.3-70b-versatile" GLM_MODEL_NAME = "glm-4.7" GLM_BASE_URL = "https://api.z.ai/api/coding/paas/v4" SYSTEM_PROMPT = """ You are an expert English language tutor for Spanish speakers. Your task is to analyze user's English input and provide a "mirror" response that includes: 1. **Grammatical Error Correction (GEC)**: Correct any morphosyntactic, lexical, or spelling errors. 2. **Reverse Translation**: Translate the *corrected* English text back into Spanish to verify the semantic intention. 3. **Pedagogical Explanation**: Explain the grammatical rules violated in Spanish. Output valid JSON strictly following this schema: { "corrected_text": "Complete corrected English text", "spanish_translation": "Spanish translation of the corrected text", "errors": [ { "original": "Error word/phrase", "correction": "Correction", "explanation": "Brief explanation in Spanish", "type": "Grammar/Spelling/Style" } ], "general_feedback": "Short motivational message in Spanish" } If there are no errors, the "errors" list should be empty, and "corrected_text" should be the same as the input. Do not include any text outside of JSON object. """ def analyze_text_glm(input_text: str, api_key: str) -> dict: """ Analyzes the input English text using GLM-4.7 API and returns a structured response. Args: input_text (str): The English text to analyze. api_key (str): The GLM API key to use. Returns: dict: The structured analysis result. """ if not input_text.strip(): return { "corrected_text": "", "spanish_translation": "", "errors": [], "general_feedback": "Por favor, ingresa un texto para analizar.", } if not api_key: return { "corrected_text": input_text, "spanish_translation": "Error de configuración", "errors": [], "general_feedback": "No se proporcionó una API Key válida.", } try: client = OpenAI(api_key=api_key, base_url=GLM_BASE_URL) response = client.chat.completions.create( model=GLM_MODEL_NAME, messages=[ {"role": "system", "content": SYSTEM_PROMPT}, {"role": "user", "content": input_text}, ], temperature=0, ) content = response.choices[0].message.content # Log raw response for debugging raw_response = content if not raw_response.strip(): return { "corrected_text": input_text, "spanish_translation": "Error al procesar", "errors": [], "general_feedback": f"Ocurrió un error: GLM devolvió una respuesta vacía o con solo espacios. Longitud: {len(raw_response)} caracteres. Respuesta cruda: '{raw_response}'", } # Clean markdown code blocks from response cleaned_response = raw_response.strip() # Remove ```json and ``` if present if cleaned_response.startswith("```json"): cleaned_response = cleaned_response[7:] # Remove ```json if cleaned_response.startswith("```"): cleaned_response = cleaned_response[3:] # Remove ``` if cleaned_response.endswith("```"): cleaned_response = cleaned_response[:-3] # Remove trailing ``` # Remove any system messages or content after JSON # Find closing brace of the JSON object try: json_end = cleaned_response.rfind("}") + 1 if json_end > 0: cleaned_response = cleaned_response[:json_end] except: pass try: return json.loads(cleaned_response) except json.JSONDecodeError as json_error: # Return actual response content for debugging preview = ( cleaned_response[:500] if len(cleaned_response) > 500 else cleaned_response ) return { "corrected_text": input_text, "spanish_translation": "Error al procesar", "errors": [], "general_feedback": f"Error de JSON: {str(json_error)}. Respuesta cruda del modelo (primeros 500 caracteres): {preview}", } except Exception as e: return { "corrected_text": input_text, "spanish_translation": "Error al procesar", "errors": [], "general_feedback": f"Ocurrió un error al conectar con GLM: {str(e)}", } def analyze_text(input_text: str, api_key: str, provider: str = "groq") -> dict: """ Analyzes the input English text using the specified provider and returns a structured response. Args: input_text (str): The English text to analyze. api_key (str): The API key to use for the provider. provider (str): The provider to use - "groq" or "glm". Defaults to "groq". Returns: dict: The structured analysis result. """ if provider == "glm": return analyze_text_glm(input_text, api_key) # Default Groq provider logic if not input_text.strip(): return { "corrected_text": "", "spanish_translation": "", "errors": [], "general_feedback": "Por favor, ingresa un texto para analizar.", } if not api_key: return { "corrected_text": input_text, "spanish_translation": "Error de configuración", "errors": [], "general_feedback": "No se proporcionó una API Key válida.", } try: client = Groq(api_key=api_key) completion = client.chat.completions.create( model=MODEL_NAME, messages=[ {"role": "system", "content": SYSTEM_PROMPT}, {"role": "user", "content": input_text}, ], temperature=0, response_format={"type": "json_object"}, ) response_content = completion.choices[0].message.content return json.loads(response_content) except Exception as e: # Fallback error handling return { "corrected_text": input_text, "spanish_translation": "Error al procesar", "errors": [], "general_feedback": f"Ocurrió un error al conectar con el asistente: {str(e)}", } if __name__ == "__main__": # Simple test test_text = "She dont like play football." print("Analyzing:", test_text) result = analyze_text(test_text) print(json.dumps(result, indent=2, ensure_ascii=False)) def validate_glm_api_key(api_key: str) -> bool: """ Validates the GLM API Key by making a simple request. """ if not api_key: return False try: client = OpenAI( model=GLM_MODEL_NAME, base_url=GLM_BASE_URL, api_key=api_key, temperature=0, max_tokens=1, ) client.chat.completions.create( model=GLM_MODEL_NAME, messages=[{"role": "user", "content": "test"}], temperature=0, ) return True except Exception: return False def validate_api_key(api_key: str) -> bool: """ Validates the Groq API Key by making a simple request (listing models). """ if not api_key: return False try: client = Groq(api_key=api_key) # Attempt to list models to verify the key client.models.list() return True except Exception: return False