All checks were successful
CI / ci (push) Successful in 21s
Co-authored-by: Anibal Angulo <a8065384@banorte.com> Reviewed-on: #31
129 lines
3.7 KiB
Python
129 lines
3.7 KiB
Python
"""Dynamic instruction provider for VAia agent."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
import time
|
|
from typing import TYPE_CHECKING
|
|
|
|
if TYPE_CHECKING:
|
|
from google.adk.agents.readonly_context import ReadonlyContext
|
|
|
|
from va_agent.notifications import NotificationBackend
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
_SECONDS_PER_MINUTE = 60
|
|
_SECONDS_PER_HOUR = 3600
|
|
_MINUTES_PER_HOUR = 60
|
|
_HOURS_PER_DAY = 24
|
|
|
|
|
|
def _format_time_ago(now: float, ts: float) -> str:
|
|
"""Return a human-readable Spanish label like 'hace 3 horas'."""
|
|
diff = max(now - ts, 0)
|
|
minutes = int(diff // _SECONDS_PER_MINUTE)
|
|
hours = int(diff // _SECONDS_PER_HOUR)
|
|
|
|
if minutes < 1:
|
|
return "justo ahora"
|
|
if minutes < _MINUTES_PER_HOUR:
|
|
return f"hace {minutes} min"
|
|
if hours < _HOURS_PER_DAY:
|
|
return f"hace {hours}h"
|
|
days = hours // _HOURS_PER_DAY
|
|
return f"hace {days}d"
|
|
|
|
|
|
async def provide_dynamic_instruction(
|
|
notification_service: NotificationBackend,
|
|
ctx: ReadonlyContext | None = None,
|
|
) -> str:
|
|
"""Provide dynamic instructions based on recent notifications.
|
|
|
|
This function is called by the ADK agent on each message. It:
|
|
1. Queries Firestore for recent notifications
|
|
2. Marks them as notified
|
|
3. Returns a dynamic instruction for the agent to mention them
|
|
|
|
Args:
|
|
notification_service: Service for fetching/marking notifications
|
|
ctx: Agent context containing session information
|
|
|
|
Returns:
|
|
Dynamic instruction string (empty if no notifications or not first message)
|
|
|
|
"""
|
|
# Only check notifications on the first message
|
|
if not ctx:
|
|
logger.debug("No context available for dynamic instruction")
|
|
return ""
|
|
|
|
session = ctx.session
|
|
if not session:
|
|
logger.debug("No session available for dynamic instruction")
|
|
return ""
|
|
|
|
# Extract phone number from user_id (they are the same in this implementation)
|
|
phone_number = session.user_id
|
|
|
|
logger.info(
|
|
"Checking recent notifications for user %s",
|
|
phone_number,
|
|
)
|
|
|
|
try:
|
|
# Fetch recent notifications
|
|
recent_notifications = await notification_service.get_recent_notifications(
|
|
phone_number
|
|
)
|
|
|
|
if not recent_notifications:
|
|
logger.info("No recent notifications for user %s", phone_number)
|
|
return ""
|
|
|
|
# Build dynamic instruction with notification details
|
|
notification_ids = [n.id_notificacion for n in recent_notifications]
|
|
count = len(recent_notifications)
|
|
|
|
# Format notification details for the agent (most recent first)
|
|
now = time.time()
|
|
notification_details = []
|
|
for i, notif in enumerate(recent_notifications, 1):
|
|
ago = _format_time_ago(now, notif.timestamp_creacion)
|
|
notification_details.append(
|
|
f" {i}. [{ago}] Evento: {notif.nombre_evento} | Texto: {notif.texto}"
|
|
)
|
|
|
|
details_text = "\n".join(notification_details)
|
|
|
|
header = (
|
|
f"Estas son {count} notificación(es) reciente(s)"
|
|
" de las cuales el usuario podría preguntar más:"
|
|
)
|
|
instruction = f"""
|
|
{header}
|
|
|
|
{details_text}
|
|
"""
|
|
|
|
# Mark notifications as notified in Firestore
|
|
await notification_service.mark_as_notified(phone_number, notification_ids)
|
|
|
|
logger.info(
|
|
"Returning dynamic instruction with %d notification(s) for user %s",
|
|
count,
|
|
phone_number,
|
|
)
|
|
logger.debug("Dynamic instruction content:\n%s", instruction)
|
|
|
|
except Exception:
|
|
logger.exception(
|
|
"Error building dynamic instruction for user %s",
|
|
phone_number,
|
|
)
|
|
return ""
|
|
else:
|
|
return instruction
|