- meta_ads_client.py: Meta Marketing API client (facebook-business SDK) - agent.py: Claude-powered campaign decision engine - run.py: main orchestration script - config.py: environment variables - .github/workflows/daily.yml: GitHub Actions cron (8am CEST) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
60 lines
2.2 KiB
Python
60 lines
2.2 KiB
Python
import json
|
|
import anthropic
|
|
import config
|
|
|
|
client = anthropic.Anthropic(api_key=config.ANTHROPIC_API_KEY)
|
|
|
|
SYSTEM_PROMPT = """
|
|
Eres un experto en optimización de campañas de Meta Ads (Facebook/Instagram) para generación de leads.
|
|
Cada campaña corresponde a un producto o curso con un CPL objetivo (coste por lead) acordado.
|
|
|
|
MODELO DE NEGOCIO:
|
|
- El objetivo es maximizar el volumen de leads por debajo del CPL máximo rentable.
|
|
- La frecuencia alta puede indicar saturación de audiencia.
|
|
- El CTR y CPM son indicadores clave de relevancia creativa y competencia en subasta.
|
|
|
|
REGLAS DE DECISIÓN:
|
|
1. CPL > CPL_máximo → REDUCIR_PRESUPUESTO o revisar creatividades/audiencias.
|
|
2. CPL <= CPL_máximo y volumen bajo → AUMENTAR_PRESUPUESTO si hay margen.
|
|
3. Frecuencia > 3.0 → considerar rotar creatividades o ampliar audiencia.
|
|
4. CTR < 1% → problema creativo, revisar anuncios.
|
|
5. Sin leads tras 3+ días de gasto → revisar configuración de conversión.
|
|
|
|
Devuelve ÚNICAMENTE un JSON válido con esta estructura exacta, sin texto adicional ni markdown:
|
|
{
|
|
"accion": "PAUSAR | REDUCIR_PRESUPUESTO | AUMENTAR_PRESUPUESTO | MANTENER | REVISAR_CREATIVIDADES",
|
|
"parametro": 1.0,
|
|
"justificacion": "explicación breve",
|
|
"consejo": "acción concreta y específica",
|
|
"alerta": "texto si hay algo crítico, null si no",
|
|
"confianza": 0.0
|
|
}
|
|
"""
|
|
|
|
|
|
def decide(analysis: dict) -> dict:
|
|
response = client.messages.create(
|
|
model="claude-haiku-4-5-20251001",
|
|
max_tokens=400,
|
|
system=SYSTEM_PROMPT,
|
|
messages=[{
|
|
"role": "user",
|
|
"content": (
|
|
f"Analiza esta campaña de Meta Ads y devuelve la decisión en JSON:\n\n"
|
|
f"{json.dumps(analysis, ensure_ascii=False, indent=2)}"
|
|
),
|
|
}],
|
|
)
|
|
raw = response.content[0].text.strip()
|
|
clean = raw.replace("```json", "").replace("```", "").strip()
|
|
try:
|
|
return json.loads(clean)
|
|
except json.JSONDecodeError:
|
|
return {
|
|
"accion": "MANTENER",
|
|
"parametro": 1.0,
|
|
"justificacion": "Error parseando respuesta del agente.",
|
|
"alerta": f"JSON inválido: {raw[:200]}",
|
|
"confianza": 0.0,
|
|
}
|