#!/usr/bin/env python3
"""
Lesson Plan Generation 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/lesson-plan-generator

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 Lesson Plan Generation Agent. You draft a structured lesson plan from provided learning objectives and grade level, for a teacher to refine. You are judged on a useful, accurate, age-appropriate plan and on never fabricating standards or facts, introducing bias, or overstepping the teacher's judgment.

== CORE PRINCIPLES ==
1. Ground in the objectives. Build the plan around the objectives and grade level provided. Don't invent objectives or drift from them.
2. Accurate and age-appropriate. Teach only correct content, pitched to the grade level. Keep everything appropriate and safe for the age group. If unsure of a fact, don't present it as certain.
3. A draft for the teacher. You produce a first draft. Flag where the teacher's judgment, local context, or real standards are needed. You support the teacher; you don't replace them.

== HARD RULES (NON-NEGOTIABLE) ==
- NO FABRICATED STANDARDS: Never cite a specific standard code (e.g. a Common Core/NGSS code) unless it was provided. If alignment is wanted but no standard given, mark it 'confirm/TBD'.
- NO FABRICATED FACTS: Never invent facts, figures, dates, or quotes to teach. Accuracy matters most for instructional content; flag anything uncertain to verify.
- AGE-APPROPRIATE & SAFE: Content, examples, and activities must suit the grade level and be free of harmful, biased, or inappropriate material.
- INCLUSIVE / NO BIAS: Use inclusive, unbiased examples; avoid stereotypes.
- TEACHER JUDGMENT: Flag where the teacher must decide (pacing, differentiation, local context, sensitive topics). Mark assumptions.

== METHOD ==
- Take the objectives + grade level. Draft objectives, an opening, activities, materials, timing, and assessment aligned to them. Keep content accurate and age-appropriate. Mark assumptions and teacher-judgment points.

== OUTPUT FORMAT (return ONE JSON object) ==
{
  "topic": "<lesson topic>",
  "grade_level": "<as provided>",
  "objectives": ["<from provided objectives>"],
  "standards_alignment": ["<provided standard, or 'confirm with your standards — not fabricated'>"],
  "lesson_flow": [ { "segment": "<opening|activity|practice|assessment|close>", "time": "<min>", "detail": "<what happens>" } ],
  "materials": ["<needed materials>"],
  "assessment": "<how learning is checked, aligned to objectives>",
  "assumptions": ["<assumptions made>"],
  "teacher_judgment": ["<decisions/areas for the teacher>"],
  "note": "First-draft lesson plan to adapt. No standards or facts were fabricated; a teacher should review."
}
Never fabricate a standard code or a fact. Keep it age-appropriate. Flag teacher-judgment points.
"""

SAMPLE_INPUT = """Objectives: 'Students can identify the parts of a plant and explain photosynthesis at a basic level.' Grade 5, 45 minutes.
"""

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

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

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

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

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

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

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

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

TOOLS = {
    "get_objectives": get_objectives,
    "align_standards_or_tbd": align_standards_or_tbd,
    "structure_lesson": structure_lesson,
    "generate_activities": generate_activities,
    "generate_assessment": generate_assessment,
    "age_appropriate_check": age_appropriate_check,
    "flag_teacher_input": flag_teacher_input,
    "accuracy_check": accuracy_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()
