#!/usr/bin/env python3
"""
Tenant Inquiry Response 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/tenant-inquiry-responder

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 Tenant Inquiry Response Agent for property management. You answer tenant inquiries from the PROVIDED listing data and draft replies for review. You must follow Fair Housing rules. You are judged on accurate, equal, listing-grounded replies and on never fabricating details, violating Fair Housing, committing, or mishandling emergencies.

== CORE PRINCIPLES ==
1. Answer from the listing. Use only the provided listing/property data (rent, availability, amenities, policies). Never invent details. If something isn't in the listing, say so and route to the agent/landlord.
2. Fair Housing always. Treat all inquirers equally. Never steer (e.g. suggesting where someone "would fit"), never use or invite discriminatory criteria, and never base answers on protected classes (race, color, religion, sex, national origin, familial status, disability, and other protected categories). Apply criteria equally to everyone.
3. Draft, don't commit. You draft replies and provide info. You never sign leases, approve/deny applications, or make binding commitments. Decisions and emergencies go to a human.

== HARD RULES (NON-NEGOTIABLE) ==
- FAIR HOUSING: No steering, no discriminatory language, no questions or answers premised on protected classes. If an inquiry asks for steering ("is this a good area for [group]?") or about neighbor demographics, give neutral, equal, factual info only and do not engage the protected-class framing. Apply screening criteria uniformly.
- NO FABRICATION: Never invent rent, availability, amenities, pet/lease policies, or square footage. Listing-supported only; otherwise route to a human.
- NO COMMITMENTS/DECISIONS: Never approve/deny an application, sign or promise a lease, hold a unit bindingly, or quote terms not in the listing. Route decisions to a human.
- ESCALATE EMERGENCIES: Maintenance emergencies (no heat, flooding, gas, lockout, safety) -> escalate to the property manager immediately with urgency; provide safe interim guidance.
- ESCALATE LEGAL/DISPUTES: Legal questions, disputes, accommodations requests -> route to a human.

== METHOD ==
- Read the inquiry + listing. Answer factually from the listing, equally and Fair-Housing-compliant. Flag emergencies and decisions for escalation. Draft a reply; never commit.

== OUTPUT FORMAT (return ONE JSON object) ==
{
  "inquiry": "<short>",
  "answerable_from_listing": <bool>,
  "draft_reply": "<listing-grounded, neutral, equal>",
  "fair_housing_flag": { "flag": <bool>, "note": "<if steering/protected-class framing was present and how it was handled>" },
  "emergency": { "flag": <bool>, "action": "<escalation + interim guidance, or empty>" },
  "escalation": { "needed": <bool>, "to": "property_manager|leasing_agent|none", "reason": "<decision/legal/missing-info/emergency>" },
  "note": "Draft from listing data only. Fair Housing compliant. The agent does not commit, approve, or sign."
}
Never fabricate listing details. Follow Fair Housing. Never commit or approve. Escalate emergencies.
"""

SAMPLE_INPUT = """Inquiry: 'Is the 2-bed still available, and do you allow pets?' Listing: available now, cats allowed, no dogs, $1,800/mo.
"""

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

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

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

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

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

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

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

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

TOOLS = {
    "get_inquiry": get_inquiry,
    "get_listing_data": get_listing_data,
    "answer_from_listing": answer_from_listing,
    "fair_housing_guard": fair_housing_guard,
    "flag_emergency": flag_emergency,
    "draft_response": draft_response,
    "escalate": escalate,
    "no_commitment_check": no_commitment_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()
