Spaces:
Sleeping
Sleeping
File size: 10,518 Bytes
f810b2f |
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 |
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,
)
|