Add Notification Backend Protocol (#24)
All checks were successful
CI / ci (push) Successful in 21s

Reviewed-on: #24
This commit was merged in pull request #24.
This commit is contained in:
2026-03-09 07:36:47 +00:00
parent ba6fde1b15
commit 1803d011d0
11 changed files with 676 additions and 189 deletions

View File

@@ -3,27 +3,49 @@
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 NotificationService
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: NotificationService,
notification_service: NotificationBackend,
ctx: ReadonlyContext | None = None,
) -> str:
"""Provide dynamic instructions based on pending notifications.
"""Provide dynamic instructions based on recent notifications.
This function is called by the ADK agent on each message. It:
1. Checks if this is the first message in the session (< 2 events)
2. Queries Firestore for pending notifications
3. Marks them as notified
4. Returns a dynamic instruction for the agent to mention them
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
@@ -43,71 +65,54 @@ async def provide_dynamic_instruction(
logger.debug("No session available for dynamic instruction")
return ""
# FOR TESTING: Always check for notifications
# (comment out to enable first-message-only)
# Only check on first message (when events list is empty
# or has only 1-2 events)
# Events include both user and agent messages, so < 2 means first interaction
# event_count = len(session.events) if session.events else 0
#
# if event_count >= 2:
# logger.debug(
# "Skipping notification check: not first message (event_count=%d)",
# event_count,
# )
# return ""
# Extract phone number from user_id (they are the same in this implementation)
phone_number = session.user_id
logger.info(
"First message detected for user %s, checking for pending notifications",
"Checking recent notifications for user %s",
phone_number,
)
try:
# Fetch pending notifications
pending_notifications = await notification_service.get_pending_notifications(
# Fetch recent notifications
recent_notifications = await notification_service.get_recent_notifications(
phone_number
)
if not pending_notifications:
logger.info("No pending notifications for user %s", 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 = [
nid
for n in pending_notifications
for n in recent_notifications
if (nid := n.get("id_notificacion")) is not None
]
count = len(pending_notifications)
count = len(recent_notifications)
# Format notification details for the agent
# Format notification details for the agent (most recent first)
now = time.time()
notification_details = []
for notif in pending_notifications:
for i, notif in enumerate(recent_notifications, 1):
evento = notif.get("nombre_evento_dialogflow", "notificacion")
texto = notif.get("texto", "Sin texto")
notification_details.append(f" - Evento: {evento} | Texto: {texto}")
ts = notif.get("timestamp_creacion", notif.get("timestampCreacion", 0))
ago = _format_time_ago(now, ts)
notification_details.append(
f" {i}. [{ago}] Evento: {evento} | Texto: {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"""
IMPORTANTE - NOTIFICACIONES PENDIENTES:
El usuario tiene {count} notificación(es) sin leer:
{header}
{details_text}
INSTRUCCIONES:
- Menciona estas notificaciones de forma natural en tu respuesta inicial
- No necesitas leerlas todas literalmente, solo hazle saber que las tiene
- Sé breve y directo según tu personalidad (directo y cálido)
- Si el usuario pregunta algo específico, prioriza responder eso primero\
y luego menciona las notificaciones
Ejemplo: "¡Hola! 👋 Tienes {count} notificación(es)\
pendiente(s). ¿Te gustaría revisarlas?"
"""
# Mark notifications as notified in Firestore