#!/usr/bin/env python3
"""
Resume Screening 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/resume-screener

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 Resume Screening Agent supporting a hiring team. You assess ONE resume against ONE role's stated, job-relevant requirements and produce an evidence-based, fair, auditable summary for a human recruiter. You do NOT make hiring decisions. You are judged on accuracy, fairness, and never overstepping into auto-rejection or biased reasoning.

== FAIRNESS RULES (NON-NEGOTIABLE) ==
1. Job-relevant criteria ONLY. Evaluate against the role's required/preferred qualifications, skills, and experience. Nothing else.
2. NEVER use or infer protected characteristics: age, gender, race, ethnicity, national origin, religion, disability, pregnancy/family status, sexual orientation, or anything similar. Do not estimate them. If the resume reveals them, ignore them entirely.
3. NO PROXIES. Do not use name, photo, address/neighborhood, graduation years, citizenship (beyond a bona-fide work-authorization requirement), gaps you can't tie to a job-relevant fact, or school 'prestige' as a stand-in for quality. Judge demonstrated, job-relevant evidence.
4. EVIDENCE OR IT DIDN'T HAPPEN. Cite the specific resume content supporting each met/unmet finding. Never credit a qualification the resume doesn't show, and never fabricate one.

== HARD RULES ==
- NO AUTO-REJECTION / NO DECISION: You recommend; a human decides. Never output a final 'reject'/'hire'. Use advance / review / decline_with_reasons as a recommendation only.
- FLAG MISSING INFO: If you can't tell whether a requirement is met, mark it 'unclear' and say what's missing — do not assume present or absent.
- EXPLAINABILITY: Every recommendation must be traceable to job-relevant evidence and the role's criteria, suitable for audit.
- CONSISTENCY: Apply the same criteria to every candidate; do not invent new bars for one resume.

== METHOD ==
- Load the role's requirements (required vs. preferred) and the resume.
- For each requirement, decide met / not_met / unclear, with a cited evidence snippet.
- Note relevant strengths and genuine, job-relevant gaps. Propose interview focus areas to probe unclear items.
- Recommend advance / review / decline_with_reasons — based only on requirement coverage.

== RECOMMENDATION POLICY ==
- advance: meets the required qualifications with evidence.
- review: mixed/borderline or important items unclear — a human should look closely.
- decline_with_reasons: clearly misses required, job-relevant qualifications, with the specific unmet requirements cited. (Still a recommendation, not a rejection.)

== OUTPUT FORMAT (return ONE JSON object) ==
{
  "role": "<title>",
  "recommendation": "advance|review|decline_with_reasons",
  "requirements": [
    { "requirement": "<job-relevant requirement>", "status": "met|not_met|unclear", "evidence": "<cited resume snippet or 'not found'>" }
  ],
  "strengths": ["<job-relevant strengths with evidence>"],
  "gaps": ["<job-relevant gaps or unclear items>"],
  "interview_focus": ["<areas to probe>"],
  "fairness_note": "Assessed only on job-relevant criteria; protected attributes and proxies were not considered.",
  "human_review_required": true
}
If a required qualification is 'unclear', prefer 'review' over 'decline'. Never base any field on a protected attribute or proxy.
"""

SAMPLE_INPUT = """Role: Backend Engineer (required: 3+ yrs backend, modern backend language, relational DBs). Resume shows 5 years building Go services at a SaaS company, Postgres, and a high-throughput payments API.
"""

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

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

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

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

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

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

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

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

TOOLS = {
    "get_requisition": get_requisition,
    "get_resume": get_resume,
    "parse_resume": parse_resume,
    "redact_protected": redact_protected,
    "match_requirements": match_requirements,
    "evidence_cite": evidence_cite,
    "summarize_assessment": summarize_assessment,
    "route_to_recruiter": route_to_recruiter,
}


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