289 lines
9.2 KiB
Python
289 lines
9.2 KiB
Python
import asyncio
|
|
import os
|
|
from datetime import datetime, timedelta, timezone
|
|
from zoneinfo import ZoneInfo
|
|
|
|
|
|
from fastmcp import Client
|
|
from langchain.tools import tool
|
|
from ..logger import logger
|
|
|
|
from .schemes import ScheduleSchema
|
|
|
|
|
|
TIMEZONE = "America/Bogota"
|
|
MCP_SERVER_HOST = os.getenv("MCP_SERVER_HOST", "localhost")
|
|
MCP_SERVER_PORT = os.getenv("MCP_SERVER_PORT", "3001")
|
|
MCP_SERVER_URL = f"http://{MCP_SERVER_HOST}:{MCP_SERVER_PORT}/mcp"
|
|
_MCP_CLIENT = Client(MCP_SERVER_URL)
|
|
|
|
|
|
def _extract_mcp_content_text(result, fallback: str = "") -> str:
|
|
if not result.content:
|
|
logger.warning("MCP result content is empty")
|
|
return fallback
|
|
if not hasattr(result.content[0], "text"):
|
|
logger.warning("MCP result content[0] has no 'text' attribute")
|
|
return fallback
|
|
return result.content[0].text
|
|
|
|
|
|
def check_mcp_connection(self) -> str:
|
|
"""
|
|
Verifica la conexión con el servidor MCP.
|
|
|
|
Returns:
|
|
str: "connected" si el servidor está disponible, "disconnected" si no hay conexión.
|
|
"""
|
|
|
|
async def call_tool():
|
|
async with _MCP_CLIENT:
|
|
result = await _MCP_CLIENT.ping()
|
|
logger.info(f"MCP connection status: {result}")
|
|
|
|
return True
|
|
|
|
is_connected = asyncio.run(call_tool())
|
|
return "connected" if is_connected else "disconnected"
|
|
|
|
|
|
class NaliiaTools:
|
|
def __init__(self):
|
|
self._session_active = False
|
|
|
|
def get_tools(self):
|
|
|
|
return [
|
|
self.get_current_datetime,
|
|
self.get_tomorrow_date,
|
|
self.register_customer,
|
|
self.schedule_appointment,
|
|
self.find_service_centers,
|
|
self.find_products_and_services,
|
|
self.find_customer_by_identifier,
|
|
]
|
|
|
|
@tool
|
|
def get_tomorrow_date() -> str:
|
|
"""
|
|
Consulta la fecha actual del sistema y calcula la fecha del día de mañana.
|
|
|
|
Returns:
|
|
str: Fecha del día siguiente en formato ISO 8601 (YYYY-MM-DD).
|
|
Ejemplo: "2026-03-10"
|
|
|
|
Notes:
|
|
- No se aceptan fechas en el pasado con respecto a esta fecha.
|
|
- Utiliza la zona horaria de Colombia (America/Bogota, UTC-5).
|
|
"""
|
|
td = timedelta(days=1)
|
|
tomorrow = datetime.now(tz=ZoneInfo(TIMEZONE)).date() + td
|
|
tomorrow_date_formated = tomorrow.isoformat()
|
|
|
|
logger.info(tomorrow_date_formated)
|
|
|
|
return tomorrow_date_formated
|
|
|
|
@tool
|
|
def get_current_datetime() -> str:
|
|
"""
|
|
Consulta la fecha y hora actual del sistema.
|
|
|
|
Returns:
|
|
str: Fecha y hora actual en formato ISO 8601 con zona horaria.
|
|
Ejemplo: "2026-03-09T17:30:00-05:00"
|
|
|
|
Notes:
|
|
- No se aceptan fechas en el pasado con respecto a esta fecha.
|
|
- Utiliza la zona horaria de Colombia (America/Bogota, UTC-5).
|
|
"""
|
|
logger.info("Consultando fecha actual.")
|
|
|
|
today_date_formated = datetime.now(tz=ZoneInfo(TIMEZONE)).isoformat()
|
|
|
|
logger.info(today_date_formated)
|
|
|
|
return today_date_formated
|
|
|
|
@tool
|
|
def register_customer(name: str, cellphone: str, email: str = None) -> bool:
|
|
"""
|
|
Registra un nuevo cliente
|
|
|
|
Args:
|
|
name: Nombre Completo del Cliente.
|
|
cellphone: Numero de Celular o Contacto.
|
|
|
|
Returns:
|
|
list: En caso del cliente fuese creado exitosamente se obtendra una lista con el id del cliente en la base de datos.
|
|
Ejemplo: [1]
|
|
"""
|
|
|
|
identifiers = {"type": "mobile", "code": cellphone}
|
|
|
|
async def call_tool():
|
|
async with _MCP_CLIENT:
|
|
result = await _MCP_CLIENT.call_tool(
|
|
"create_customer", {"name": name, "identifiers": identifiers}
|
|
)
|
|
|
|
logger.info(_extract_mcp_content_text(result))
|
|
|
|
return result
|
|
|
|
result = asyncio.run(call_tool())
|
|
|
|
return _extract_mcp_content_text(result)
|
|
|
|
@tool
|
|
def find_customer_by_identifier(identifier: str) -> str:
|
|
"""
|
|
Busca un cliente por su identificador (teléfono o número de documento).
|
|
|
|
Args:
|
|
identifier: Identificador del cliente (teléfono o número de documento).
|
|
|
|
Returns:
|
|
str: JSON con la información del cliente encontrado.
|
|
Estructura: {"id": int, "name": str, "phone": str, "email": str}
|
|
Ejemplo: '{"id": 1, "name": "Juan Perez", "phone": "573001234567", "email": "juan@example.com"}'
|
|
Retorna "{}" si no se encuentra el cliente.
|
|
"""
|
|
|
|
async def call_tool():
|
|
async with _MCP_CLIENT:
|
|
result = await _MCP_CLIENT.call_tool(
|
|
"find_customer_by_identifier", {"identifier": identifier}
|
|
)
|
|
logger.info(_extract_mcp_content_text(result))
|
|
return result
|
|
|
|
result = asyncio.run(call_tool())
|
|
|
|
return _extract_mcp_content_text(result)
|
|
|
|
@tool
|
|
def find_service_centers():
|
|
"""
|
|
Obtiene la lista de centros de servicio disponibles para agendar citas.
|
|
|
|
Returns:
|
|
str: JSON array con los centros de servicio disponibles.
|
|
Cada elemento contiene:
|
|
- id (int): Identificador único del centro de servicio
|
|
- name (str): Nombre del centro de servicio
|
|
|
|
Example:
|
|
'[{"id": 1, "name": "Campo Amor"}, {"id": 2, "name": "Belen"}]'
|
|
"""
|
|
|
|
async def call_tool():
|
|
async with _MCP_CLIENT:
|
|
result = await _MCP_CLIENT.call_tool("find_service_centers", None)
|
|
logger.info(_extract_mcp_content_text(result))
|
|
|
|
return result
|
|
|
|
result = asyncio.run(call_tool())
|
|
|
|
return _extract_mcp_content_text(result)
|
|
|
|
@tool
|
|
def find_products_and_services():
|
|
"""
|
|
Obtiene la lista de productos y servicios disponibles para agendar.
|
|
|
|
Returns:
|
|
str: JSON array con los productos y servicios disponibles.
|
|
Cada elemento contiene:
|
|
- id (int): Identificador único del producto/servicio
|
|
- name (str): Nombre del producto o servicio
|
|
- price (float): Precio del servicio
|
|
- description (str | None): Descripción adicional del servicio
|
|
|
|
Example:
|
|
'[{"id": 1, "name": "Manicure", "price": 20000.0, "description": null},
|
|
{"id": 2, "name": "Corte de Cabello", "price": 30000.0, "description": "Corte frances, aleman"}]'
|
|
"""
|
|
|
|
async def call_tool():
|
|
async with _MCP_CLIENT:
|
|
result = await _MCP_CLIENT.call_tool("find_products_and_services", None)
|
|
logger.info(_extract_mcp_content_text(result))
|
|
|
|
return result
|
|
|
|
result = asyncio.run(call_tool())
|
|
|
|
return _extract_mcp_content_text(result)
|
|
|
|
@tool(args_schema=ScheduleSchema)
|
|
def schedule_appointment(
|
|
schedule_date,
|
|
schedule_time,
|
|
description,
|
|
service_center=11,
|
|
customer=None,
|
|
professional=6,
|
|
customer_id=None,
|
|
) -> bool:
|
|
"""
|
|
Permite agendar una cita para un cliente en un centro de servicio específico.
|
|
|
|
Args:
|
|
schedule_date: Fecha de la cita en formato YYYY-MM-DD.
|
|
schedule_time: Hora de la cita en formato HH:MM:SS.
|
|
description (str): Descripción o motivo de la cita.
|
|
service_center (int, optional): ID del centro de servicio. Por defecto es 11.
|
|
customer (int, optional): ID del cliente. Si no se proporciona, se usa customer_id.
|
|
professional (int, optional): ID del profesional. Por defecto es 6.
|
|
customer_id (int, optional): ID del cliente obtenido del estado.
|
|
|
|
Returns:
|
|
bool: True si la cita fue agendada exitosamente, False en caso contrario.
|
|
|
|
Notes:
|
|
- La fecha y hora se almacenan en zona horaria America/Bogota (UTC-5).
|
|
- La fecha no puede ser en el pasado.
|
|
"""
|
|
logger.info("Agendando cita...")
|
|
logger.info([schedule_date, schedule_time])
|
|
|
|
if not schedule_date and schedule_time:
|
|
return False
|
|
|
|
if customer is None and customer_id is not None:
|
|
customer = customer_id
|
|
logger.info(f"Customer ID obtained from agent state: {customer}")
|
|
|
|
if customer is None or customer == 0:
|
|
logger.warning("No se pudo obtener el ID del cliente")
|
|
return False
|
|
|
|
schedule = (
|
|
datetime.combine(schedule_date, schedule_time)
|
|
.replace(tzinfo=ZoneInfo(TIMEZONE))
|
|
.isoformat()
|
|
)
|
|
|
|
async def call_tool():
|
|
async with _MCP_CLIENT:
|
|
result = await _MCP_CLIENT.call_tool(
|
|
"create_schedule",
|
|
{
|
|
"professional": professional,
|
|
"description": description,
|
|
"customer": customer,
|
|
"date": schedule,
|
|
"service_center": service_center,
|
|
},
|
|
)
|
|
|
|
logger.info(result)
|
|
|
|
return result
|
|
|
|
result = asyncio.run(call_tool())
|
|
|
|
return True
|