Files
NaliiaBot/src/naliiabot/bot/tools/naliia_tools.py

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