v5Prod / modules /chatbot /chat_process_by-Jamba.py
AIdeaText's picture
Rename modules/chatbot/chat_process.py to modules/chatbot/chat_process_by-Jamba.py
1c0c177 verified
# modules/chatbot/chat_process.py
import os
import json
import boto3
import logging
import time
import base64
from typing import Generator
from botocore.config import Config
from botocore.exceptions import ClientError
logger = logging.getLogger(__name__)
class ChatProcessor:
def __init__(self):
"""Inicializa el procesador de chat con AWS Bedrock (Jamba 1.5 Large)"""
# Configurar cliente de Bedrock con más reintentos
self.bedrock = boto3.client(
'bedrock-runtime',
region_name=os.environ.get("AWS_REGION", "us-east-1"),
aws_access_key_id=os.environ.get("AWS_ACCESS_KEY_ID"),
aws_secret_access_key=os.environ.get("AWS_SECRET_ACCESS_KEY"),
config=Config(
retries={
'max_attempts': 5,
'mode': 'adaptive'
}
)
)
self.conversation_history = []
self.semantic_context = None
self.current_lang = 'en'
self.last_request_time = 0
self.min_request_interval = 2.0 # Mínimo 2 segundos entre peticiones
def set_semantic_context(self, text, metrics, graph_data, lang_code='en'):
"""Configura el contexto semántico completo para el chat"""
if not text or not metrics:
logger.error("Faltan datos esenciales para el contexto semántico")
raise ValueError("Texto y métricas son requeridos")
self.semantic_context = {
'full_text': text,
'key_concepts': metrics.get('key_concepts', []),
'concept_centrality': metrics.get('concept_centrality', {}),
'graph_available': graph_data is not None,
'graph_data': graph_data,
'language': lang_code
}
self.current_lang = lang_code
self.conversation_history = []
logger.info("Contexto semántico configurado correctamente")
def _get_system_prompt(self):
"""Genera el prompt del sistema con todo el contexto necesario"""
if not self.semantic_context:
return "You are a helpful assistant."
concepts = self.semantic_context['key_concepts']
top_concepts = ", ".join([f"{c[0]} ({c[1]:.2f})" for c in concepts[:5]])
prompts = {
'en': f"""You are a semantic analysis expert. The user analyzed a research article.
Full text available (abbreviated for context).
Key concepts: {top_concepts}
Graph available: {self.semantic_context['graph_available']}
Your tasks:
1. Answer questions about concepts and their relationships
2. Explain the semantic network structure
3. Suggest text improvements
4. Provide insights based on concept centrality""",
'es': f"""Eres un experto en análisis semántico. El usuario analizó un artículo de investigación.
Texto completo disponible (abreviado para contexto).
Conceptos clave: {top_concepts}
Gráfico disponible: {self.semantic_context['graph_available']}
Tus tareas:
1. Responder preguntas sobre conceptos y sus relaciones
2. Explicar la estructura de la red semántica
3. Sugerir mejoras al texto
4. Proporcionar insights basados en centralidad de conceptos""",
'pt': f"""Você é um especialista em análise semântica. O usuário analisou um artigo de pesquisa.
Texto completo disponível (abreviado para contexto).
Conceitos-chave: {top_concepts}
Gráfico disponível: {self.semantic_context['graph_available']}
Suas tarefas:
1. Responder perguntas sobre conceitos e suas relações
2. Explicar a estrutura da rede semântica
3. Sugerir melhorias no texto
4. Fornecer insights com base na centralidade dos conceitos""",
'fr': f"""Vous êtes un expert en analyse sémantique. L'utilisateur a analysé un article de recherche.
Texte complet disponible (abrégé pour le contexte).
Concepts clés: {top_concepts}
Graphique disponible: {self.semantic_context['graph_available']}
Vos tâches:
1. Répondre aux questions sur les concepts et leurs relations
2. Expliquer la structure du réseau sémantique
3. Suggérer des améliorations de texte
4. Fournir des insights basés sur la centralité des concepts"""
}
return prompts.get(self.current_lang, prompts['en'])
def clean_generated_text(self, text):
"""Limpia caracteres especiales del texto generado"""
return text.replace("\u2588", "").replace("▌", "").strip()
def _build_multimodal_content(self, message):
"""Construye el contenido multimodal con texto + grafo si está disponible"""
content_parts = []
# 1. Añadir el texto del documento (reducido para ahorrar tokens)
if self.semantic_context and 'full_text' in self.semantic_context:
content_parts.append(
f"Documento analizado (extracto):\n{self.semantic_context['full_text'][:1000]}..."
)
# 2. Añadir conceptos clave
if self.semantic_context and 'key_concepts' in self.semantic_context:
concepts = self.semantic_context['key_concepts'][:5]
content_parts.append(f"Conceptos clave: {concepts}")
# 3. Añadir el mensaje actual del usuario
content_parts.append(f"Pregunta del usuario: {message}")
return "\n\n".join(content_parts)
def process_chat_input(self, message: str, lang_code: str) -> Generator[str, None, None]:
"""Procesa el mensaje con todo el contexto disponible usando Jamba 1.5 en Bedrock"""
max_retries = 3
base_delay = 5
for attempt in range(max_retries):
try:
if not self.semantic_context:
yield "Error: Contexto semántico no configurado. Recargue el análisis."
return
# Actualizar idioma si es diferente
if lang_code != self.current_lang:
self.current_lang = lang_code
logger.info(f"Idioma cambiado a: {lang_code}")
# Control de tasa simple (no más de 1 petición cada 2 segundos)
current_time = time.time()
time_since_last = current_time - self.last_request_time
if time_since_last < self.min_request_interval:
sleep_time = self.min_request_interval - time_since_last
logger.info(f"Respetando intervalo mínimo: esperando {sleep_time:.2f}s")
time.sleep(sleep_time)
# Construir el contenido multimodal
user_content = self._build_multimodal_content(message)
# Construir mensajes para Jamba
messages = []
# Añadir system prompt
messages.append({
"role": "system",
"content": self._get_system_prompt()
})
# Añadir historial de conversación (últimos 4 intercambios)
for msg in self.conversation_history[-8:]:
messages.append(msg)
# Añadir mensaje actual del usuario
messages.append({
"role": "user",
"content": user_content
})
# Preparar el cuerpo de la petición para Jamba 1.5 Large
request_body = {
"messages": messages,
"max_tokens": 1500, # Reducido de 2000 a 1500 para ahorrar tokens
"temperature": 0.7,
"top_p": 0.9,
"stop": [],
"n": 1
}
logger.info(f"Enviando petición a Jamba (intento {attempt + 1}/{max_retries})")
# Llamar a Bedrock
response = self.bedrock.invoke_model(
modelId='ai21.jamba-1-5-large-v1:0',
contentType='application/json',
accept='application/json',
body=json.dumps(request_body)
)
# Actualizar tiempo de última petición
self.last_request_time = time.time()
# Procesar la respuesta
response_body = json.loads(response['body'].read())
# Extraer el texto de la respuesta
if 'choices' in response_body and len(response_body['choices']) > 0:
full_response = response_body['choices'][0]['message']['content']
else:
full_response = "Lo siento, no pude generar una respuesta."
# Limpiar la respuesta
clean_response = self.clean_generated_text(full_response)
# Simular streaming
chunk_size = 50
for i in range(0, len(clean_response), chunk_size):
yield clean_response[i:i+chunk_size]
# Guardar respuesta en historial
self.conversation_history.append({"role": "user", "content": message})
self.conversation_history.append({"role": "assistant", "content": clean_response})
# Mantener historial manejable
if len(self.conversation_history) > 40:
self.conversation_history = self.conversation_history[-40:]
logger.info("Respuesta generada y guardada en historial")
return # Éxito, salir del bucle
except ClientError as e:
error_code = e.response['Error']['Code']
error_message = e.response['Error']['Message']
if error_code == 'ThrottlingException' and attempt < max_retries - 1:
wait_time = base_delay * (2 ** attempt) # 5, 10, 20 segundos
logger.warning(f"Throttling detectado. Esperando {wait_time}s (intento {attempt+1}/{max_retries})")
# Mensaje amigable para el usuario
if attempt == 0:
yield "⏳ El sistema está procesando muchas solicitudes. Espera un momento..."
time.sleep(wait_time)
else:
logger.error(f"Error en process_chat_input: {error_code} - {error_message}", exc_info=True)
error_messages = {
'en': "Error processing message. Please try again in a moment.",
'es': "Error al procesar mensaje. Intente nuevamente en un momento.",
'pt': "Erro ao processar mensagem. Tente novamente em um momento.",
'fr': "Erreur lors du traitement. Réessayez dans un moment."
}
yield error_messages.get(self.current_lang, "Processing error")
return
except Exception as e:
logger.error(f"Error inesperado en process_chat_input: {str(e)}", exc_info=True)
error_messages = {
'en': "Unexpected error. Please try again.",
'es': "Error inesperado. Intente nuevamente.",
'pt': "Erro inesperado. Tente novamente.",
'fr': "Erreur inattendue. Réessayez."
}
yield error_messages.get(self.current_lang, "Processing error")
return