GroqEnglish / core.py
OttoUlbrich's picture
feat: Refactor to use OpenAI client and improve response handling
15b17b1
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