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:
@@ -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
|
||||||
|
|||||||
@@ -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>
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -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:
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -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}}
|
||||||
|
|
||||||
initial_state = {
|
|
||||||
"messages": [HumanMessage(content=user_message)],
|
|
||||||
"llm_calls": 0,
|
|
||||||
"customer_phone": "",
|
|
||||||
"customer_name": "",
|
|
||||||
"customer_id": 0,
|
|
||||||
}
|
|
||||||
|
|
||||||
existing_state = await agent.checkpointer.aget(config)
|
existing_state = await agent.checkpointer.aget(config)
|
||||||
|
|
||||||
if existing_state is None:
|
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}")
|
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
|
||||||
|
|||||||
@@ -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
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user