fix: PEP8 Style
This commit is contained in:
833
poetry.lock
generated
833
poetry.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -15,7 +15,9 @@ dependencies = [
|
|||||||
"dotenv (>=0.9.9,<0.10.0)",
|
"dotenv (>=0.9.9,<0.10.0)",
|
||||||
"fastapi[standard] (>=0.128.5,<0.129.0)",
|
"fastapi[standard] (>=0.128.5,<0.129.0)",
|
||||||
"langgraph-checkpoint-postgres (>=3.0.4,<4.0.0)",
|
"langgraph-checkpoint-postgres (>=3.0.4,<4.0.0)",
|
||||||
"psycopg[binary] (>=3.3.3,<4.0.0)"
|
"psycopg[binary] (>=3.3.3,<4.0.0)",
|
||||||
|
"fastmcp (>=3.1.0,<4.0.0)",
|
||||||
|
"flake8 (>=7.3.0,<8.0.0)"
|
||||||
]
|
]
|
||||||
|
|
||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
|
|||||||
@@ -6,24 +6,22 @@ from langchain_core.runnables import RunnableConfig
|
|||||||
from typing import Literal, Callable, Any, Union
|
from typing import Literal, Callable, Any, Union
|
||||||
from .schemas import MessagesState
|
from .schemas import MessagesState
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
import logging
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class AgentConfig:
|
class AgentConfig:
|
||||||
"""Configuración inmutable del agente para fácil testing."""
|
"""Configuración inmutable del agente para fácil testing."""
|
||||||
system_prompt: str = (
|
system_prompt: str = (
|
||||||
"helpful assistant that can call tools when needed. Always respond with a message. "
|
"helpful assistant that can call tools when needed."
|
||||||
|
" Always respond with a message."
|
||||||
)
|
)
|
||||||
max_iterations: int = 10
|
max_iterations: int = 200
|
||||||
timeout_seconds: float = 30.0
|
timeout_seconds: float = 30.0
|
||||||
|
|
||||||
|
|
||||||
class Agent:
|
class Agent:
|
||||||
"""
|
"""
|
||||||
Agente conversacional basado en LangGraph.
|
Agente conversacional basado en LangGraph.
|
||||||
|
|
||||||
Soporta tools pasadas como lista de instancias.
|
Soporta tools pasadas como lista de instancias.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@@ -36,7 +34,7 @@ class Agent:
|
|||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Inicializa el agente.
|
Inicializa el agente.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
model: Modelo LLM a usar
|
model: Modelo LLM a usar
|
||||||
config: Configuración del agente (AgentConfig)
|
config: Configuración del agente (AgentConfig)
|
||||||
@@ -49,7 +47,7 @@ class Agent:
|
|||||||
self._config = config or AgentConfig()
|
self._config = config or AgentConfig()
|
||||||
self._tools = tools or []
|
self._tools = tools or []
|
||||||
self._checkpointer = checkpointer or InMemorySaver()
|
self._checkpointer = checkpointer or InMemorySaver()
|
||||||
|
|
||||||
self._tools_by_name: dict[str, Any] = {
|
self._tools_by_name: dict[str, Any] = {
|
||||||
tool.name: tool for tool in self._tools
|
tool.name: tool for tool in self._tools
|
||||||
}
|
}
|
||||||
@@ -73,7 +71,9 @@ class Agent:
|
|||||||
"""Expone el grafo para inspección en tests."""
|
"""Expone el grafo para inspección en tests."""
|
||||||
return self._build_agent()
|
return self._build_agent()
|
||||||
|
|
||||||
async def ainvoke(self, state: dict, config: RunnableConfig | None = None) -> dict:
|
async def ainvoke(
|
||||||
|
self, state: dict, config: RunnableConfig | None = None
|
||||||
|
) -> dict:
|
||||||
"""
|
"""
|
||||||
Versión asíncrona de invoke, necesaria para checkpointers de DB.
|
Versión asíncrona de invoke, necesaria para checkpointers de DB.
|
||||||
"""
|
"""
|
||||||
@@ -86,7 +86,9 @@ class Agent:
|
|||||||
# Llamamos al método ainvoke del grafo compilado
|
# Llamamos al método ainvoke del grafo compilado
|
||||||
return await self._compiled_agent.ainvoke(state, config=config)
|
return await self._compiled_agent.ainvoke(state, config=config)
|
||||||
|
|
||||||
def invoke(self, state: dict, config: RunnableConfig | None = None) -> dict:
|
def invoke(
|
||||||
|
self, state: dict, config: RunnableConfig | None = None
|
||||||
|
) -> dict:
|
||||||
"""
|
"""
|
||||||
Ejecuta el agente con el estado inicial.
|
Ejecuta el agente con el estado inicial.
|
||||||
|
|
||||||
@@ -98,7 +100,7 @@ class Agent:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
if not config:
|
if not config:
|
||||||
config : RunnableConfig = {"configurable": {"thread_id": "1"}}
|
config: RunnableConfig = {"configurable": {"thread_id": "1"}}
|
||||||
|
|
||||||
if "messages" not in state:
|
if "messages" not in state:
|
||||||
raise ValueError("State must contain 'messages' key")
|
raise ValueError("State must contain 'messages' key")
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
prompts:
|
prompts:
|
||||||
NALIIA_PROMPT: |
|
NALIIA_PROMPT: |
|
||||||
"""
|
"""
|
||||||
Eres Naliia, asistente virtual de un centro de belleza.
|
Eres Naliia, asistente virtual.
|
||||||
|
|
||||||
Responde usando formato Markdown:
|
Responde usando formato Markdown:
|
||||||
- Usa **negrita** para énfasis.
|
- Usa **negrita** para énfasis.
|
||||||
@@ -9,19 +9,11 @@ prompts:
|
|||||||
- Usa saltos de línea entre secciones.
|
- Usa saltos de línea entre secciones.
|
||||||
- Destaca opciones importantes con `código inline`.
|
- Destaca opciones importantes con `código inline`.
|
||||||
|
|
||||||
Ejemplo de formato:
|
*Lo que NUNCA debes hacer:*
|
||||||
¡Hola! Bienvenido/a, soy **Naliia**, tu asistente virtual.
|
|
||||||
|
|
||||||
Puedo ayudarte con:
|
❌ Dar diagnósticos médicos
|
||||||
|
❌ Recomendar tratamientos clínicos sin evaluación
|
||||||
**Agendar una cita**
|
❌ Minimizar riesgos ("es super seguro, no pasa nada")
|
||||||
Ver horarios disponibles y reservar tu servicio
|
❌ Prometer resultados específicos
|
||||||
|
❌ Agendar procedimientos invasivos sin mencionar evaluación previa
|
||||||
**Información sobre servicios y productos**
|
|
||||||
Conocer nuestros tratamientos y productos
|
|
||||||
|
|
||||||
**Cancelar una cita**
|
|
||||||
Si necesitas cancelar alguna reserva previa
|
|
||||||
|
|
||||||
¿En qué puedo ayudarte hoy?
|
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -1,67 +1,70 @@
|
|||||||
|
import asyncio
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
from fastmcp import Client
|
||||||
from langchain.tools import tool
|
from langchain.tools import tool
|
||||||
from ..logger import logger
|
from ..logger import logger
|
||||||
|
|
||||||
|
from .schemes import ScheduleSchema
|
||||||
|
|
||||||
|
|
||||||
|
MCP_SERVER_URL = "http://localhost:8000/mcp"
|
||||||
|
_MCP_CLIENT = Client(MCP_SERVER_URL)
|
||||||
|
|
||||||
|
|
||||||
class NaliiaTools:
|
class NaliiaTools:
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self._session_active = False
|
||||||
|
|
||||||
def get_tools(self):
|
def get_tools(self):
|
||||||
|
|
||||||
return [
|
return [
|
||||||
self.verificar_usuario_registrado,
|
self.get_current_datetime,
|
||||||
self.registrar_usuario_nuevo,
|
self.schedule_appointment
|
||||||
self.consultar_sedes,
|
|
||||||
self.consultar_agenda_disponible,
|
|
||||||
self.agendar_cita
|
|
||||||
]
|
]
|
||||||
|
|
||||||
@tool
|
@tool
|
||||||
def verificar_usuario_registrado(phone: str) -> bool:
|
def get_current_datetime() -> datetime:
|
||||||
"""
|
"""
|
||||||
Verifica si el usuario esta registrado.
|
Consulta la fecha actual, no se aceptan fechas
|
||||||
|
en el pasado con respecto a esta fecha.
|
||||||
Args:
|
|
||||||
phone: Numero de contacto del cliente ejemplo 30123334
|
|
||||||
"""
|
"""
|
||||||
logger.info("Llamando a Verificar usuario.")
|
return datetime.now()
|
||||||
|
|
||||||
return False
|
@tool(args_schema=ScheduleSchema)
|
||||||
|
def schedule_appointment(
|
||||||
@tool
|
schedule_date, schedule_time,
|
||||||
def registrar_usuario_nuevo(full_name: str, phone: str) -> bool:
|
service_center, customer,
|
||||||
|
professional, description
|
||||||
|
) -> bool:
|
||||||
"""
|
"""
|
||||||
En caso de que no se haya podido verificar el usuario, sera necesario
|
Permite al Cliente al Agendar una Cita
|
||||||
registrarlo como un usuario nuevo.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
full_name: Nombre completo del cliente.
|
|
||||||
phone: Numero de contacto del cliente.
|
|
||||||
"""
|
|
||||||
logger.info("Llamando a Registrar Nuevo Usuario.")
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
@tool
|
|
||||||
def consultar_sedes() -> list:
|
|
||||||
"""
|
|
||||||
Brinda informacion al usuario acerca de las sedes disponibles y sus horarios.
|
|
||||||
"""
|
|
||||||
logger.info("Llamando a consultar sedes.")
|
|
||||||
|
|
||||||
return []
|
|
||||||
|
|
||||||
@tool
|
|
||||||
def consultar_agenda_disponible() -> list:
|
|
||||||
"""
|
|
||||||
Ayuda a consultar al cliente los horarios de atencion disponibles para agendar su cita.
|
|
||||||
"""
|
|
||||||
logger.info("Llamando a consultar agenda disponible.")
|
|
||||||
|
|
||||||
return []
|
|
||||||
|
|
||||||
@tool
|
|
||||||
def agendar_cita() -> bool:
|
|
||||||
"""
|
|
||||||
Ayuda al cliente a confirmar su cita.
|
|
||||||
"""
|
"""
|
||||||
logger.info("Llamando a agendar cita.")
|
logger.info("Llamando a agendar cita.")
|
||||||
|
logger.info([
|
||||||
|
schedule_date,
|
||||||
|
schedule_time,
|
||||||
|
service_center,
|
||||||
|
customer,
|
||||||
|
professional,
|
||||||
|
description
|
||||||
|
])
|
||||||
|
|
||||||
|
example_schedule = {
|
||||||
|
'professional': 6,
|
||||||
|
'description': "Bien Bonito Todo!",
|
||||||
|
'customer': 4,
|
||||||
|
'date': '2026-03-25 14:00',
|
||||||
|
'service_center': 11
|
||||||
|
}
|
||||||
|
|
||||||
|
async def call_tool():
|
||||||
|
async with _MCP_CLIENT:
|
||||||
|
result = await _MCP_CLIENT.call_tool(
|
||||||
|
"create_schedule", example_schedule)
|
||||||
|
logger.info(result)
|
||||||
|
|
||||||
|
asyncio.run(call_tool())
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|||||||
37
src/naliiabot/bot/tools/schemes.py
Normal file
37
src/naliiabot/bot/tools/schemes.py
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
from datetime import date, time
|
||||||
|
from typing import Optional
|
||||||
|
from pydantic import BaseModel, Field, PositiveInt
|
||||||
|
|
||||||
|
|
||||||
|
class ScheduleSchema(BaseModel):
|
||||||
|
"""
|
||||||
|
Scheme for Schedule Appointment
|
||||||
|
"""
|
||||||
|
|
||||||
|
schedule_date: date = Field(
|
||||||
|
description="Date in format DD-MM-YYYY")
|
||||||
|
schedule_time: time = Field(
|
||||||
|
description="Time in format 12h (HH:MM)")
|
||||||
|
service_center: Optional[PositiveInt] = Field(
|
||||||
|
None, description="ID Service center Example: 1")
|
||||||
|
customer: Optional[PositiveInt] = Field(
|
||||||
|
None, description="ID Customer Example: 1")
|
||||||
|
professional: Optional[PositiveInt] = Field(
|
||||||
|
None, description="ID Professional Example: 1")
|
||||||
|
description: str = Field(
|
||||||
|
default="General Schedule",
|
||||||
|
max_length=255,
|
||||||
|
description="Max 255 characters."
|
||||||
|
)
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
json_schema_extra = {
|
||||||
|
"example": {
|
||||||
|
"schedule_date": "15-03-2026",
|
||||||
|
"schedule_time": "02:00",
|
||||||
|
"service_center": 1,
|
||||||
|
"professional": 12,
|
||||||
|
"customer": 999,
|
||||||
|
"description": "Review"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,17 +1,12 @@
|
|||||||
|
from naliiabot.bot.agent.agent import Agent
|
||||||
import os
|
|
||||||
from naliiabot.bot.agent.agent import Agent, AgentConfig
|
|
||||||
from naliiabot.bot.tools.naliia_tools import NaliiaTools
|
|
||||||
from naliiabot.bot.factories.llm_factory import LLMFactory
|
|
||||||
from naliiabot.bot.prompts.load_prompt import get_prompt_template
|
|
||||||
from langgraph.checkpoint.postgres.aio import AsyncPostgresSaver
|
|
||||||
from psycopg_pool import AsyncConnectionPool
|
from psycopg_pool import AsyncConnectionPool
|
||||||
from psycopg.rows import dict_row
|
from psycopg.rows import dict_row
|
||||||
from ..logger import logger
|
from .settings import _settings as st
|
||||||
from .settings import _settings
|
|
||||||
|
|
||||||
|
|
||||||
_connection_string = f"postgresql://{_settings.DB_USER}:{_settings.DB_PASSWORD}@localhost:5432/{_settings.DB_NAME}"
|
_protocol = "postgresql://"
|
||||||
|
_connection_string =\
|
||||||
|
f"{_protocol}{st.DB_USER}:{st.DB_PASSWORD}@localhost:5432/{st.DB_NAME}"
|
||||||
agent_instance: Agent | None = None
|
agent_instance: Agent | None = None
|
||||||
db_pool = None
|
db_pool = None
|
||||||
|
|
||||||
|
|||||||
@@ -4,8 +4,9 @@ from langchain_core.messages import HumanMessage
|
|||||||
|
|
||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
|
|
||||||
|
|
||||||
@router.post("/chat")
|
@router.post("/chat")
|
||||||
def chat(messages: dict, agent = Depends(get_agent)):
|
def chat(messages: dict, agent=Depends(get_agent)):
|
||||||
"""
|
"""
|
||||||
Simulate a chat response based on the input message.
|
Simulate a chat response based on the input message.
|
||||||
|
|
||||||
@@ -16,7 +17,12 @@ def chat(messages: dict, agent = Depends(get_agent)):
|
|||||||
dict: A dictionary containing the response message.
|
dict: A dictionary containing the response message.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
messages = [HumanMessage(content=messages["messages"])]
|
messages = [
|
||||||
response_message = agent.invoke({"messages": messages})
|
HumanMessage(
|
||||||
|
content=messages["messages"]
|
||||||
return {"response": response_message}
|
)]
|
||||||
|
response_message = agent.invoke(
|
||||||
|
{"messages": messages}
|
||||||
|
)
|
||||||
|
|
||||||
|
return {"response": response_message}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import json
|
|||||||
|
|
||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
|
|
||||||
|
|
||||||
async def send_whatsapp_message(request_data: SendMessageScheme):
|
async def send_whatsapp_message(request_data: SendMessageScheme):
|
||||||
"""
|
"""
|
||||||
Envía un mensaje de texto a través de la API de WhatsApp.
|
Envía un mensaje de texto a través de la API de WhatsApp.
|
||||||
@@ -32,11 +33,16 @@ async def send_whatsapp_message(request_data: SendMessageScheme):
|
|||||||
"delay": delay,
|
"delay": delay,
|
||||||
"linkPreview": False
|
"linkPreview": False
|
||||||
}
|
}
|
||||||
|
|
||||||
async with httpx.AsyncClient() as client:
|
async with httpx.AsyncClient() as client:
|
||||||
try:
|
try:
|
||||||
response = await client.post(endpoint, json=payload, headers=headers)
|
response = await client.post(
|
||||||
|
endpoint,
|
||||||
|
json=payload,
|
||||||
|
headers=headers
|
||||||
|
)
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
|
|
||||||
return response.json()
|
return response.json()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error enviando mensaje a WhatsApp: {e}")
|
logger.error(f"Error enviando mensaje a WhatsApp: {e}")
|
||||||
@@ -44,7 +50,7 @@ async def send_whatsapp_message(request_data: SendMessageScheme):
|
|||||||
|
|
||||||
|
|
||||||
@router.post("/webhook")
|
@router.post("/webhook")
|
||||||
async def webhook_chat(request: Request, agent = Depends(get_agent)):
|
async def webhook_chat(request: Request, agent=Depends(get_agent)):
|
||||||
logger.info("Received webhook request")
|
logger.info("Received webhook request")
|
||||||
body = await request.json()
|
body = await request.json()
|
||||||
logger.info(f"Webhook payload: {json.dumps(body)}")
|
logger.info(f"Webhook payload: {json.dumps(body)}")
|
||||||
@@ -74,9 +80,11 @@ async def webhook_chat(request: Request, agent = Depends(get_agent)):
|
|||||||
|
|
||||||
messages = [HumanMessage(content=user_message)]
|
messages = [HumanMessage(content=user_message)]
|
||||||
|
|
||||||
agent_response = await agent.ainvoke({"messages": messages}, config=config)
|
agent_response = await agent.ainvoke(
|
||||||
|
{"messages": messages}, config=config
|
||||||
|
)
|
||||||
agent_response_content = agent_response["messages"][-1].content
|
agent_response_content = agent_response["messages"][-1].content
|
||||||
|
|
||||||
clean_jid = thread_id.split('@')[0]
|
clean_jid = thread_id.split('@')[0]
|
||||||
|
|
||||||
await send_whatsapp_message(
|
await send_whatsapp_message(
|
||||||
@@ -87,10 +95,10 @@ async def webhook_chat(request: Request, agent = Depends(get_agent)):
|
|||||||
jid=clean_jid,
|
jid=clean_jid,
|
||||||
text=agent_response_content,
|
text=agent_response_content,
|
||||||
delay=1200
|
delay=1200
|
||||||
))
|
))
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error processing webhook: {e}")
|
logger.error(f"Error processing webhook: {e}")
|
||||||
return {"error": str(e)}
|
return {"error": str(e)}
|
||||||
|
|
||||||
return {"status": "sent", "reply": agent_response_content}
|
return {"status": "sent", "reply": agent_response_content}
|
||||||
|
|||||||
@@ -16,10 +16,10 @@ async def lifespan(app: FastAPI):
|
|||||||
await checkpointer.setup()
|
await checkpointer.setup()
|
||||||
|
|
||||||
model_name = "anthropic"
|
model_name = "anthropic"
|
||||||
|
model = deps.LLMFactory(model_name).get_model()
|
||||||
naliia_prompt = deps.get_prompt_template("NALIIA_PROMPT")
|
naliia_prompt = deps.get_prompt_template("NALIIA_PROMPT")
|
||||||
naliia_tools = deps.NaliiaTools().get_tools()
|
naliia_tools = deps.NaliiaTools().get_tools()
|
||||||
config = deps.AgentConfig(system_prompt=naliia_prompt)
|
config = deps.AgentConfig(system_prompt=naliia_prompt)
|
||||||
model = deps.LLMFactory(model_name).get_model()
|
|
||||||
|
|
||||||
deps.agent_instance = deps.Agent(
|
deps.agent_instance = deps.Agent(
|
||||||
model=model,
|
model=model,
|
||||||
@@ -35,7 +35,9 @@ async def lifespan(app: FastAPI):
|
|||||||
|
|
||||||
app = FastAPI(
|
app = FastAPI(
|
||||||
title="NaliiaBot API",
|
title="NaliiaBot API",
|
||||||
description="API for NaliiaBot, a chatbot that provides customer service and related topics.",
|
description=(
|
||||||
|
"API for NaliiaBot, a chatbot that provides "
|
||||||
|
"customer service and related topics."),
|
||||||
version="1.0.0",
|
version="1.0.0",
|
||||||
lifespan=lifespan
|
lifespan=lifespan
|
||||||
)
|
)
|
||||||
@@ -43,10 +45,12 @@ app = FastAPI(
|
|||||||
app.include_router(chat_router)
|
app.include_router(chat_router)
|
||||||
app.include_router(chat_hook_router)
|
app.include_router(chat_hook_router)
|
||||||
|
|
||||||
|
|
||||||
@app.get("/")
|
@app.get("/")
|
||||||
def read_root():
|
def read_root():
|
||||||
return {"Hello": "World"}
|
return {"Hello": "World"}
|
||||||
|
|
||||||
|
|
||||||
@app.get("/health")
|
@app.get("/health")
|
||||||
def health_check():
|
def health_check():
|
||||||
return {"status": "ok"}
|
return {"status": "ok"}
|
||||||
|
|||||||
@@ -7,4 +7,4 @@ class SendMessageScheme(TypedDict):
|
|||||||
apikey: str
|
apikey: str
|
||||||
jid: str
|
jid: str
|
||||||
delay: int = 1200
|
delay: int = 1200
|
||||||
text: str
|
text: str
|
||||||
|
|||||||
@@ -1,10 +1,8 @@
|
|||||||
import streamlit as st
|
import streamlit as st
|
||||||
from agent_client import AgentClient
|
from agent_client import AgentClient
|
||||||
from logger import logger
|
import asyncio
|
||||||
import json
|
import json
|
||||||
import httpx
|
import httpx
|
||||||
import random
|
|
||||||
import time
|
|
||||||
|
|
||||||
|
|
||||||
async def main():
|
async def main():
|
||||||
@@ -18,7 +16,6 @@ async def main():
|
|||||||
with st.chat_message(message["role"]):
|
with st.chat_message(message["role"]):
|
||||||
st.markdown(message["content"])
|
st.markdown(message["content"])
|
||||||
|
|
||||||
|
|
||||||
if prompt := st.chat_input("What is up?"):
|
if prompt := st.chat_input("What is up?"):
|
||||||
st.chat_message("user").markdown(prompt)
|
st.chat_message("user").markdown(prompt)
|
||||||
st.session_state.messages.append({"role": "user", "content": prompt})
|
st.session_state.messages.append({"role": "user", "content": prompt})
|
||||||
@@ -26,10 +23,8 @@ async def main():
|
|||||||
agent_client = AgentClient()
|
agent_client = AgentClient()
|
||||||
response = await agent_client.send_message(prompt)
|
response = await agent_client.send_message(prompt)
|
||||||
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
data = response.json()
|
|
||||||
except json.JSONDecodeError:
|
except json.JSONDecodeError:
|
||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
f"Respuesta inválida (no JSON): {response.text}"
|
f"Respuesta inválida (no JSON): {response.text}"
|
||||||
@@ -41,8 +36,10 @@ async def main():
|
|||||||
|
|
||||||
raw_response = response.json()['response']['messages'][-1]['content']
|
raw_response = response.json()['response']['messages'][-1]['content']
|
||||||
|
|
||||||
with st.chat_message("assistant"):
|
st.session_state.messages.append({
|
||||||
formated_response = st.markdown(raw_response)
|
"role": "assistant",
|
||||||
st.session_state.messages.append({"role": "assistant", "content": raw_response})
|
"content": raw_response
|
||||||
|
})
|
||||||
|
|
||||||
if __name__ == "__main__": import asyncio; asyncio.run(main())
|
if __name__ == "__main__":
|
||||||
|
asyncio.run(main())
|
||||||
|
|||||||
@@ -4,4 +4,4 @@ logging.basicConfig(
|
|||||||
level=logging.INFO,
|
level=logging.INFO,
|
||||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
||||||
)
|
)
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|||||||
@@ -6,13 +6,15 @@ from src.naliiabot.bot.factories.llm_factory import LLMFactory
|
|||||||
from httpx import ASGITransport, AsyncClient
|
from httpx import ASGITransport, AsyncClient
|
||||||
from naliiabotapi.main import app
|
from naliiabotapi.main import app
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="function")
|
@pytest.fixture(scope="function")
|
||||||
async def async_client():
|
async def async_client():
|
||||||
"""Fixture para un cliente HTTP asíncrono."""
|
"""Fixture para un cliente HTTP asíncrono."""
|
||||||
|
|
||||||
async with AsyncClient(
|
async with AsyncClient(
|
||||||
transport=ASGITransport(app=app),
|
transport=ASGITransport(app=app),
|
||||||
base_url="http://localhost:8010") as client:
|
base_url="http://localhost:8010"
|
||||||
|
) as client:
|
||||||
|
|
||||||
yield client
|
yield client
|
||||||
|
|
||||||
@@ -73,101 +75,101 @@ def send_message_payload():
|
|||||||
"""Payload de ejemplo para enviar mensajes al webhook."""
|
"""Payload de ejemplo para enviar mensajes al webhook."""
|
||||||
return {
|
return {
|
||||||
"event": "messages.upsert",
|
"event": "messages.upsert",
|
||||||
"instance": "ALEJANDRO AYALA",
|
"instance": "ALEJANDRO AYALA",
|
||||||
"data": {
|
"data": {
|
||||||
"key": {
|
"key": {
|
||||||
"remoteJid": "5512992544490@s.whatsapp.net",
|
"remoteJid": "5512992544490@s.whatsapp.net",
|
||||||
"remoteJidAlt": "5512992544490@s.whatsapp.net",
|
"remoteJidAlt": "5512992544490@s.whatsapp.net",
|
||||||
"fromMe": False, "id": "AC4367597BDC50537133B9C8848B2E62",
|
"fromMe": False, "id": "AC4367597BDC50537133B9C8848B2E62",
|
||||||
"participant": "", "addressingMode": "lid"
|
"participant": "", "addressingMode": "lid"
|
||||||
},
|
},
|
||||||
"pushName": "Alejandro",
|
"pushName": "Alejandro",
|
||||||
"status": "DELIVERY_ACK",
|
"status": "DELIVERY_ACK",
|
||||||
"message": {
|
"message": {
|
||||||
"conversation": "Hola",
|
"conversation": "Hola",
|
||||||
"messageContextInfo": {
|
"messageContextInfo": {
|
||||||
"threadId": [],
|
"threadId": [],
|
||||||
"deviceListMetadata": {
|
"deviceListMetadata": {
|
||||||
"senderKeyIndexes": [],
|
"senderKeyIndexes": [],
|
||||||
"recipientKeyIndexes": [],
|
"recipientKeyIndexes": [],
|
||||||
"senderKeyHash": {
|
"senderKeyHash": {
|
||||||
"0": 154,
|
"0": 154,
|
||||||
"1": 106,
|
"1": 106,
|
||||||
"2": 69,
|
"2": 69,
|
||||||
"3": 189,
|
"3": 189,
|
||||||
"4": 90,
|
"4": 90,
|
||||||
"5": 105,
|
"5": 105,
|
||||||
"6": 172,
|
"6": 172,
|
||||||
"7": 243,
|
"7": 243,
|
||||||
"8": 27,
|
"8": 27,
|
||||||
"9": 98
|
"9": 98
|
||||||
},
|
},
|
||||||
"senderTimestamp": {
|
"senderTimestamp": {
|
||||||
"low": 1772156772,
|
"low": 1772156772,
|
||||||
"high": 0,
|
"high": 0,
|
||||||
"unsigned": "true"
|
"unsigned": "true"
|
||||||
},
|
},
|
||||||
"recipientKeyHash": {
|
"recipientKeyHash": {
|
||||||
"0": 16,
|
"0": 16,
|
||||||
"1": 98,
|
"1": 98,
|
||||||
"2": 75,
|
"2": 75,
|
||||||
"3": 100,
|
"3": 100,
|
||||||
"4": 131,
|
"4": 131,
|
||||||
"5": 43,
|
"5": 43,
|
||||||
"6": 24,
|
"6": 24,
|
||||||
"7": 225,
|
"7": 225,
|
||||||
"8": 244,
|
"8": 244,
|
||||||
"9": 126
|
"9": 126
|
||||||
},
|
},
|
||||||
"recipientTimestamp": {
|
"recipientTimestamp": {
|
||||||
"low": 1772155597,
|
"low": 1772155597,
|
||||||
"high": 0,
|
"high": 0,
|
||||||
"unsigned": True
|
"unsigned": True
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"deviceListMetadataVersion": 2,
|
"deviceListMetadataVersion": 2,
|
||||||
"messageSecret": {
|
"messageSecret": {
|
||||||
"0": 100,
|
"0": 100,
|
||||||
"1": 47,
|
"1": 47,
|
||||||
"2": 112,
|
"2": 112,
|
||||||
"3": 133,
|
"3": 133,
|
||||||
"4": 255,
|
"4": 255,
|
||||||
"5": 156,
|
"5": 156,
|
||||||
"6": 129,
|
"6": 129,
|
||||||
"7": 57,
|
"7": 57,
|
||||||
"8": 239,
|
"8": 239,
|
||||||
"9": 219,
|
"9": 219,
|
||||||
"10": 74,
|
"10": 74,
|
||||||
"11": 171,
|
"11": 171,
|
||||||
"12": 212,
|
"12": 212,
|
||||||
"13": 150,
|
"13": 150,
|
||||||
"14": 112,
|
"14": 112,
|
||||||
"15": 83,
|
"15": 83,
|
||||||
"16": 123,
|
"16": 123,
|
||||||
"17": 78,
|
"17": 78,
|
||||||
"18": 189,
|
"18": 189,
|
||||||
"19": 13,
|
"19": 13,
|
||||||
"20": 51,
|
"20": 51,
|
||||||
"21": 133,
|
"21": 133,
|
||||||
"22": 129,
|
"22": 129,
|
||||||
"23": 6,
|
"23": 6,
|
||||||
"24": 197,
|
"24": 197,
|
||||||
"25": 119,
|
"25": 119,
|
||||||
"26": 197,
|
"26": 197,
|
||||||
"27": 56,
|
"27": 56,
|
||||||
"28": 87,
|
"28": 87,
|
||||||
"29": 176,
|
"29": 176,
|
||||||
"30": 135,
|
"30": 135,
|
||||||
"31": 62
|
"31": 62
|
||||||
}}},
|
}}},
|
||||||
"messageType": "conversation",
|
"messageType": "conversation",
|
||||||
"messageTimestamp": 1772168493,
|
"messageTimestamp": 1772168493,
|
||||||
"instanceId": "75a877f1-d772-422b-a6f1-6ac470af5663",
|
"instanceId": "75a877f1-d772-422b-a6f1-6ac470af5663",
|
||||||
"source": "android"
|
"source": "android"
|
||||||
},
|
},
|
||||||
"destination": "http://192.168.58.110:8010/webhook",
|
"destination": "http://192.168.58.110:8010/webhook",
|
||||||
"date_time": "2026-02-27T02:01:34.178Z",
|
"date_time": "2026-02-27T02:01:34.178Z",
|
||||||
"sender": "573016859278@s.whatsapp.net",
|
"sender": "573016859278@s.whatsapp.net",
|
||||||
"server_url": "http://localhost:8080",
|
"server_url": "http://localhost:8080",
|
||||||
"apikey": "98B6B820AFC3-4544-9A4C-011B0719C2D1"
|
"apikey": "98B6B820AFC3-4544-9A4C-011B0719C2D1"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -375,8 +375,9 @@ class TestIntegrationFlows:
|
|||||||
assert "messages" in result
|
assert "messages" in result
|
||||||
assert "llm_calls" in result
|
assert "llm_calls" in result
|
||||||
assert result["llm_calls"] == 1
|
assert result["llm_calls"] == 1
|
||||||
assert len(result["messages"]) == 2 # Input + Response
|
assert len(result["messages"]) == 2
|
||||||
assert result["messages"][-1].content == "¡Hola! ¿En qué puedo ayudarte?"
|
assert result["messages"][-1].content == (
|
||||||
|
"¡Hola! ¿En qué puedo ayudarte?")
|
||||||
|
|
||||||
def test_tool_use_flow(self, agent_with_tool):
|
def test_tool_use_flow(self, agent_with_tool):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import pytest
|
import pytest
|
||||||
from fastapi.testclient import TestClient
|
from fastapi.testclient import TestClient
|
||||||
from src.naliiabotapi.main import app
|
from src.naliiabotapi.main import app
|
||||||
from langchain_core.messages import HumanMessage
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
@@ -10,16 +9,19 @@ def client():
|
|||||||
|
|
||||||
return client
|
return client
|
||||||
|
|
||||||
|
|
||||||
def test_root(client):
|
def test_root(client):
|
||||||
response = client.get("/")
|
response = client.get("/")
|
||||||
|
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
|
||||||
def test_health(client):
|
def test_health(client):
|
||||||
response = client.get("/health")
|
response = client.get("/health")
|
||||||
|
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
|
||||||
def test_chat(client):
|
def test_chat(client):
|
||||||
response = client.post("/chat", json={"messages": "Hello, how are you?"})
|
response = client.post("/chat", json={"messages": "Hello, how are you?"})
|
||||||
|
|
||||||
|
|||||||
@@ -26,11 +26,14 @@ class TestWebhookChatBot:
|
|||||||
# Sobrescribir la dependencia del agente en la app
|
# Sobrescribir la dependencia del agente en la app
|
||||||
app.dependency_overrides[get_agent] = lambda: mock_agent
|
app.dependency_overrides[get_agent] = lambda: mock_agent
|
||||||
|
|
||||||
async with AsyncClient(transport=ASGITransport(app=app), base_url="http://localhost:8010") as client:
|
async with AsyncClient(
|
||||||
|
transport=ASGITransport(app=app),
|
||||||
|
base_url="http://localhost:8010"
|
||||||
|
) as client:
|
||||||
response = await client.post("/webhook", json=send_message_payload)
|
response = await client.post("/webhook", json=send_message_payload)
|
||||||
|
|
||||||
# Limpiar override para no afectar a otros tests
|
# Limpiar override para no afectar a otros tests
|
||||||
app.dependency_overrides.pop(get_agent, None)
|
app.dependency_overrides.pop(get_agent, None)
|
||||||
|
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
assert response.json().get("reply") == "Respuesta mock"
|
assert response.json().get("reply") == "Respuesta mock"
|
||||||
|
|||||||
Reference in New Issue
Block a user