Skip to navigation

Automatizaciones en código

Python con el SDK sc, un disparador, unos pasos y un resultado.

View as Markdown

El builder escribe las automatizaciones por ti. Esta guía es para leerlas, cambiarlas o escribirlas tú.

Una automatización

Es un archivo en la carpeta del agente, automations/<nombre>.py: una función bajo un disparador, con un título y un resumen con tus palabras, y sus pasos con nombre.

automations/recordatorio.py
from datetime import date, timedelta
import sc
@sc.cron(
"0 9 * * *",
tz="Europe/Madrid",
title="Recordatorio de citas",
summary="Avisa por WhatsApp a quien tiene cita mañana",
table="citas",
group_by="aviso",
)
def recordatorio(event: sc.Event) -> str:
manana = (date.today() + timedelta(days=1)).isoformat()
avisos = 0
with sc.step("Avisar a quien tiene cita mañana"):
for cita in sc.data.find("citas", where={"dia": manana, "aviso": "pendiente"}):
texto = sc.ai(f"Recordatorio breve y cálido de esta cita: {cita['values']}")
sc.send(cita["values"]["telefono"], texto, via="whatsapp", key=f"aviso:{cita['id']}")
sc.data.update("citas", cita["id"], {"aviso": "enviado"})
avisos += 1
return f"{avisos} avisos"
  • Lo que devuelve es la línea de la ejecución: “12 avisos”.
  • table y group_by hacen que la página de la automatización muestre la tabla por estados (“Pendientes 8, Enviados 34”).
  • key= hace que cada aviso se envíe una sola vez, aunque la ejecución vuelva a empezar.
  • El agente necesita acceso a la tabla citas y un canal de WhatsApp. Pasadas 24 horas desde el último mensaje del cliente, WhatsApp solo deja enviar plantillas: elige una en los ajustes del canal o pásala con template={"name": ..., "values": [...]}.

Disparadores

Uno por automatización:

DisparadorEmpieza
@sc.cron("0 9 * * 1-5", tz="Europe/Madrid", ...)A una hora, en cron de cinco campos. Sin tz, en UTC.
@sc.webhook(...)Cuando otro sistema llama a su dirección. Lo que manda está en event.data.
@sc.event("gmail", "GMAIL_NEW_GMAIL_MESSAGE", ...)Cuando pasa algo en una app conectada.
@sc.on("message.received", decides=True, ...)Cuando llega un mensaje a un canal, con una regla tuya.

Todos llevan title y summary. Una ejecución dura como mucho 5 minutos; timeout_s= la alarga hasta 900 segundos.

Una regla sobre lo que llega decide cada mensaje con sc.answer(), sc.hold(motivo) (a una persona) o sc.ignore(motivo):

automations/proveedores.py
import sc
@sc.on(
"message.received",
decides=True,
title="Proveedores",
summary="Pasa a una persona lo que traen los proveedores",
)
def proveedores(event: sc.Event) -> None:
with sc.step("Mirar si es un proveedor"):
if "albarán" in event.text.lower():
sc.hold("Es un proveedor")

Si una regla falla o tarda, el agente responde: un error nunca deja a un cliente sin respuesta.

Lo que puede llamar

El código se ejecuta aislado, sin red salvo a SignalCore. Todo lo que hace pasa por sc:

LlamadaHace
sc.data.find(tabla, where=, words=) · add · updateLee y escribe en las tablas que el agente tiene.
sc.ai(prompt, model=, system=, schema=, files=, **opciones)Una llamada a un modelo; lo que hace el modelo decide qué devuelve. Uno que escribe da texto (con schema, JSON); uno que dibuja, habla, hace música o vídeo deja un archivo del agente y da su ruta; uno que escucha un audio (files=[ruta]) da lo que dice. Las opciones son las del modelo (voice=, seconds=, aspect_ratio=…).
sc.send(contacto, texto, via=, template=, files=, key=)Un mensaje a un contacto por uno de los canales del agente; files, rutas de archivos suyos (lo que hizo sc.ai), van como imagen, audio, vídeo o documento.
sc.tool(app, accion, **datos)Una acción de una app que el agente tiene.
sc.http.get(url) · post · putUna llamada a otra web; key= pone una de sus claves sin que el código la vea.
sc.files.read(ruta) · write · list · searchSus archivos y los de la organización.
sc.notify(texto)Una línea en tu bandeja.
sc.agent.start(texto)Abre una conversación del agente, para lo que pide criterio de principio a fin.
sc.step(nombre)Nombra un paso en el registro y en la página de la automatización.

¿Necesita una librería? Decláralas en agent.yaml, fijadas, y se instalan antes de la primera ejecución:

agent.yaml
packages:
pip: [openpyxl==3.1.5]
apt: [poppler-utils]

Probar y encender

Con el builder: pídele el cambio, la prueba en seco y te enseña la tarjeta para encenderla.

Con la API, con una clave con permiso write:

# 1. Escribir el archivo. Si algo está mal, la respuesta dice qué.
curl -X PUT https://signalcore.ai/v1/agents/$AGENT/folder/automations/recordatorio.py \
-H "Authorization: Bearer $SIGNALCORE_KEY" \
-H "Content-Type: application/json" \
-d "$(jq -n --rawfile c recordatorio.py '{content: $c}')"
# 2. Probarla en seco: lee de verdad, no envía ni cambia nada.
curl -X POST https://signalcore.ai/v1/agents/$AGENT/automations/recordatorio/test \
-H "Authorization: Bearer $SIGNALCORE_KEY"
# 3. Encenderla. Una automatización nueva empieza en pausa.
curl -X PATCH https://signalcore.ai/v1/agents/$AGENT/automations/recordatorio \
-H "Authorization: Bearer $SIGNALCORE_KEY" \
-H "Content-Type: application/json" \
-d '{"on": true}'

Sus ejecuciones, con cada paso: GET /v1/runs?agent=$AGENT&automation=recordatorio y GET /v1/runs/{run_id}.

Una acción propia

Lo que el agente hace mientras habla (no solo) es una acción: un archivo en actions/, una función que el modelo llama como herramienta.

actions/precio.py
import sc
PRECIOS = {"limpieza": 45.0, "blanqueamiento": 180.0}
@sc.action
def precio(servicio: str, con_iva: bool = True) -> float:
"""El precio de un servicio en euros. Llámala cuando pregunten cuánto cuesta un tratamiento."""
base = PRECIOS[servicio.lower()]
return round(base * 1.21, 2) if con_iva else base

El docstring le dice al modelo cuándo llamarla. @sc.action(destructive=True) marca una que cambia algo fuera: una persona dice que sí antes de que se ejecute.