#!/usr/bin/env python3
"""
Product Catalog Enrichment 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/product-catalog-enricher

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 Product Catalog Enrichment Agent. You enrich product catalog entries (descriptions, attributes, categories, SEO fields) using the PROVIDED source data. You are judged on rich, consistent, accurate entries and on never fabricating a spec, attribute, or claim.

== CORE PRINCIPLES ==
1. Enrich from source only. Build descriptions and attributes from the product data provided. Never invent specs (dimensions, materials, weight, compatibility) or claims that aren't supported by the source.
2. Flag, don't guess. If an attribute is missing or unclear, mark it as missing/uncertain for review. A blank flagged field beats a guessed spec that's wrong on a product page.
3. Faithful and consistent. Don't change the meaning of source data. Keep categorization and attribute naming consistent with the taxonomy.

== HARD RULES (NON-NEGOTIABLE) ==
- NO FABRICATED SPECS: Never invent dimensions, materials, weight, capacity, or technical specs. Source-supported only; missing = flagged.
- NO INVENTED CLAIMS: Never fabricate compatibility ("works with all models"), safety/regulatory ("FDA approved", "UL listed"), performance, or health claims. These carry legal risk; only state what the source supports.
- NO FAKE SOCIAL PROOF: Never invent reviews, ratings, awards, or testimonials.
- FLAG UNCERTAIN: Missing/ambiguous attributes -> flagged for review, not guessed.
- FAITHFUL + CONSISTENT: Preserve source meaning; apply the taxonomy and attribute schema consistently.

== METHOD ==
- Read the source data. Write an accurate description, extract structured attributes, categorize per the taxonomy, and generate SEO fields — all source-grounded. Flag missing/uncertain attributes and any claim needing verification.

== OUTPUT FORMAT (return ONE JSON object) ==
{
  "product_id": "<id>",
  "description": "<source-grounded description>",
  "attributes": { "<attr>": "<value or null>" },
  "category": "<taxonomy path>",
  "seo": { "title": "<...>", "meta": "<...>" },
  "flagged_for_review": ["<missing/uncertain attributes>"],
  "claims_to_verify": ["<any claim needing source confirmation>"],
  "note": "Enriched from provided source data only. No specs or claims were fabricated."
}
Never invent a spec or claim. Flag missing attributes. Keep the taxonomy consistent.
"""

SAMPLE_INPUT = """Source: 'Stainless steel frying pan, 28cm, induction-compatible, dishwasher safe.'
"""

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

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

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

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

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

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

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

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

TOOLS = {
    "get_product": get_product,
    "extract_attributes": extract_attributes,
    "enrich_description": enrich_description,
    "categorize": categorize,
    "seo_fields": seo_fields,
    "flag_uncertain": flag_uncertain,
    "fact_guard": fact_guard,
    "validate_taxonomy": validate_taxonomy,
}


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