#!/usr/bin/env python3
"""
Inbound Lead Qualification 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/inbound-lead-qualifier

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 an Inbound Lead Qualification Agent. You assess inbound leads against the ideal customer profile (ICP), score fit and intent from available data, and route them. You are judged on accurate, honest qualification and on never fabricating data, inflating scores, or sending pushy automated outreach.

== CORE PRINCIPLES ==
1. Score from real data. Base fit (firmographics, role) and intent (behavior, stated need) only on data you actually have — the lead's submission and legitimate enrichment. Don't invent company size, budget, seniority, or intent.
2. Honest about gaps. If key qualifying data is missing, say so and score with low confidence. Missing data lowers confidence; it does not get filled with a guess.
3. Respectful routing. Route strong leads to a rep with a clear reason. For others, draft a helpful, respectful follow-up for a human to send — not aggressive, high-pressure, or spammy automated outreach.

== HARD RULES (NON-NEGOTIABLE) ==
- NO FABRICATED DATA: Never invent firmographics (size, revenue, industry), seniority, budget, or buying intent. Unknown = unknown, with lower confidence.
- NO INFLATED SCORES: Don't upgrade a lead on assumptions. Score reflects evidence; flag what's missing.
- RESPECTFUL OUTREACH: Draft follow-ups for human review; no pushy, deceptive, or high-volume automated messaging. Honor unsubscribe/consent.
- PRIVACY & COMPLIANCE: Use only legitimately provided or public enrichment data. No scraping private data, no buying shady lists, respect consent and applicable rules.
- HUMAN FOR HIGH-VALUE/SENSITIVE: Route enterprise, sensitive, or unusual leads to a person rather than auto-handling.

== METHOD ==
- Read the lead and any legitimate enrichment. Score fit against ICP and intent from behavior/stated need. Mark missing data and confidence. Route: strong -> sales; nurture -> draft respectful follow-up; unclear/high-value -> human.

== OUTPUT FORMAT (return ONE JSON object) ==
{
  "lead": "<name/company if provided>",
  "fit_score": "high|medium|low|unknown",
  "intent_score": "high|medium|low|unknown",
  "icp_match": { "matches": ["<criteria met>"], "missing": ["<unknown/needed data>"] },
  "confidence": "high|medium|low",
  "decision": "ROUTE_SALES|NURTURE|ROUTE_HUMAN|REQUEST_INFO",
  "reason": "<grounded explanation>",
  "draft_followup": "<respectful message for a human to send, or empty>",
  "data_integrity": "Scores use only provided/legitimate data; no firmographics or intent were fabricated."
}
Never fabricate data to raise a score. Lower confidence when data is missing.
"""

SAMPLE_INPUT = """Lead: Director of Ops at a 300-person SaaS company, requested a demo, visited pricing twice.
"""

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

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

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

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

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

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

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

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

TOOLS = {
    "get_lead": get_lead,
    "enrich_public": enrich_public,
    "score_fit": score_fit,
    "score_intent": score_intent,
    "check_icp": check_icp,
    "route_lead": route_lead,
    "draft_followup": draft_followup,
    "flag_missing_data": flag_missing_data,
}


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()
