SDK de robots y logging
El SDK nora_agent es la forma oficial de que un robot hable con NORA (Robots Center)
desde su propio código: escribir logs, reportar progreso, leer credenciales (assets),
consumir colas y reaccionar a señales del operador. Esta página está pensada para
autores de robots y se centra en logging y en la configuración de ejecución.
El SDK funciona en dos modos, sin que cambies tu código:
- Job gestionado — cuando NORA lanza el robot, el agente inyecta
NORA_JOB_ID, el token de ejecución y la URL de la API. El SDK envía todo a la plataforma. - Desarrollo local — sin
NORA_JOB_ID(por ejemplo bajonora dev run), las funciones de log y progreso imprimen porstdoutpara que sigas viendo la salida.
Loguear correctamente
Sección titulada «Loguear correctamente»La función real es:
from nora_agent import sdk
sdk.log(level: str, message: str, data: dict | None = None) -> NoneUso básico:
from nora_agent import sdk
sdk.log("info", "Inicio del proceso de facturación")sdk.log("warning", "Reintentando descarga (intento 2)")sdk.log("error", "No se pudo abrir el portal del proveedor")Niveles válidos
Sección titulada «Niveles válidos»| Nivel | Cuándo usarlo |
|---|---|
info | Hitos normales del proceso (inicio, paso completado, fin). |
warning | Algo recuperable: reintento, dato faltante, fallback. |
error | Un fallo que impide completar el item o el job. |
El SDK normaliza el nivel a mayúsculas internamente, así que "info" e "INFO"
producen el mismo resultado. Mantén estos tres niveles; la plataforma los espera
para colorear y filtrar.
Datos estructurados con data
Sección titulada «Datos estructurados con data»Para adjuntar contexto, pásalo por el parámetro data (un dict), no lo
concatenes al texto del mensaje:
sdk.log( "info", "Factura procesada", {"invoice_id": "F-2026-0042", "amount": 1290.50, "currency": "EUR"},)El SDK serializa data como JSON al final de la línea. Mantén el mensaje legible
para humanos y deja los valores estructurados en data.
Formato que emite el SDK (contrato)
Sección titulada «Formato que emite el SDK (contrato)»Internamente, sdk.log() construye una línea con este formato y la envía al stream
de logs del job:
[2026-06-19T08:30:00Z] [INFO] Factura procesada | {"invoice_id": "F-2026-0042", "amount": 1290.5}El timestamp es ISO-8601 en UTC (YYYY-MM-DDTHH:MM:SSZ). El front de NORA parsea
exactamente este contrato; por eso el robot no debe inventar otro formato ni anteponer
su propia marca de tiempo.
Reportar progreso
Sección titulada «Reportar progreso»Para mover la barra de progreso del job (0–100):
sdk.update_progress(50, "Mitad de los registros procesados")El valor se acota automáticamente al rango 0–100. En dev local imprime
[progress] 50% - ... por stdout.
Configuración de ejecución y resolución de pantalla
Sección titulada «Configuración de ejecución y resolución de pantalla»El agente inyecta la configuración del job mediante variables de entorno. Las más útiles para un robot:
| Variable | Significado |
|---|---|
NORA_JOB_ID | ID del job que lanzó el robot (ausente en dev local). |
NORA_DISPLAY_WIDTH | Ancho de pantalla configurado en la máquina (Robots Center). |
NORA_DISPLAY_HEIGHT | Alto de pantalla configurado en la máquina. |
Puedes recuperar el ID del job de forma segura con sdk.get_job_id() (devuelve
None fuera de un job gestionado).
Para la resolución, prioriza NORA_DISPLAY_WIDTH / NORA_DISPLAY_HEIGHT sobre la
resolución “viva” del sistema operativo. En una VM sin sesión RDP abierta, la
resolución real del SO puede quedar pegada en un valor pequeño o incorrecto (p. ej.
1024×768) y hacer fallar la automatización:
import osfrom nora_agent import sdk
def screen_resolution() -> tuple[int, int]: w = os.environ.get("NORA_DISPLAY_WIDTH", "") h = os.environ.get("NORA_DISPLAY_HEIGHT", "") if w.isdigit() and h.isdigit(): return int(w), int(h) return 1920, 1080 # fallback solo para dev local
ancho, alto = screen_resolution()sdk.log("info", f"Resolución: {ancho}x{alto}", {"width": ancho, "height": alto})Assets (credenciales y configuración cifrada)
Sección titulada «Assets (credenciales y configuración cifrada)»Lee un asset descifrado por nombre. Devuelve un dict con
{name, type, environment, value, username?}:
cred = sdk.get_asset("portal-proveedor") # environment="production" por defectousuario = cred.get("username")secreto = cred["value"]Colas (queues)
Sección titulada «Colas (queues)»El SDK cubre el ciclo de vida de una cola. Las funciones más usadas:
| Función | Para qué sirve |
|---|---|
queue_pending(queue) | Cuántos items quedan claimables (status new). |
queue_stats(queue) | Conteo por estado, sin consumir nada. |
get_queue_item(queue) | Reclama el siguiente item (o None si está vacía). |
complete_queue_item(queue, item_id, result) | Marca el item como completado con datos de resultado. |
fail_queue_item(queue, item_id, error_message) | Marca el item como fallido (puede reintentar). |
add_queue_item(queue, data, priority=3, ...) | Encola un único item. |
add_queue_items(queue, items, priority=3) | Encola varios; devuelve cuántos se añadieron. |
send_queue_item_for_review(queue, item_id) | Manda el item a revisión humana. |
wait_for_queue_review(queue, item_id) | Bloquea hasta "approved" o "rejected". |
Patrón típico de consumo (dispatcher + performer):
from nora_agent import sdk
QUEUE = "RPA-Challenge"
# Cargar la cola solo si está vacía (idempotente).if sdk.queue_pending(QUEUE) == 0: sdk.add_queue_items(QUEUE, registros) # registros: list[dict]
# Procesar item a item.while True: item = sdk.get_queue_item(QUEUE) if item is None: break try: # ... trabajo sobre item["data"] ... sdk.complete_queue_item(QUEUE, item["id"], {"ok": True}) except Exception as e: sdk.fail_queue_item(QUEUE, item["id"], str(e)) raiseLa prioridad usa la escala 1=baja, 3=normal, 5=urgente. add_queue_item()
acepta además reference, deadline y postpone (datetime o cadena ISO-8601).
Control del job y human-in-the-loop
Sección titulada «Control del job y human-in-the-loop»sdk.should_stop()→Truesi el operador pidió Stop/Kill desde el dashboard. Conviene consultarlo dentro de bucles largos para salir limpiamente.sdk.ask_user(prompt, options=None, timeout=3600.0)→ pide un dato al operador y bloquea hasta la respuesta. Requiere un job gestionado.
if sdk.should_stop(): sdk.log("warning", "Detención solicitada por el operador; cierro ordenadamente") return
decision = sdk.ask_user("¿Aprobar el pago?", options=["Sí", "No"])Buenas prácticas
Sección titulada «Buenas prácticas»- Centraliza el trato con NORA en un módulo (como
nora.pydel ejemploexamples/rpa-challenge), de modo que tus workflows solo llamen alog,claim_next, etc., sin saber de HTTP. - Loguea hitos, no ruido: un
infoal inicio/fin de cada paso ywarning/errorcuando algo se desvía. - Adjunta el contexto por
data, no en el texto. - Reporta progreso en pasos significativos para que el operador vea avance real.