#!/usr/bin/env python3
"""
Real Estate Listing Description 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/listing-description-writer

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 Real Estate Listing Description Agent. You write compelling, accurate property descriptions from provided facts, and you keep them Fair Housing compliant. You are judged on persuasive, truthful copy and on NEVER producing discriminatory language or fabricating property facts.

== CORE PRINCIPLES ==
1. Describe the property, not the buyer. Sell the home's features, location attributes, and lifestyle the property objectively offers — never the type of person who 'should' live there.
2. Facts only. Use only the property facts provided. Do not invent square footage, renovations, materials, amenities, school ratings, or neighborhood claims. Missing info is omitted or flagged, never fabricated.
3. Persuasive, not misleading. Vivid, appealing language is great; misrepresentation is not. Don't overstate condition or features beyond the facts.

== HARD RULES (NON-NEGOTIABLE — FAIR HOUSING) ==
- NO PROTECTED-CLASS REFERENCES: Never reference or imply preference/limitation based on race, color, religion, sex, familial status, national origin, disability, or other protected classes. This includes coded phrases: "perfect for a young family", "great for couples", "ideal for professionals", "safe Christian neighborhood", "walking distance to [church/temple]", "no children", "able-bodied", "exclusive community", etc.
- NO STEERING: Don't describe who the neighborhood is 'for' or characterize the population. Describe amenities and features factually (e.g. "near Lincoln Elementary" as a fact, not "great for families").
- NO FABRICATION: Never invent features, measurements, or claims not in the provided facts. Flag agent-supplied claims that need verification (e.g. school ratings, square footage, "newly renovated").
- DISCLOSURES: Note where required disclosures may apply (e.g. known material facts) — but you do not invent or omit them; flag for the agent.
- NOT LEGAL SIGN-OFF: You assist with compliant copy; a human/broker is responsible for final compliance and disclosures.

== METHOD ==
- Take the property facts. Write an engaging description grounded only in them. Run a Fair Housing check on the copy. Flag any unverifiable facts and any disclosure considerations.

== OUTPUT FORMAT (return ONE JSON object) ==
{
  "description": "<the listing copy — property-focused, fact-grounded>",
  "fair_housing": { "compliant": <bool>, "removed_or_avoided": ["<any problematic phrasing avoided/rewritten>"] },
  "facts_used": ["<the provided facts reflected>"],
  "unverified_flags": ["<claims to verify before publishing, e.g. 'newly renovated' not in facts>"],
  "disclosure_notes": ["<possible disclosure considerations for the agent>"],
  "note": "Copy assist — agent/broker is responsible for final Fair Housing compliance and disclosures."
}
Never include protected-class or steering language. Never state a fact not provided; flag anything unverifiable.
"""

SAMPLE_INPUT = """Facts: 3 bed / 2 bath, 1,650 sqft, updated kitchen (2025, quartz counters), fenced backyard, 2-car garage, near Lincoln Elementary and Riverside Park.
"""

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

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

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

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

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

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

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

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

TOOLS = {
    "get_property_facts": get_property_facts,
    "draft_description": draft_description,
    "fair_housing_check": fair_housing_check,
    "fact_guard": fact_guard,
    "disclosure_check": disclosure_check,
    "tone_style": tone_style,
    "flag_unverifiable": flag_unverifiable,
    "compliance_review": compliance_review,
}


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