- Sync shows campaign ID, name and status for new campaigns, and field-level changes (before/after) for updated ones - Agent now returns a 'consejo' field with a specific actionable recommendation per campaign - Summary table includes leads, margin and consejo per campaign Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
70 lines
3.1 KiB
Python
70 lines
3.1 KiB
Python
import json
|
||
import anthropic
|
||
import config
|
||
|
||
client = anthropic.Anthropic(api_key=config.ANTHROPIC_API_KEY)
|
||
|
||
SYSTEM_PROMPT = """
|
||
Eres un agente experto en optimización de campañas de generación de leads para centros de formación.
|
||
Cada campaña corresponde a un curso concreto con un PPL (precio por lead) fijo acordado con los centros compradores.
|
||
|
||
MODELO DE NEGOCIO:
|
||
- Ingreso = leads_entregados × PPL
|
||
- Margen = (Ingreso - Gasto Google Ads) / Ingreso
|
||
- El objetivo es maximizar leads dentro del capping mensual manteniendo margen positivo.
|
||
- El CPA máximo ya refleja el margen mínimo aceptable.
|
||
|
||
REGLAS DE DECISIÓN:
|
||
1. urgencia=PAUSAR → accion=PAUSAR siempre. El capping está lleno, seguir gastando destruye margen.
|
||
2. urgencia=SPRINT → accion=AUMENTAR_PRESUPUESTO con parametro entre 1.3 y 1.5. Quedan pocos días y leads por entregar.
|
||
3. urgencia=ACELERAR y campaña rentable → accion=AUMENTAR_PRESUPUESTO con parametro entre 1.1 y 1.25.
|
||
4. urgencia=ACELERAR y campaña NO rentable → accion=MANTENER o revisar keywords (no gastar más si no convierte).
|
||
5. urgencia=FRENAR → accion=REDUCIR_PRESUPUESTO con parametro entre 0.75 y 0.9.
|
||
6. urgencia=EN_RITMO y rentable → accion=MANTENER.
|
||
7. urgencia=EN_RITMO y NO rentable → accion=REDUCIR_PRESUPUESTO con parametro 0.85.
|
||
8. alerta_tracking=true → añadir alerta sobre discrepancia de tracking aunque la acción sea otra.
|
||
|
||
Devuelve ÚNICAMENTE un JSON válido con esta estructura exacta, sin texto adicional ni markdown:
|
||
{
|
||
"accion": "PAUSAR | REDUCIR_PRESUPUESTO | AUMENTAR_PRESUPUESTO | MANTENER",
|
||
"parametro": 1.0,
|
||
"nuevo_budget_diario": 0.0,
|
||
"justificacion": "explicación breve del porqué de la decisión",
|
||
"consejo": "acción concreta y específica que debería tomar el gestor (keywords, pujas, anuncios, configuración, etc.)",
|
||
"alerta": "texto si hay algo crítico, null si no hay",
|
||
"confianza": 0.0
|
||
}
|
||
|
||
El campo nuevo_budget_diario = budget_diario_actual × parametro (calcula tú el valor final).
|
||
El campo consejo debe ser accionable y específico: qué revisar, qué cambiar, qué hacer a continuación.
|
||
"""
|
||
|
||
|
||
def decide(analysis: dict) -> dict:
|
||
response = client.messages.create(
|
||
model="claude-sonnet-4-20250514",
|
||
max_tokens=400,
|
||
system=SYSTEM_PROMPT,
|
||
messages=[{
|
||
"role": "user",
|
||
"content": (
|
||
f"Analiza esta campaña 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:
|
||
# Fallback seguro si el modelo no devuelve JSON limpio
|
||
return {
|
||
"accion": "MANTENER",
|
||
"parametro": 1.0,
|
||
"nuevo_budget_diario": analysis.get("budget_diario_actual", 0),
|
||
"justificacion": "Error parseando respuesta del agente. Revisión manual recomendada.",
|
||
"alerta": f"JSON inválido recibido: {raw[:200]}",
|
||
"confianza": 0.0,
|
||
}
|