enhance: auto-identify customers and add new tools

- Agent now auto-calls find_customer_by_identifier when user provides identifier
- Added register_customer and check_mcp_connection tools
- Refactored chat_hook to resume existing conversations from state
- Reduced default max_iterations (100) and timeout (15s)
This commit is contained in:
2026-03-18 22:30:02 -05:00
parent ea3da00b95
commit 949dacd59c
5 changed files with 109 additions and 19 deletions

View File

@@ -17,8 +17,8 @@ class AgentConfig:
"helpful assistant that can call tools when needed."
" Always respond with a message."
)
max_iterations: int = 200
timeout_seconds: float = 30.0
max_iterations: int = 100
timeout_seconds: float = 15.0
class Agent:
@@ -135,12 +135,12 @@ class Agent:
"""Construye el grafo de estado completo."""
agent_builder = StateGraph(MessagesState)
agent_builder.add_node("identify_customer", self._identify_customer_node)
# agent_builder.add_node("identify_customer", self._identify_customer_node)
agent_builder.add_node("llm_call", self._llm_call_node)
agent_builder.add_node("tool_node", self._tool_node)
agent_builder.add_edge(START, "identify_customer")
agent_builder.add_edge("identify_customer", "llm_call")
agent_builder.add_edge(START, "llm_call")
# agent_builder.add_edge("identify_customer", "llm_call")
agent_builder.add_conditional_edges(
"llm_call", self._should_continue, {"tool_node": "tool_node", END: END}
)
@@ -226,6 +226,7 @@ class Agent:
return {"messages": state["messages"]}
tool_results: list[BaseMessage] = []
updates = {}
for tool_call in last_message.tool_calls:
result = self._execute_tool(
@@ -233,10 +234,29 @@ class Agent:
)
tool_results.append(result)
if tool_call.get("name") == "find_customer_by_identifier":
try:
customer_data = json.loads(result.content) if result.content else {}
if isinstance(customer_data, list):
customer_data = customer_data[0] if customer_data else {}
if customer_data and customer_data.get("party.", {}).get("id", 0):
updates["customer_name"] = customer_data.get("party.", {}).get(
"name", "Cliente"
)
updates["customer_id"] = customer_data.get("party.", {}).get(
"id", 0
)
except (json.JSONDecodeError, KeyError):
pass
if self._post_tool_hook:
self._post_tool_hook(state, result)
return {"messages": state["messages"] + tool_results}
result_dict = {"messages": state["messages"] + tool_results}
if updates:
result_dict.update(updates)
return result_dict
def _execute_tool(
self, tool_call: dict, customer_id=None, customer_name=None

View File

@@ -21,6 +21,13 @@ prompts:
que puedas obtener a travez de las tools.
- Destaca opciones importantes con `código inline`.
IMPORTANTE - Identificación del Cliente:
- Cuando el usuario proporcione un número de teléfono, documento de identidad,
DEBES inmediatamente llamar a la herramienta find_customer_by_identifier con ese valor.
- No asumas que ya conoces al cliente, verifica su identidad usando la herramienta.
- Ejemplo: Si el usuario dice "mi teléfono es 3016859278" o "3016859278", llama inmediatamente
a find_customer_by_identifier con ese identificador.
</Reglas de Conversación>
<Límites éticos y legales (INQUEBRANTABLES)>
@@ -53,7 +60,7 @@ prompts:
- schedule_appointment: Agendar y confirmar cita.
- find_service_centers: Consultar los centros de atencion o servicio disponibles.
- find_products_and_services: Consultar los productos y servicios disponibles con precios.
- find_customer_by_identifier: Buscar cliente por telefono, email o numero de documento.
- find_customer_by_identifier: Buscar cliente por telefono, email o numero de documento. SIEMPRE llama esta herramienta cuando el usuario proporcione un identificador.
</Tools Disponibles Para Brindar Atencion al Cliente>
"""

View File

@@ -24,12 +24,32 @@ class NaliiaTools:
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,
self.check_mcp_connection,
]
@tool
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"
@tool
def get_tomorrow_date() -> str:
"""
@@ -43,7 +63,6 @@ class NaliiaTools:
- 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().date() + td
tomorrow_date_formated = tomorrow.isoformat()
@@ -73,6 +92,41 @@ class NaliiaTools:
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(result.content[0].text)
return result
result = asyncio.run(call_tool())
return result.content[0].text
@tool
def find_customer_by_identifier(identifier: str) -> str:
"""

View File

@@ -15,7 +15,7 @@ async def send_whatsapp_message(request_data: SendMessageScheme):
Envía un mensaje de texto a través de la API de WhatsApp.
"""
# instance_url = request_data["instance_url"]
instance_url = 'http://192.168.58.109:8080'
instance_url = "http://192.168.58.109:8080"
instance_name = request_data["instance_name"]
apikey = request_data["apikey"]
jid = request_data["jid"]
@@ -65,18 +65,27 @@ async def webhook_chat(request: Request, agent=Depends(get_agent)):
config = {"configurable": {"thread_id": thread_id}}
initial_state = {
"messages": [HumanMessage(content=user_message)],
"llm_calls": 0,
"customer_phone": "",
"customer_name": "",
"customer_id": 0,
}
existing_state = await agent.checkpointer.aget(config)
if existing_state is None:
initial_state["customer_phone"] = customer_phone
initial_state = {
"messages": [HumanMessage(content=user_message)],
"llm_calls": 0,
"customer_phone": customer_phone,
"customer_name": "",
"customer_id": 0,
}
logger.info(f"Nueva conversación iniciada con cliente: {customer_phone}")
else:
initial_state = {
"messages": existing_state["messages"]
+ [HumanMessage(content=user_message)],
"llm_calls": existing_state.get("llm_calls", 0),
"customer_phone": customer_phone,
"customer_name": existing_state.get("customer_name", ""),
"customer_id": existing_state.get("customer_id", 0),
}
logger.info(f"Conversación reanudada con cliente: {customer_phone}")
agent_response = await agent.ainvoke(initial_state, config=config)
agent_response_content = agent_response["messages"][-1].content

View File

@@ -10,7 +10,7 @@ class AgentClient:
logger.info(f"Sending message to agent: {message}")
response = await self._client.post(
"http://localhost:8010/chat",
"http://192.168.58.109:8010/chat",
json={"messages": message},
timeout=60
)