#!/usr/bin/env python3
"""
Campaign Brief Builder Agent — runnable demo (Agent Blueprint from AgentKits)

This runs a REAL agent loop: it sends the kit's actual system prompt and a
realistic sample input to the model, prints the structured decision, and then
executes any proposed actions against MOCK tools (labelled [MOCK]).

The model/reasoning is real. The tools are stubs that return fake data and do
NOT touch any real system. Wiring the mock tools to your own integrations
(CRM, database, GitHub, etc.) is the final implementation step — see README.md.

Works with Anthropic OR OpenAI — it auto-detects whichever API key is set.

© 2026 AgentKits · www.agent-kits.com · Licensed CC BY 4.0 (attribution required)
Original blueprint: https://agent-kits.com/kit/campaign-brief-builder

Setup (pick one):
    pip install anthropic   &&  export ANTHROPIC_API_KEY=sk-ant-...
    pip install openai      &&  export OPENAI_API_KEY=sk-...
Then:
    python run.py
"""
import os, json, sys

ANTHROPIC_MODEL = "claude-sonnet-4-6"
OPENAI_MODEL = "gpt-4o"

SYSTEM_PROMPT = """You are a Campaign Brief Builder Agent. You turn provided inputs into a structured marketing campaign brief for a team to refine. You are judged on a useful, well-structured brief and on never fabricating data, guaranteeing results, or committing budget.

== CORE PRINCIPLES ==
1. Ground in the inputs. Build the brief from what's provided (objective, audience, channels, budget, timeline). Don't invent audience segments, market data, or numbers that weren't given.
2. Honest about gaps. Where information is missing — KPIs, budget, audience detail — mark it TBD and flag it, rather than filling it with invented figures. A good brief shows what's undecided.
3. Recommend, don't promise or commit. Suggest channels, messaging, and KPIs. Never guarantee results/ROI, and never commit spend — those are decisions for the marketing lead/stakeholders.

== HARD RULES (NON-NEGOTIABLE) ==
- NO FABRICATED DATA: Never invent audience statistics, market size, benchmark conversion/CTR numbers, or competitor data. Use provided values or mark 'TBD — needs data'.
- NO RESULT GUARANTEES: Never promise a specific ROI, reach, or conversion outcome. Targets are goals/estimates, clearly caveated.
- NO BUDGET COMMITMENT: Propose budget allocation as a recommendation; don't commit or finalize spend.
- FLAG ASSUMPTIONS & DECISIONS: Label assumptions and surface choices needing a marketing lead/stakeholder.
- INFERRED vs PROVIDED: Distinguish what you inferred from what was actually provided.

== METHOD ==
- Take the inputs. Draft the brief sections grounded in them. Mark missing KPIs/audience/budget as TBD. Flag assumptions and decisions. Caveat any projections.

== OUTPUT FORMAT (return ONE JSON object) ==
{
  "objective": "<from input>",
  "audience": "<provided detail, or 'TBD — needs audience data'>",
  "channels": ["<recommended, with rationale>"],
  "messaging": ["<key messages grounded in input>"],
  "kpis": ["<provided targets, or 'TBD — set with team'>"],
  "budget_note": "<allocation recommendation, not a commitment>",
  "timeline": "<from input, or proposed with assumption noted>",
  "assumptions": ["<labeled assumptions>"],
  "decisions_for_lead": ["<choices needing a marketing lead>"],
  "caveat": "Draft brief from provided inputs. No data fabricated; results not guaranteed.",
  "note": "Recommendations only — a marketing lead reviews and approves."
}
Never fabricate data or guarantee results. Mark gaps TBD. Don't commit budget.
"""

SAMPLE_INPUT = """Inputs: 'Objective: drive signups for a new app. Audience: provided — busy professionals 25-40. Channels: LinkedIn, email. Budget: $10k. Timeline: 6 weeks.'
"""

# ---- MOCK tools (stubs — replace with real integrations to deploy) ----
def get_inputs(args=None):
    print(f"  [MOCK] get_inputs(" + str(args) + ")")
    return {"status": "mock", "tool": "get_inputs"}

def structure_brief(args=None):
    print(f"  [MOCK] structure_brief(" + str(args) + ")")
    return {"status": "mock", "tool": "structure_brief"}

def define_audience_or_tbd(args=None):
    print(f"  [MOCK] define_audience_or_tbd(" + str(args) + ")")
    return {"status": "mock", "tool": "define_audience_or_tbd"}

def define_kpis_or_tbd(args=None):
    print(f"  [MOCK] define_kpis_or_tbd(" + str(args) + ")")
    return {"status": "mock", "tool": "define_kpis_or_tbd"}

def channel_plan(args=None):
    print(f"  [MOCK] channel_plan(" + str(args) + ")")
    return {"status": "mock", "tool": "channel_plan"}

def flag_assumptions(args=None):
    print(f"  [MOCK] flag_assumptions(" + str(args) + ")")
    return {"status": "mock", "tool": "flag_assumptions"}

def flag_missing(args=None):
    print(f"  [MOCK] flag_missing(" + str(args) + ")")
    return {"status": "mock", "tool": "flag_missing"}

def no_guarantee_check(args=None):
    print(f"  [MOCK] no_guarantee_check(" + str(args) + ")")
    return {"status": "mock", "tool": "no_guarantee_check"}

TOOLS = {
    "get_inputs": get_inputs,
    "structure_brief": structure_brief,
    "define_audience_or_tbd": define_audience_or_tbd,
    "define_kpis_or_tbd": define_kpis_or_tbd,
    "channel_plan": channel_plan,
    "flag_assumptions": flag_assumptions,
    "flag_missing": flag_missing,
    "no_guarantee_check": no_guarantee_check,
}


def call_model(system, user):
    """Dispatch to whichever provider's key is configured."""
    if os.environ.get("ANTHROPIC_API_KEY"):
        try:
            from anthropic import Anthropic
        except ImportError:
            sys.exit("Run: pip install anthropic")
        msg = Anthropic().messages.create(
            model=ANTHROPIC_MODEL,
            max_tokens=1600,
            system=system,
            messages=[{"role": "user", "content": user}],
        )
        return "".join(b.text for b in msg.content if getattr(b, "type", "") == "text")
    if os.environ.get("OPENAI_API_KEY"):
        try:
            from openai import OpenAI
        except ImportError:
            sys.exit("Run: pip install openai")
        resp = OpenAI().chat.completions.create(
            model=OPENAI_MODEL,
            max_tokens=1600,
            messages=[
                {"role": "system", "content": system},
                {"role": "user", "content": user},
            ],
        )
        return resp.choices[0].message.content
    sys.exit("Set ANTHROPIC_API_KEY or OPENAI_API_KEY first.")


def extract_json(text):
    a, b = text.find("{"), text.rfind("}")
    if a == -1 or b == -1:
        return None
    try:
        return json.loads(text[a : b + 1])
    except json.JSONDecodeError:
        return None


def main():
    print("=" * 70)
    print("INPUT:\n" + SAMPLE_INPUT)
    print("=" * 70)

    text = call_model(SYSTEM_PROMPT, SAMPLE_INPUT)
    print("AGENT DECISION:\n" + text)
    print("=" * 70)

    data = extract_json(text)
    if not data:
        print("(No JSON object parsed — the model returned prose.)")
        return

    actions = data.get("actions") or []
    if actions:
        print("EXECUTING PROPOSED ACTIONS (mock):")
        for a in actions:
            name = a.get("tool")
            args = a.get("args", {})
            if name in TOOLS:
                TOOLS[name](args)
            else:
                print(f"  [skip] no mock tool for '{name}'")
    else:
        print("This blueprint returned a structured result (no side-effecting "
              "actions in this sample). Available mock tools: "
              + ", ".join(TOOLS.keys()))


if __name__ == "__main__":
    main()
