File size: 5,290 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
from typing import TypedDict, Annotated, List, Dict, Any
from langgraph.graph.message import add_messages
from langchain_core.messages import AnyMessage, SystemMessage
from langgraph.prebuilt import ToolNode
from langgraph.graph import START, StateGraph
from langgraph.prebuilt import tools_condition
from langchain_openai import ChatOpenAI
from langchain.tools import Tool

from utils.logger import log_info, log_warn, log_error, log_debug

class RestaurantAgent:
    def __init__(self, llm: ChatOpenAI, restaurant_name: str, tools: List[Tool]):
        """
        Inicializa el agente del restaurante con LangGraph.
        
        Args:
            llm: Modelo de lenguaje a utilizar
            restaurant_name: Nombre del restaurante
            tools: Lista de herramientas para el agente
        """
        self.restaurant_name = restaurant_name
        self.tools = tools
        
        # Prompt para el asistente
        self.system_prompt = f"""
                Eres un camarero virtual profesional de {restaurant_name}, atendiendo la mesa 1. Combinas la calidez y simpatía gaditana con un servicio excelente y eficiente.

                ## Tu personalidad
                - Profesional pero cercano, con el encanto natural de Cádiz
                - Confiable, simpático y resolutivo
                - Transmites seguridad en cada recomendación
                - Hablas con naturalidad, como si fueras un camarero experimentado

                ## Comunicación (optimizada para TTS)
                - Frases naturales, claras y conversacionales
                - Tono directo pero amable, sin rodeos innecesarios
                - Evita símbolos especiales, comillas o emojis
                - Respuestas concisas que fluyan bien al ser leídas en voz alta

                ## Protocolo de servicio OBLIGATORIO
                1. **SIEMPRE verifica** la disponibilidad de platos en el menú antes de confirmar pedidos
                2. **NUNCA recomiendes** productos sin consultarlos primero en la carta
                3. **Informa inmediatamente** si algo no está disponible y ofrece alternativas
                4. **Confirma cada pedido** antes de enviarlo a cocina
                5. **Despídete cordialmente** tras completar el servicio

                ## Manejo de consultas del menú
                - Cuando pregunten por opciones disponibles: proporciona un resumen claro y natural
                - Para platos específicos: verifica existencia, precio e ingredientes principales
                - Si desconoces algo: sé transparente y consulta la información necesaria
                - Presenta las opciones de forma atractiva pero honesta

                ## Gestión de pedidos
                - Confirma cada plato solicitado existe en el menú
                - Repite el pedido completo antes de enviarlo
                - Informa el tiempo estimado si es relevante
                - Mantén un registro mental del estado del pedido

                Recuerda: tu objetivo es brindar una experiencia gastronómica excepcional combinando profesionalidad, eficiencia y ese toque especial gaditano que hace sentir como en casa.
                """

        # Configurar el LLM con las herramientas
        self.llm_with_tools = llm.bind_tools(tools=tools)
        
        # Construir el grafo
        self.graph = self._build_graph()

    def _build_graph(self) -> StateGraph:
        """Construye el grafo de estados del agente."""
        
        # Definición del tipo de estado para nuestro grafo
        class AgentState(TypedDict):
            """Tipo para el estado del agente de LangGraph."""
            messages: Annotated[list[AnyMessage], add_messages]

        # Nodo para el asistente que invoca al LLM
        def assistant(state: AgentState):
            """Procesa los mensajes usando el LLM y devuelve una respuesta."""
            log_info("Assistant processing messages")
            
            # Añadir el mensaje del sistema al principio de la lista de mensajes
            messages = [SystemMessage(content=self.system_prompt)] + state["messages"]
            
            return {
                "messages": [self.llm_with_tools.invoke(messages)],
            }

        # Crear el grafo con una estructura mucho más simple
        builder = StateGraph(AgentState)

        # Definir nodos: el asistente y el nodo para herramientas
        builder.add_node("assistant", assistant)
        builder.add_node("tools", ToolNode(self.tools))

        # Definir bordes con enrutamiento condicional automático
        builder.add_edge(START, "assistant")
        builder.add_conditional_edges(
            "assistant",
            # Si el último mensaje requiere una herramienta, enrutar a "tools"
            # De lo contrario, terminar el flujo y devolver la respuesta
            tools_condition,
        )
        builder.add_edge("tools", "assistant")

        # Compilar y retornar el grafo
        return builder.compile()

    def invoke(self, messages: List[AnyMessage]) -> Dict[str, Any]:
        """
        Procesa la consulta del usuario y genera una respuesta usando el grafo LangGraph.
        """
        log_info(f"Processing query: {messages}")

        return self.graph.invoke({"messages": messages})