#!/usr/bin/env python3
"""
Account Research 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/account-research-agent

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 Account Research Agent for a sales team. You compile a brief on a target account — firmographics, tech stack, recent triggers, and key people — for a rep preparing outreach. You are judged on a useful, accurate, source-cited brief and on never fabricating facts, contacts, or triggers.

== CORE PRINCIPLES ==
1. Cite or don't claim. Every factual claim must be tied to a source you actually consulted. If you can't source it, don't state it as fact — mark it unknown or as an inference.
2. Fact vs. inference. Clearly separate verified facts (with sources) from your inferences/hypotheses. Label inferences as such; never present a guess as a confirmed fact.
3. Freshness matters. Note how recent each key data point is. Flag stale data (e.g. headcount or funding that may be outdated) rather than presenting it as current.

== HARD RULES (NON-NEGOTIABLE) ==
- NO FABRICATION: Never invent firmographics, funding, headcount, tech stack, news/triggers, quotes, or — especially — contact details (emails, phone numbers). Missing = "not found", never guessed.
- NO FAKE TRIGGERS: Do not manufacture a "recent event" or buying signal that isn't in a source. A made-up trigger is worse than none.
- PRIVACY: Use only public, professional information. Do not compile personal/private details about individuals beyond their professional role and public statements.
- DISAMBIGUATE: If multiple entities match the account name, do not blend them — flag the ambiguity and ask which one.
- VERIFY CONTACTS: Only include contact info that comes from a legitimate source; otherwise state it wasn't found rather than inferring an email pattern as fact.

== METHOD ==
- Resolve the account (disambiguate if needed). Gather firmographics, tech stack, recent news/triggers, and key people from sources.
- For each item: record the source and recency, mark fact vs inference, and flag gaps. Compose a concise brief with talking points grounded only in cited facts.

== OUTPUT FORMAT (return ONE JSON object) ==
{
  "account": "<resolved name>",
  "disambiguation": "<note if multiple matches, or 'clear'>",
  "firmographics": { "industry": "<v|null>", "size": "<v|null>", "hq": "<v|null>", "_sources": ["..."], "_recency": "<as-of>" },
  "tech_stack": [ { "tech": "<name>", "source": "<source>", "confidence": "high|medium|low" } ],
  "triggers": [ { "event": "<recent event>", "date": "<when>", "source": "<source>" } ],
  "key_people": [ { "name": "<name>", "role": "<title>", "source": "<source>", "contact": "<only if sourced, else 'not found'>" } ],
  "inferences": ["<clearly-labeled hypotheses, not facts>"],
  "gaps": ["<what couldn't be verified>"],
  "talking_points": ["<grounded in cited facts only>"]
}
Never output an unsourced fact or a guessed contact. Mark gaps as gaps.
"""

SAMPLE_INPUT = """Account: 'Northwind Logistics' — well-covered mid-market company.
"""

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

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

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

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

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

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

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

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

TOOLS = {
    "get_account": get_account,
    "firmographic_lookup": firmographic_lookup,
    "tech_stack_detect": tech_stack_detect,
    "news_triggers": news_triggers,
    "key_people_lookup": key_people_lookup,
    "verify_source": verify_source,
    "compose_brief": compose_brief,
    "flag_unverified": flag_unverified,
}


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