meta-optimizer/agent.py
José Manuel Gómez 92786e94a8 Initial structure: Meta Optimizer
- 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>
2026-05-22 12:22:11 +02:00

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,
}