WAIter / tools.py
ItzRoBeerT's picture
init project
f810b2f
raw
history blame
10.5 kB
from langchain.tools import Tool
from langchain_core.vectorstores import VectorStoreRetriever
from typing import Optional
from utils.logger import log_info, log_warn, log_error, log_debug
import json
from langchain_core.messages import HumanMessage, SystemMessage
from langchain_openai import ChatOpenAI
from utils.classes import Order
from supabase_client import SupabaseOrderManager
import asyncio
try:
supabase = SupabaseOrderManager()
except Exception as e:
log_error(f"Error al inicializar el cliente de Supabase: {e}")
supabase = None
def create_menu_info_tool(retriever: VectorStoreRetriever) -> Tool:
"""
Crea una herramienta para extraer información relevante del menú del restaurante.
Args:
retriever: Un retriever configurado para buscar en la base de conocimiento del menú
Returns:
Una herramienta de LangChain para consultar información del menú
"""
def extract_text(query: str) -> str:
"""Extrae texto relevante del menú basado en la consulta."""
results = retriever.invoke(query)
log_info("ENTRADA DE EXTRACCIÓN DE TEXTO")
result_texts = []
if results:
log_info("\n=== Fragmentos de documento utilizados para la respuesta ===")
for i, result in enumerate(results):
log_info(f"Fragmento {i+1}: {result.page_content[:100]}...")
# Comprobar si tiene score (algunos retrievers no incluyen este atributo)
if hasattr(result, 'score'):
log_info(f"Score: {result.score}")
result_texts.append(result.page_content)
log_info("=========================================================\n")
# Unir los resultados relevantes
return "\n\n".join(result_texts)
else:
return "Lo siento, no tengo información sobre eso."
return Tool(
name="guest_info_tool",
description="""Herramienta para consultar información detallada del menú del restaurante.
Úsala cuando necesites:
- Buscar platos específicos y verificar su disponibilidad
- Consultar precios exactos de productos
- Obtener información sobre ingredientes, alérgenos o composición de platos
- Explorar secciones del menú (entrantes, principales, postres, bebidas, etc.)
- Verificar la existencia de productos antes de recomendarlos
- Responder preguntas específicas sobre la carta del restaurante
Esta herramienta accede al contenido completo del menú para proporcionar información precisa y actualizada.""",
func=extract_text,
)
def create_send_to_kitchen_tool(llm: ChatOpenAI) -> Tool:
"""
Crea una herramienta para enviar pedidos a la cocina.
Args:
llm: Un modelo de lenguaje para analizar la conversación
Returns:
Una herramienta de LangChain para enviar pedidos a la cocina
"""
def extract_order_from_summary(conversation_summary: str) -> Order:
"""
Usa un LLM para extraer detalles del pedido a partir de un resumen de la conversación.
Args:
conversation_summary: Resumen de la conversación entre cliente y camarero
Returns:
Objeto Order con los detalles del pedido extraído
"""
# Crear mensaje para el LLM
messages = [
SystemMessage(content="""
Eres un asistente especializado en extraer información de pedidos de restaurante.
Analiza el siguiente resumen de conversación entre un cliente y un camarero.
Extrae SOLO los elementos del pedido (platos, bebidas, etc.), cantidades, y cualquier instrucción especial.
Devuelve los resultados en formato JSON entre las etiquetas <order> </order> estrictamente con esta estructura:
{
"table_number": número_de_mesa (entero o "desconocida" si no se especifica),
"items": [
{
"name": "nombre_del_plato",
"quantity": cantidad (entero, por defecto 1),
"variations": "variaciones o personalizaciones"
},
...
],
"special_instructions": "instrucciones especiales generales"
}
No incluyas ninguna otra información o explicación, SOLO el JSON entre las etiquetas.
"""),
HumanMessage(content=f"Resumen de la conversación: {conversation_summary}")
]
# Invocar el LLM para obtener el análisis del pedido
response = llm.invoke(messages)
response_text = response.content
# Extraer el JSON de la respuesta usando las etiquetas <order></order>
try:
# Buscar contenido entre etiquetas <order> y </order>
import re
order_pattern = re.compile(r'<order>(.*?)</order>', re.DOTALL)
order_match = order_pattern.search(response_text)
if order_match:
# Extraer el contenido JSON de las etiquetas
json_str = order_match.group(1).strip()
order_data = json.loads(json_str)
# Crear objeto Order con los datos extraídos
return Order(
items=order_data.get("items", []),
special_instructions=order_data.get("special_instructions", ""),
table_number=order_data.get("table_number", "desconocida")
)
else:
# Si no hay etiquetas, reportar error
log_error("No se encontraron etiquetas <order> en la respuesta del LLM")
# Devolver un objeto Order vacío con un flag de error
empty_order = Order(table_number="desconocida")
empty_order.error = "NO_TAGS_FOUND"
return empty_order
except json.JSONDecodeError as e:
log_error(f"Error al parsear JSON de la respuesta del LLM: {e}")
log_debug(f"Respuesta problemática: {response_text}")
empty_order = Order(table_number="desconocida")
empty_order.error = "JSON_PARSE_ERROR"
return empty_order
except Exception as e:
log_error(f"Error inesperado al procesar la respuesta: {e}")
log_debug(f"Respuesta completa: {response_text}")
empty_order = Order(table_number="desconocida")
empty_order.error = "UNKNOWN_ERROR"
return empty_order
def send_to_kitchen(conversation_summary: str) -> str:
"""
Procesa el resumen de la conversación para extraer el pedido y enviarlo a la cocina.
Args:
conversation_summary: Resumen de la conversación cliente-camarero
Returns:
Mensaje de confirmación
"""
try:
log_info(f"Procesando resumen para enviar pedido a cocina...")
log_debug(f"Resumen recibido: {conversation_summary}")
# Extraer el pedido a partir del resumen
order = extract_order_from_summary(conversation_summary)
# Verificar si hay un error en el procesamiento
if hasattr(order, 'error') and order.error:
if order.error == "NO_TAGS_FOUND":
log_error("No se encontraron las etiquetas <order> en la respuesta del LLM")
return "Lo siento, ha ocurrido un problema al procesar su pedido. Por favor, inténtelo de nuevo."
elif order.error == "JSON_PARSE_ERROR":
log_error("Error al analizar el JSON en las etiquetas <order>")
return "Ha ocurrido un error técnico al procesar su pedido. ¿Podría repetirlo de otra forma?"
else:
log_error(f"Error desconocido: {order.error}")
return "Lo siento, algo salió mal al procesar su pedido. Por favor, inténtelo de nuevo."
# Verificar si hay elementos en el pedido
if not order.items:
log_warn("No se identificaron artículos en el pedido")
return "No se pudo identificar ningún artículo en el pedido. ¿Podría repetir su pedido, por favor?"
# Simular envío a la cocina
order_dict = order.to_dict()
log_info(f"ENVIANDO PEDIDO A COCINA: {json.dumps(order_dict, indent=2, ensure_ascii=False)}")
# Aquí iría la integración real con el sistema de la cocina
# Por ejemplo, enviar a una API, base de datos, etc.
async def async_send_and_get_result(order):
return await supabase.send_order(order)
res = asyncio.run(async_send_and_get_result(order))
if res.get("success"):
log_info(f"Pedido enviado correctamente a la cocina: {res['order_id']}")
return f"Su pedido ha sido enviado a la cocina. ID de pedido: {res['order_id']}"
else:
log_error(f"Error al enviar el pedido a la cocina: {res.get('error', 'Desconocido')}")
return "Lo siento, hubo un problema al enviar su pedido a la cocina. ¿Podría intentarlo de nuevo?"
except Exception as e:
log_error(f"Error al procesar pedido: {e}")
log_debug(f"Error detallado: {str(e)}")
import traceback
log_debug(traceback.format_exc())
return "Lo siento, hubo un problema al procesar su pedido. ¿Podría intentarlo de nuevo?"
# Retornar la herramienta configurada con la función send_to_kitchen
return Tool(
name="send_to_kitchen_tool",
description="""
Envía el pedido completo a la cocina. Usa esta herramienta SOLAMENTE cuando el cliente haya terminado de hacer su pedido
completo y esté listo para enviarlo.
Esta herramienta espera recibir un RESUMEN de la conversación que describe los elementos del pedido.
No envíes la conversación completa, solo un resumen claro de lo que el cliente ha pedido, la mesa,
y cualquier instrucción especial relevante.
""",
func=send_to_kitchen,
)