File size: 11,434 Bytes
1fc131a |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 |
# modules/studentact/claude_recommendations.py
import os
import anthropic
import streamlit as st
import logging
import time
import json
from datetime import datetime, timezone
# Local imports
from ..utils.widget_utils import generate_unique_key
from ..database.current_situation_mongo_db import store_current_situation_result
logger = logging.getLogger(__name__)
# Define text types
TEXT_TYPES = {
'es': {
'academic_article': 'artículo académico',
'university_work': 'trabajo universitario',
'general_communication': 'comunicación general'
},
'en': {
'academic_article': 'academic article',
'university_work': 'university work',
'general_communication': 'general communication'
},
'fr': {
'academic_article': 'article académique',
'university_work': 'travail universitaire',
'general_communication': 'communication générale'
}
}
# Cache for recommendations to avoid redundant API calls
recommendation_cache = {}
def get_recommendation_cache_key(text, metrics, text_type, lang_code):
"""
Generate a cache key for recommendations.
"""
# Create a simple hash based on text content and metrics
text_hash = hash(text[:1000]) # Only use first 1000 chars for hashing
metrics_hash = hash(json.dumps(metrics, sort_keys=True))
return f"{text_hash}_{metrics_hash}_{text_type}_{lang_code}"
def format_metrics_for_claude(metrics, lang_code, text_type):
"""
Format metrics in a way that's readable for Claude
"""
formatted_metrics = {}
for key, value in metrics.items():
if isinstance(value, (int, float)):
formatted_metrics[key] = round(value, 2)
else:
formatted_metrics[key] = value
# Add context about what type of text this is
text_type_label = TEXT_TYPES.get(lang_code, {}).get(text_type, text_type)
formatted_metrics['text_type'] = text_type_label
return formatted_metrics
def generate_claude_recommendations(text, metrics, text_type, lang_code):
"""
Generate personalized recommendations using Claude API.
"""
try:
api_key = os.environ.get("ANTHROPIC_API_KEY")
if not api_key:
logger.error("Claude API key not found in environment variables")
return get_fallback_recommendations(lang_code)
# Check cache first
cache_key = get_recommendation_cache_key(text, metrics, text_type, lang_code)
if cache_key in recommendation_cache:
logger.info("Using cached recommendations")
return recommendation_cache[cache_key]
# Format metrics for Claude
formatted_metrics = format_metrics_for_claude(metrics, lang_code, text_type)
# Determine language for prompt
if lang_code == 'es':
system_prompt = """Eres un asistente especializado en análisis de textos académicos y comunicación escrita.
Tu tarea es analizar el texto del usuario y proporcionar recomendaciones personalizadas.
Usa un tono constructivo y específico. Sé claro y directo con tus sugerencias.
"""
user_prompt = f"""Por favor, analiza este texto de tipo '{formatted_metrics['text_type']}'
y proporciona recomendaciones personalizadas para mejorarlo.
MÉTRICAS DE ANÁLISIS:
{json.dumps(formatted_metrics, indent=2, ensure_ascii=False)}
TEXTO A ANALIZAR:
{text[:2000]} # Limitamos el texto para evitar exceder tokens
Proporciona tu análisis con el siguiente formato:
1. Un resumen breve (2-3 frases) del análisis general
2. 3-4 recomendaciones específicas y accionables (cada una de 1-2 frases)
3. Un ejemplo concreto de mejora tomado del propio texto del usuario
4. Una sugerencia sobre qué herramienta de AIdeaText usar (Análisis Morfosintáctico, Análisis Semántico o Análisis del Discurso)
Tu respuesta debe ser concisa y no exceder los 300 palabras."""
else:
# Default to English
system_prompt = """You are an assistant specialized in analyzing academic texts and written communication.
Your task is to analyze the user's text and provide personalized recommendations.
Use a constructive and specific tone. Be clear and direct with your suggestions.
"""
user_prompt = f"""Please analyze this text of type '{formatted_metrics['text_type']}'
and provide personalized recommendations to improve it.
ANALYSIS METRICS:
{json.dumps(formatted_metrics, indent=2, ensure_ascii=False)}
TEXT TO ANALYZE:
{text[:2000]} # Limiting text to avoid exceeding tokens
Provide your analysis with the following format:
1. A brief summary (2-3 sentences) of the general analysis
2. 3-4 specific and actionable recommendations (each 1-2 sentences)
3. A concrete example of improvement taken from the user's own text
4. A suggestion about which AIdeaText tool to use (Morphosyntactic Analysis, Semantic Analysis or Discourse Analysis)
Your response should be concise and not exceed 300 words."""
# Initialize Claude client
client = anthropic.Anthropic(api_key=api_key)
# Call Claude API
start_time = time.time()
response = client.messages.create(
model="claude-3-5-sonnet-20241022",
max_tokens=1024,
temperature=0.7,
system=system_prompt,
messages=[
{"role": "user", "content": user_prompt}
]
)
logger.info(f"Claude API call completed in {time.time() - start_time:.2f} seconds")
# Extract recommendations
recommendations = response.content[0].text
# Cache the result
recommendation_cache[cache_key] = recommendations
return recommendations
except Exception as e:
logger.error(f"Error generating recommendations with Claude: {str(e)}")
return get_fallback_recommendations(lang_code)
def get_fallback_recommendations(lang_code):
"""
Return fallback recommendations if Claude API fails
"""
if lang_code == 'es':
return """
**Análisis General**
Tu texto presenta una estructura básica adecuada, pero hay áreas que pueden mejorarse para mayor claridad y cohesión.
**Recomendaciones**:
- Intenta variar tu vocabulario para evitar repeticiones innecesarias
- Considera revisar la longitud de tus oraciones para mantener un mejor ritmo
- Asegúrate de establecer conexiones claras entre las ideas principales
- Revisa la consistencia en el uso de tiempos verbales
**Herramienta recomendada**:
Te sugerimos utilizar el Análisis Morfosintáctico para identificar patrones en tu estructura de oraciones.
"""
else:
return """
**General Analysis**
Your text presents an adequate basic structure, but there are areas that can be improved for better clarity and cohesion.
**Recommendations**:
- Try to vary your vocabulary to avoid unnecessary repetition
- Consider reviewing the length of your sentences to maintain a better rhythm
- Make sure to establish clear connections between main ideas
- Check consistency in the use of verb tenses
**Recommended tool**:
We suggest using Morphosyntactic Analysis to identify patterns in your sentence structure.
"""
#######################################
def store_recommendations(username, text, metrics, text_type, recommendations):
"""
Store the recommendations in the database
"""
try:
# Importar la función de almacenamiento de recomendaciones
from ..database.claude_recommendations_mongo_db import store_claude_recommendation
# Guardar usando la nueva función especializada
result = store_claude_recommendation(
username=username,
text=text,
metrics=metrics,
text_type=text_type,
recommendations=recommendations
)
logger.info(f"Recommendations stored successfully: {result}")
return result
except Exception as e:
logger.error(f"Error storing recommendations: {str(e)}")
return False
##########################################
##########################################
def display_personalized_recommendations(text, metrics, text_type, lang_code, t):
"""
Display personalized recommendations based on text analysis
"""
try:
# Generate recommendations
recommendations = generate_claude_recommendations(text, metrics, text_type, lang_code)
# Format and display recommendations in a nice container
st.markdown("### 📝 " + t.get('recommendations_title', 'Personalized Recommendations'))
with st.container():
st.markdown(f"""
<div style="padding: 20px; border-radius: 10px;
background-color: #f8f9fa; margin-bottom: 20px;">
{recommendations}
</div>
""", unsafe_allow_html=True)
# Add prompt to use assistant
st.info("💡 **" + t.get('assistant_prompt', 'For further improvement:') + "** " +
t.get('assistant_message', 'Open the virtual assistant (powered by Claude AI) in the upper left corner by clicking the arrow next to the logo.'))
# Add save button
col1, col2, col3 = st.columns([1,1,1])
with col2:
if st.button(
t.get('save_button', 'Save Analysis'),
key=generate_unique_key("claude_recommendations", "save"),
type="primary",
use_container_width=True
):
if 'username' in st.session_state:
success = store_recommendations(
st.session_state.username,
text,
metrics,
text_type,
recommendations
)
if success:
st.success(t.get('save_success', 'Analysis saved successfully'))
else:
st.error(t.get('save_error', 'Error saving analysis'))
else:
st.error(t.get('login_required', 'Please log in to save analysis'))
except Exception as e:
logger.error(f"Error displaying recommendations: {str(e)}")
st.error(t.get('recommendations_error', 'Error generating recommendations. Please try again later.')) |