
    	hA                        U d dl Z d dlZd dlmZ d dlmZmZ d dlmZ d dl	Z	d dl
Z
d dlmZ d dlmZmZ d dlmZmZ d dlmZ d d	lmZ d d
lmZmZmZ d dlZd dlmZ  ee      j?                         j@                  Z! ee!dz  d        e jD                  d      Z# e jD                  d      Z$ e%d        e%de#         e%de$rdnd        da&edz  e'd<   i Z(ee)ef   e'd<    G d de      Z* edd      Z+ eddg      Z, ed !      Z- e jD                  d      Z$e$s e.d"       ee$d#d$%      Z/ ej`                  d& ed'(      d) ed*(      g      Z1d+e)d,efd-Z2e-jg                  d.      d/e*fd0       Z4e,jk                  e-       e+jk                  e,       d1 Z6d2 Z7e8d3k(  r	  ejr                   e7              yy# e:$ r  e%d4       Y yw xY w)5    N)Path)DictAny)load_dotenv)
ChatOpenAI)ChatPromptTemplateMessagesPlaceholder)AgentExecutorcreate_openai_functions_agent)ConversationBufferMemory)MultiServerMCPClient)FastAPIHTTPException	APIRouter)	BaseModelz.envT)overrideMCP_HTTP_URLOPENAI_API_KEYu#   🔹 Variables de entorno cargadas:z  MCP_HTTP_URL: z  OPENAI_API_KEY: CARGADAz
NO CARGADAAGENT_EXECUTORCONVERSATION_MEMORIESc                   "    e Zd ZU eed<   eed<   y)UserMessagewhatsapp_idmensajeN)__name__
__module____qualname__str__annotations__     9/opt/transporte/ClienteMCP/client_langchain_transporte.pyr   r   )   s    Lr"   r   z$Asistente de Transporte LHIA WebhookzQEndpoint para recibir mensajes de WhatsApp e interactuar con el Agente LangChain.)titledescriptionz/cliente-mcpzAPI Cliente MCP)prefixtagsz/webhook)r&   u3   OPENAI_API_KEY no está definido en el archivo .envzgpt-4og?)openai_api_keymodeltemperature)systemu$  
    Eres el **Asistente de Transporte Rural LHIA**, un experto en la gestión de transporte de carga agropecuaria.
    Tu objetivo es ayudar al usuario a registrarse como transportista o crear una orden de servicio.

    [INSTRUCCIÓN CLAVE DE ENTRADA]
    El mensaje de entrada, {input}, comenzará con el ID de WhatsApp del usuario envuelto en corchetes, así: **[593963709752] Mensaje del usuario**. 
    **SIEMPRE** debes extraer el número de WhatsApp de estos corchetes para usarlo en las llamadas a herramientas.
    
    [REGLAS CLAVE DE CONVERSIÓN DE PESO Y CÁLCULO]
    **OBLIGATORIO:** Cualquier cantidad de peso o carga que obtengas del usuario debe ser convertida a su valor **decimal equivalente en toneladas (FLOAT)**.
    - Ejemplo: "2.5 toneladas" -> 2.5
    - Ejemplo: "media tonelada" -> 0.5
    - Ejemplo: "4000 libras" -> 1.814 (porque 4000 lb / 2204.6 lb/ton ≈ 1.814)
    - Ejemplo: "500 kilogramos" -> 0.5
    
    **INFERENCIA DE PESO (CLAVE PARA ÓRDENES):** Si el usuario NO proporciona un peso explícito (como "2 toneladas") en la descripción de la carga (ej: "3 vacas"), debes utilizar tu conocimiento general para estimar y calcular el peso total de la `Carga` en toneladas antes de llamar a `register_new_order`.
    - Ejemplo de cálculo interno: Si la carga es "3 vacas", asume un peso promedio de 0.5 toneladas por vaca. El valor de `peso_aproximado` es **1.5**.
    - **Prioridad:** Si el usuario da un peso explícito ("3 vacas y 1.9 toneladas"), usa el valor explícito (1.9).
    
    [REGLAS DE OPERACIÓN CONVERSACIONAL]
    
    0.  **Intención de Campaña (Máxima Prioridad):**
        * **Detección:** Si el usuario usa frases con la palabra clave **"campaña"** o **"campañas"**, ignora todas las demás intenciones.
        * **Acción:** Responde amablemente con un mensaje de información, por ejemplo: "**¡Claro! Las campañas de LHIA son iniciativas que lanzamos periódicamente para fomentar el transporte eficiente de productos agropecuarios. Actualmente no tenemos una campaña activa, pero te avisaremos cuando lancemos la siguiente. ¿En qué otra cosa relacionada con transporte de carga o registro de transportistas puedo ayudarte?**" **NO uses herramientas en este caso.**

    1.  **Intención de Registro (Transportista - Prioridad Alta):**
        * **Detección:** Si el usuario usa frases como "quiero registrarme", "quiero ser transportista", **"soy transportista"** o similares, asume esta intención.
        * **Datos a Recolectar (Secuencial):** Necesitas: **Nombre completo**, **Placa del vehículo**, y **Carga Máxima** (siempre pídelo en toneladas, pero acepta la conversión).
        * **Fijación Automática de Campos:**
            * El campo **`whatsapp`** (de Transportista) es el ID extraído de los corchetes.
        * **Acción Tool:** Solo usa la herramienta `register_new_transporter` cuando tengas **TODOS** los datos del transportista y el valor de la Carga Máxima esté **convertido a decimal (toneladas)**.

    2.  **Intención de Orden (Cliente - Por Defecto):**
        * **Detección:** Si el mensaje NO es de campaña ni de registro, asume que es un **Cliente** que busca transporte.
        * **PRIORIDAD CONVERSACIONAL (Mensaje Inicial):** Si el mensaje del usuario NO contiene palabras clave de registro (soy transportista, quiero registrarme) ni de campaña, y se detecta que la conversación es nueva (o el agente no ha solicitado un dato específico), **DEBES EMPEZAR** preguntando directamente: "**¿Qué necesitas transportar? Por favor, describe la carga con detalle para estimar el vehículo adecuado.**" NO uses saludos introductorios.
        * **PRIORIDAD CONVERSACIONAL (Flujo No Secuencial):** Si el usuario en un solo mensaje provee datos de **MÁS DE UN CAMPO** (ej: Nombre, Carga, y Origen), **DEBES CAPTURAR TODOS ESOS CAMPOS SIMULTÁNEAMENTE** y pasar a preguntar solo por la información que falte. **NO** preguntes por información que el usuario ya ha proporcionado.
        * **Datos a Recolectar (Secuencial CONVERSACIONAL - 4 pasos):** Necesitas:
            1.  *Nombre Cliente*.
            2.  *Carga* (la descripción literal, ej: "3 vacas" o "20 sacos de papas").
            3.  *Origen* (con referencia).
            4.  *Destino*.
        * **Fijación Automática de Campos:**
            * El campo **`whatsappCliente`** (de Orden) es el ID extraído de los corchetes.
            * Los campos `whatsappTransportistaAsignado`, `precio` y `estado` son internos y NO deben ser preguntados.
        * **Regla de Origen con Referencia:** Al pedir el **Origen**, PIDE UNA REFERENCIA CLARA y precisa. (ej: "¿Cuál es el punto de recogida? Por favor, indica una referencia cercana, como *junto a la iglesia*, *en la intersección de*, *en la entrada de la finca*, o un punto conocido."). **Detecta y fomenta el uso de estas referencias clave.**
        * **Carga y Peso (Doble Campo - INFERENCIA DE PESO):**
            * El campo **`carga`** de la herramienta debe ser la descripción literal de la carga (ej: "3 vacas").
            * El campo **`peso_aproximado`** de la herramienta **DEBE SER CALCULADO INTERNAMENTE** por el LLM en base a la descripción de la `carga` y ser el **valor decimal convertido a toneladas** (ej: 1.5, si infieres 3 vacas). Este campo es obligatorio si hay carga y DEBE ser un float. **NO debes preguntarle al cliente por este valor.**
        * **Acción Tool:** Solo usa la herramienta `register_new_order` cuando tengas **TODOS** los 4 datos clave (*Nombre Cliente*, *Carga*, *Origen*, *Destino*) y el **Peso Aproximado (float)** haya sido calculado internamente.

    [REGLAS DE LOGS Y NOTIFICACIÓN]
    - **LOGS (agent_scratchpad):** **SIEMPRE** antes de invocar una herramienta, debes generar un log detallado en tu `agent_scratchpad` mostrando el JSON o los parámetros exactos que vas a enviar al servidor MCP. Para órdenes, verifica explícitamente que los campos `carga` (string descriptivo) y `peso_aproximado` (float decimal) estén correctamente llenos.
    - **ETIQUETA Y DATOS:** Si usas la herramienta `register_new_order` con éxito, tu respuesta FINAL debe comenzar SIEMPRE con la etiqueta `[ORDEN_CREADA_EXITOSAMENTE]` seguida INMEDIATAMENTE de un salto de línea y luego el **JSON COMPLETO** (sin envolver en bloques de código) que devolvió la herramienta. El resto del mensaje conversacional para el usuario va DESPUÉS de ese JSON.
    history)variable_name)humanz{input}agent_scratchpadr   returnc                 h    | t         vr"t        d|         t        dd      t         | <   t         |    S )uG   Obtiene la memoria de conversación por 'whatsapp_id' o crea una nueva.u   ✨ Creando nueva memoria para r,   T)
memory_keyreturn_messages)r   printr   )r   s    r#   get_or_create_memoryr5      s>    ///}=>-E  .
k* !--r"   z/messageuser_messagec                   K   d}t         t        dd      | j                  }| j                  }t	        |      }d| d| }	 |t         _        t         j                  d|i       d{   }|d	   }||v r	 |j                  |d
      d
   j                         }t        j                  d|t        j                        }	|	r2|	j                  d      }
|j                  |
d      j                         }n+t        d       |j                  |d      j                         }n|}d||dS 7 # t        j                   $ r8}t        d|        |j                  |d      j                         }Y d}~Nd}~wt"        $ r8}t        d|        |j                  |d      j                         }Y d}~d}~ww xY w# t"        $ r%}d| }t        |       t        dd|       d}~ww xY ww)zM
    Endpoint para recibir mensajes entrantes de un usuario de WhatsApp.
    z[ORDEN_CREADA_EXITOSAMENTE]Ni  u#   El Asistente no está inicializado.)status_codedetail[z] inputoutput   z(\{.*\})r    uu   ⚠️ Advertencia: Etiqueta de orden encontrada, pero JSON no pudo ser extraído. Enviando solo la respuesta limpia.u6   ❌ Error de JSON al parsear la respuesta del agente: u<   ❌ Error inesperado al procesar la notificación de orden: success)statusr   mensaje_respuestau1   ⚠️ Ocurrió un error al procesar el mensaje: i  z6Error interno del asistente al procesar la solicitud: )r   r   r   r   r5   memoryainvokesplitstripresearchDOTALLgroupreplacer4   jsonJSONDecodeError	Exception)r6   NOTIFICATION_TAG
user_inputr   rB   	llm_inputresultassistant_responsejson_and_response
json_match	json_textclean_responsee	error_msgs                 r#   receive_messagerY      s     54YZZ%%J**K "+.F K=:,/I6
 &%--w	.BCC#H- 11Z$6$<$<=Mq$QRS$T$Z$Z$\!  YY{4EryyQ
 * 0 0 3I &7%>%>y"%M%S%S%UN   R  S%7%?%?@PRT%U%[%[%]N 0N  &!/
 	
M D4 '' ZNqcRS!3!;!;<Lb!Q!W!W!Y ZTUVTWXY!3!;!;<Lb!Q!W!W!YZ  
GsK	iKA3O
 	
	
sy   AG(%F7 )D(*F7 8B'D* F7 'G((F7 *F4=.E0+F7 0F4<.F/*F7 /F44F7 7	G%  G  G%%G(c                    K   t        dt         d       	 t        dt        ddi      } | j                          d{   }t        d|D cg c]  }|j                   c}        t        t        |t        
      }t        ||dd      a
t        d       t        d       y7 fc c}w # t
        $ r}t        d|        t        d	        d}~ww xY ww)zEInicializa el agente y las herramientas antes de iniciar el servidor.u(   🔌 Conectando al MCP de Transporte en z...
transportestreamable_http)url	transportNu   ✅ Herramientas detectadas: u"   ❌ Error al conectar con el MCP: uH   💡 Verifica que el servidor Spring AI MCP esté corriendo y accesible.)llmtoolspromptT)agentr`   rB   verboseuI   
🤖 Asistente de Transporte Rural LHIA listo para recibir mensajes 🚚uM   📢 Servidor escuchando en http://localhost:8003/cliente-mcp/webhook/message)r4   MCP_URLr   	get_toolsnamerM   r   r_   ra   r
   r   )transporte_mcp_clientr`   toolrW   rb   s        r#   setup_agentri      s      
4WIS
AB 4.6
 ! ,5577-U.KTtyy.K-LMN *E #	N 

VW	
YZ+ 8.K 21#67XYsJ   C'B, B%B, B'
 	B, )<C%B, 'B, ,	C5CCCc                     K   t                d{    t        j                  t        ddd      } t        j                  |       }|j                          d{    y7 O7 w)u=   Función principal para inicializar y correr el servidor web.Nz0.0.0.0iC  info)hostport	log_level)ri   uvicornConfigappServerserve)configservers     r#   mainrv     sQ      - ^^CidfMF^^F#F
,,.  s"   A$A A	A$A"A$"A$__main__u   
👋 Servidor apagado.);osasynciopathlibr   typingr   r   dotenvr   rK   rF   langchain_openair   langchain_core.promptsr   r	   langchain.agentsr
   r   langchain.memoryr   langchain_mcp_adapters.clientr   fastapir   r   r   ro   pydanticr   __file__resolveparentBASE_DIRgetenvrd   
openai_keyr4   r   r    r   r   r   rq   
api_routerwebhook_router
ValueErrorr_   from_messagesra   r5   postrY   include_routerri   rv   r   runKeyboardInterruptr!   r"   r#   <module>r      s/   	      	 ' J I 5 > 5 5  
 >!!#** Hv - "))N
#RYY'(
 + , 	" # 
9EF G (,$ +=? tC!99: ?) 
 
0c 
	
  RYY'(

J
KK
 
*		)	)5	l i0&89s:+ :
@.c ..F . Z I
 I
 !I
`   . )   : "[H	 z*DF   *()*s   )G GG