mirror of
https://github.com/marcogll/talia_bot.git
synced 2026-01-13 13:25:19 +00:00
feat: Audit, repair, and stabilize bot architecture
This commit addresses critical issues in the Talia Bot system, including fixing missing admin flows, correcting agenda privacy logic, implementing voice message transcription, and enforcing RAG guardrails. Key changes include: - Modified the onboarding module to dynamically generate admin menus from available JSON flows, making all admin functions accessible. - Updated the agenda module to correctly use separate work and personal Google Calendar IDs, ensuring privacy and accurate availability. - Implemented audio transcription using the OpenAI Whisper API, replacing placeholder logic and enabling multimodal interaction. - Reworked the sales RAG module to prevent it from generating generic responses when it lacks sufficient context. Additionally, this commit introduces comprehensive documentation as requested: - `AGENTS.md`: Defines the roles and responsibilities of each system agent. - `Agent_skills.md`: Details the technical capabilities and business rules for each agent. - `plan_de_pruebas.md`: Provides a step-by-step test plan to verify the fixes. - `reparacion_vs_refactor.md`: Outlines the immediate repairs performed and proposes a strategic, incremental plan for long-term architectural improvements.
This commit is contained in:
@@ -1,7 +1,8 @@
|
||||
{
|
||||
"id": "admin_idea_capture",
|
||||
"role": "admin",
|
||||
"trigger_button": "💡 Capturar Idea",
|
||||
"name": "💡 Capturar Idea",
|
||||
"trigger_button": "capture_idea",
|
||||
"steps": [
|
||||
{
|
||||
"step_id": 0,
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
{
|
||||
"id": "admin_print_file",
|
||||
"role": "admin",
|
||||
"trigger_button": "🖨️ Imprimir",
|
||||
"name": "🖨️ Imprimir Archivo",
|
||||
"trigger_button": "print_file",
|
||||
"steps": [
|
||||
{
|
||||
"step_id": 0,
|
||||
|
||||
@@ -33,6 +33,7 @@ from talia_bot.modules.vikunja import vikunja_conv_handler, get_projects_list, g
|
||||
from talia_bot.modules.printer import send_file_to_printer, check_print_status
|
||||
from talia_bot.db import setup_database
|
||||
from talia_bot.modules.flow_engine import FlowEngine
|
||||
from talia_bot.modules.llm_engine import transcribe_audio
|
||||
|
||||
from talia_bot.scheduler import schedule_daily_summary
|
||||
|
||||
@@ -101,7 +102,7 @@ async def start(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
||||
logger.info(f"Usuario {chat_id} inició conversación con el rol: {user_role}")
|
||||
|
||||
# Obtenemos el texto y los botones de bienvenida desde el módulo de onboarding
|
||||
response_text, reply_markup = onboarding_handle_start(user_role)
|
||||
response_text, reply_markup = onboarding_handle_start(user_role, flow_engine)
|
||||
|
||||
# Respondemos al usuario
|
||||
await update.message.reply_text(response_text, reply_markup=reply_markup)
|
||||
@@ -120,9 +121,25 @@ async def text_and_voice_handler(update: Update, context: ContextTypes.DEFAULT_T
|
||||
|
||||
user_response = update.message.text
|
||||
if update.message.voice:
|
||||
# Here you would add the logic to transcribe the voice message
|
||||
# For now, we'll just use a placeholder
|
||||
user_response = "Voice message received (transcription not implemented yet)."
|
||||
voice = update.message.voice
|
||||
temp_dir = 'temp_files'
|
||||
os.makedirs(temp_dir, exist_ok=True)
|
||||
file_path = os.path.join(temp_dir, f"{voice.file_id}.ogg")
|
||||
|
||||
try:
|
||||
voice_file = await context.bot.get_file(voice.file_id)
|
||||
await voice_file.download_to_drive(file_path)
|
||||
logger.info(f"Voice message saved to {file_path}")
|
||||
|
||||
user_response = transcribe_audio(file_path)
|
||||
logger.info(f"Transcription result: '{user_response}'")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error during voice transcription: {e}")
|
||||
user_response = "Error al procesar el mensaje de voz."
|
||||
finally:
|
||||
if os.path.exists(file_path):
|
||||
os.remove(file_path)
|
||||
|
||||
result = flow_engine.handle_response(user_id, user_response)
|
||||
|
||||
|
||||
@@ -5,12 +5,14 @@
|
||||
import datetime
|
||||
import logging
|
||||
from talia_bot.modules.calendar import get_events
|
||||
from talia_bot.config import WORK_GOOGLE_CALENDAR_ID, PERSONAL_GOOGLE_CALENDAR_ID
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
async def get_agenda():
|
||||
"""
|
||||
Obtiene y muestra la agenda del usuario para el día actual desde Google Calendar.
|
||||
Diferencia entre eventos de trabajo (visibles) y personales (bloqueos).
|
||||
"""
|
||||
try:
|
||||
logger.info("Obteniendo agenda...")
|
||||
@@ -18,24 +20,34 @@ async def get_agenda():
|
||||
start_of_day = now.replace(hour=0, minute=0, second=0, microsecond=0)
|
||||
end_of_day = start_of_day + datetime.timedelta(days=1)
|
||||
|
||||
logger.info(f"Buscando eventos desde {start_of_day} hasta {end_of_day}")
|
||||
events = get_events(start_of_day, end_of_day)
|
||||
logger.info(f"Buscando eventos de trabajo en {WORK_GOOGLE_CALENDAR_ID} y personales en {PERSONAL_GOOGLE_CALENDAR_ID}")
|
||||
|
||||
if not events:
|
||||
logger.info("No se encontraron eventos.")
|
||||
return "📅 *Agenda para Hoy*\n\nNo tienes eventos programados para hoy."
|
||||
# Obtener eventos de trabajo (para mostrar)
|
||||
work_events = get_events(start_of_day, end_of_day, calendar_id=WORK_GOOGLE_CALENDAR_ID)
|
||||
|
||||
# Obtener eventos personales (para comprobar bloqueos, no se muestran)
|
||||
personal_events = get_events(start_of_day, end_of_day, calendar_id=PERSONAL_GOOGLE_CALENDAR_ID)
|
||||
|
||||
if not work_events and not personal_events:
|
||||
logger.info("No se encontraron eventos de ningún tipo.")
|
||||
return "📅 *Agenda para Hoy*\n\nTotalmente despejado. No hay eventos de trabajo ni personales."
|
||||
|
||||
agenda_text = "📅 *Agenda para Hoy*\n\n"
|
||||
for event in events:
|
||||
start = event["start"].get("dateTime", event["start"].get("date"))
|
||||
# Formatear la hora si es posible
|
||||
if "T" in start:
|
||||
time_str = start.split("T")[1][:5]
|
||||
else:
|
||||
time_str = "Todo el día"
|
||||
|
||||
summary = event.get("summary", "(Sin título)")
|
||||
agenda_text += f"• *{time_str}* - {summary}\n"
|
||||
if not work_events:
|
||||
agenda_text += "No tienes eventos de trabajo programados para hoy.\n"
|
||||
else:
|
||||
for event in work_events:
|
||||
start = event["start"].get("dateTime", event["start"].get("date"))
|
||||
if "T" in start:
|
||||
time_str = start.split("T")[1][:5]
|
||||
else:
|
||||
time_str = "Todo el día"
|
||||
|
||||
summary = event.get("summary", "(Sin título)")
|
||||
agenda_text += f"• *{time_str}* - {summary}\n"
|
||||
|
||||
if personal_events:
|
||||
agenda_text += "\n🔒 Tienes tiempo personal bloqueado."
|
||||
|
||||
logger.info("Agenda obtenida con éxito.")
|
||||
return agenda_text
|
||||
|
||||
@@ -32,3 +32,25 @@ def get_smart_response(prompt):
|
||||
except Exception as e:
|
||||
# Si algo sale mal, devolvemos el error
|
||||
return f"Ocurrió un error al comunicarse con OpenAI: {e}"
|
||||
|
||||
def transcribe_audio(audio_file_path):
|
||||
"""
|
||||
Transcribes an audio file using OpenAI's Whisper model.
|
||||
|
||||
Parameters:
|
||||
- audio_file_path: The path to the audio file.
|
||||
"""
|
||||
if not OPENAI_API_KEY:
|
||||
return "Error: OPENAI_API_KEY is not configured."
|
||||
|
||||
try:
|
||||
client = openai.OpenAI(api_key=OPENAI_API_KEY)
|
||||
|
||||
with open(audio_file_path, "rb") as audio_file:
|
||||
transcript = client.audio.transcriptions.create(
|
||||
model="whisper-1",
|
||||
file=audio_file
|
||||
)
|
||||
return transcript.text
|
||||
except Exception as e:
|
||||
return f"Error during audio transcription: {e}"
|
||||
|
||||
@@ -4,14 +4,22 @@
|
||||
|
||||
from telegram import InlineKeyboardButton, InlineKeyboardMarkup
|
||||
|
||||
def get_admin_menu():
|
||||
def get_admin_menu(flow_engine):
|
||||
"""Crea el menú de botones principal para los Administradores."""
|
||||
keyboard = [
|
||||
[InlineKeyboardButton("👑 Revisar Pendientes", callback_data='view_pending')],
|
||||
[InlineKeyboardButton("📅 Agenda", callback_data='view_agenda')],
|
||||
[InlineKeyboardButton(" NFC", callback_data='start_create_tag')],
|
||||
[InlineKeyboardButton("▶️ Más opciones", callback_data='admin_menu')],
|
||||
]
|
||||
|
||||
# Dynamic buttons from flows
|
||||
if flow_engine:
|
||||
for flow in flow_engine.flows:
|
||||
if flow.get("role") == "admin" and "trigger_button" in flow and "name" in flow:
|
||||
button = InlineKeyboardButton(flow["name"], callback_data=flow["trigger_button"])
|
||||
keyboard.append([button])
|
||||
|
||||
keyboard.append([InlineKeyboardButton("▶️ Más opciones", callback_data='admin_menu')])
|
||||
|
||||
return InlineKeyboardMarkup(keyboard)
|
||||
|
||||
def get_admin_secondary_menu():
|
||||
@@ -41,14 +49,14 @@ def get_client_menu():
|
||||
]
|
||||
return InlineKeyboardMarkup(keyboard)
|
||||
|
||||
def handle_start(user_role):
|
||||
def handle_start(user_role, flow_engine=None):
|
||||
"""
|
||||
Decide qué mensaje y qué menú mostrar según el rol del usuario.
|
||||
"""
|
||||
welcome_message = "Hola, soy Talía. ¿En qué puedo ayudarte hoy?"
|
||||
|
||||
if user_role == "admin":
|
||||
menu = get_admin_menu()
|
||||
menu = get_admin_menu(flow_engine)
|
||||
elif user_role == "crew":
|
||||
menu = get_crew_menu()
|
||||
else:
|
||||
|
||||
@@ -41,19 +41,19 @@ def generate_sales_pitch(user_query, collected_data):
|
||||
relevant_services = find_relevant_services(user_query, services)
|
||||
|
||||
if not relevant_services:
|
||||
# Fallback to all services if no specific keywords match
|
||||
context_str = "Aquí hay una descripción general de nuestros servicios:\n"
|
||||
for service in services:
|
||||
context_str += f"- **{service['service_name']}**: {service['description']}\n"
|
||||
else:
|
||||
context_str = "Según tus necesidades, aquí tienes algunos de nuestros servicios y ejemplos de lo que podemos hacer:\n"
|
||||
for service in relevant_services:
|
||||
context_str += f"\n**Servicio:** {service['service_name']}\n"
|
||||
context_str += f"*Descripción:* {service['description']}\n"
|
||||
if "work_examples" in service:
|
||||
context_str += "*Ejemplos de trabajo:*\n"
|
||||
for example in service["work_examples"]:
|
||||
context_str += f" - {example}\n"
|
||||
logger.warning(f"No se encontraron servicios relevantes para la consulta: '{user_query}'. No se generará respuesta.")
|
||||
return ("Gracias por tu interés. Sin embargo, con la información proporcionada no he podido identificar "
|
||||
"servicios específicos que se ajusten a tu necesidad. ¿Podrías describir tu proyecto con otras palabras "
|
||||
"o dar más detalles sobre lo que buscas?")
|
||||
|
||||
context_str = "Según tus necesidades, aquí tienes algunos de nuestros servicios y ejemplos de lo que podemos hacer:\n"
|
||||
for service in relevant_services:
|
||||
context_str += f"\n**Servicio:** {service['service_name']}\n"
|
||||
context_str += f"*Descripción:* {service['description']}\n"
|
||||
if "work_examples" in service:
|
||||
context_str += "*Ejemplos de trabajo:*\n"
|
||||
for example in service["work_examples"]:
|
||||
context_str += f" - {example}\n"
|
||||
|
||||
prompt = (
|
||||
f"Eres Talía, una asistente de ventas experta y amigable. Un cliente potencial llamado "
|
||||
|
||||
Reference in New Issue
Block a user