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." "helpful assistant that can call tools when needed."
" Always respond with a message." " Always respond with a message."
) )
max_iterations: int = 200 max_iterations: int = 100
timeout_seconds: float = 30.0 timeout_seconds: float = 15.0
class Agent: class Agent:
@@ -135,12 +135,12 @@ class Agent:
"""Construye el grafo de estado completo.""" """Construye el grafo de estado completo."""
agent_builder = StateGraph(MessagesState) 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("llm_call", self._llm_call_node)
agent_builder.add_node("tool_node", self._tool_node) agent_builder.add_node("tool_node", self._tool_node)
agent_builder.add_edge(START, "identify_customer") agent_builder.add_edge(START, "llm_call")
agent_builder.add_edge("identify_customer", "llm_call") # agent_builder.add_edge("identify_customer", "llm_call")
agent_builder.add_conditional_edges( agent_builder.add_conditional_edges(
"llm_call", self._should_continue, {"tool_node": "tool_node", END: END} "llm_call", self._should_continue, {"tool_node": "tool_node", END: END}
) )
@@ -226,6 +226,7 @@ class Agent:
return {"messages": state["messages"]} return {"messages": state["messages"]}
tool_results: list[BaseMessage] = [] tool_results: list[BaseMessage] = []
updates = {}
for tool_call in last_message.tool_calls: for tool_call in last_message.tool_calls:
result = self._execute_tool( result = self._execute_tool(
@@ -233,10 +234,29 @@ class Agent:
) )
tool_results.append(result) 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: if self._post_tool_hook:
self._post_tool_hook(state, result) 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( def _execute_tool(
self, tool_call: dict, customer_id=None, customer_name=None 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. que puedas obtener a travez de las tools.
- Destaca opciones importantes con `código inline`. - 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> </Reglas de Conversación>
<Límites éticos y legales (INQUEBRANTABLES)> <Límites éticos y legales (INQUEBRANTABLES)>
@@ -53,7 +60,7 @@ prompts:
- schedule_appointment: Agendar y confirmar cita. - schedule_appointment: Agendar y confirmar cita.
- find_service_centers: Consultar los centros de atencion o servicio disponibles. - find_service_centers: Consultar los centros de atencion o servicio disponibles.
- find_products_and_services: Consultar los productos y servicios disponibles con precios. - 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> </Tools Disponibles Para Brindar Atencion al Cliente>
""" """

View File

@@ -24,12 +24,32 @@ class NaliiaTools:
return [ return [
self.get_current_datetime, self.get_current_datetime,
self.get_tomorrow_date, self.get_tomorrow_date,
self.register_customer,
self.schedule_appointment, self.schedule_appointment,
self.find_service_centers, self.find_service_centers,
self.find_products_and_services, self.find_products_and_services,
self.find_customer_by_identifier, 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 @tool
def get_tomorrow_date() -> str: def get_tomorrow_date() -> str:
""" """
@@ -43,7 +63,6 @@ class NaliiaTools:
- No se aceptan fechas en el pasado con respecto a esta fecha. - No se aceptan fechas en el pasado con respecto a esta fecha.
- Utiliza la zona horaria de Colombia (America/Bogota, UTC-5). - Utiliza la zona horaria de Colombia (America/Bogota, UTC-5).
""" """
td = timedelta(days=1) td = timedelta(days=1)
tomorrow = datetime.now().date() + td tomorrow = datetime.now().date() + td
tomorrow_date_formated = tomorrow.isoformat() tomorrow_date_formated = tomorrow.isoformat()
@@ -73,6 +92,41 @@ class NaliiaTools:
return 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(result.content[0].text)
return result
result = asyncio.run(call_tool())
return result.content[0].text
@tool @tool
def find_customer_by_identifier(identifier: str) -> str: 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. Envía un mensaje de texto a través de la API de WhatsApp.
""" """
# instance_url = request_data["instance_url"] # 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"] instance_name = request_data["instance_name"]
apikey = request_data["apikey"] apikey = request_data["apikey"]
jid = request_data["jid"] jid = request_data["jid"]
@@ -65,18 +65,27 @@ async def webhook_chat(request: Request, agent=Depends(get_agent)):
config = {"configurable": {"thread_id": thread_id}} config = {"configurable": {"thread_id": thread_id}}
existing_state = await agent.checkpointer.aget(config)
if existing_state is None:
initial_state = { initial_state = {
"messages": [HumanMessage(content=user_message)], "messages": [HumanMessage(content=user_message)],
"llm_calls": 0, "llm_calls": 0,
"customer_phone": "", "customer_phone": customer_phone,
"customer_name": "", "customer_name": "",
"customer_id": 0, "customer_id": 0,
} }
existing_state = await agent.checkpointer.aget(config)
if existing_state is None:
initial_state["customer_phone"] = customer_phone
logger.info(f"Nueva conversación iniciada con cliente: {customer_phone}") 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 = await agent.ainvoke(initial_state, config=config)
agent_response_content = agent_response["messages"][-1].content agent_response_content = agent_response["messages"][-1].content

View File

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