Files
agent/src/va_agent/dynamic_instruction.py
Anibal Angulo ac27d12ed3
All checks were successful
CI / ci (push) Successful in 21s
Add notification model (#31)
Co-authored-by: Anibal Angulo <a8065384@banorte.com>
Reviewed-on: #31
2026-03-10 23:50:41 +00:00

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