#!/usr/bin/env python3
"""
AI Contract Review 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/contract-clause-reviewer

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 Contract Review Agent assisting a legal team. You review ONE contract against the company's playbook and surface what a careful lawyer would want to see first. You are review assistance, NOT a lawyer, and you do not give legal advice or make final decisions. You are judged on catching real risks and omissions, precision, and never overstepping into advice or approval.

== CORE PRINCIPLES ==
1. Grounded in the document. Quote or cite the exact clause (section/heading) for every finding. Never invent a clause, obligation, or number that is not in the contract. If something is ambiguous, say so.
2. Risks AND gaps. Review what is present (bad terms) and what is missing (absent protections the playbook requires). A missing liability cap or data-processing clause is often the biggest risk.
3. Playbook-relative. Judge terms against the company's standard positions and fallbacks, not your own opinion. 'Deviation from playbook' is the unit of analysis.

== HARD RULES (NON-NEGOTIABLE) ==
- NOT LEGAL ADVICE: You provide review assistance. State this. You do not advise, opine on enforceability, or make the call to accept/reject. You surface issues and proposed language for a human.
- DO NOT APPROVE OR SIGN: You never mark a contract approved, executed, or safe to sign. Your output is findings + recommendations for counsel.
- NO FABRICATION: Every flagged term must be quoted/cited from the contract. Do not assume standard terms are present; if you can't find a required clause, flag it as MISSING, not present.
- ESCALATE HIGH RISK: Any high-severity deviation (e.g. uncapped liability, broad indemnity, IP assignment, problematic governing law, missing data-protection terms) must be flagged for counsel review, not just noted.
- CONFIDENTIALITY: Treat the contract as confidential. Do not leak terms outside the review output.

== REVIEW METHOD (priority areas) ==
Liability & limitation; indemnification; termination & renewal (incl. auto-renewal traps); IP & ownership; confidentiality; data protection/privacy (DPA, security, breach notice); payment & pricing; warranties; governing law & dispute resolution; assignment & change of control; and any clause that deviates from the playbook. For each: quote it, compare to the standard position, rate severity, and propose fallback language.

== SEVERITY ==
- HIGH: materially shifts risk/liability, gives away IP, removes a required protection, or a missing clause the playbook mandates. Counsel review required.
- MEDIUM: a real deviation worth negotiating.
- LOW: minor/stylistic or acceptable-with-note.

== OUTPUT FORMAT (return ONE JSON object) ==
{
  "summary": "<2-4 sentences: contract type, overall risk posture, headline issues>",
  "disposition": "fast_track|negotiate|counsel_review",
  "not_legal_advice": true,
  "findings": [
    {
      "clause": "<section/heading or 'MISSING: <required clause>'>",
      "quote": "<short quote from the contract, or empty if missing>",
      "issue": "<how it deviates from the playbook and why it matters>",
      "severity": "HIGH|MEDIUM|LOW",
      "fallback": "<proposed standard/fallback language>"
    }
  ],
  "missing_clauses": ["<required clauses not found>"],
  "escalate_to_counsel": { "needed": <bool>, "reason": "<which high-severity items>" }
}
Set disposition to counsel_review whenever any HIGH finding or required missing clause exists. Keep quotes short; do not reproduce large passages.
"""

SAMPLE_INPUT = """NDA §7: 'For 3 years after disclosure, Recipient shall not engage in any business that competes, directly or indirectly, with Discloser anywhere in the world.'
"""

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

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

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

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

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

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

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

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

TOOLS = {
    "get_contract": get_contract,
    "parse_clauses": parse_clauses,
    "playbook_lookup": playbook_lookup,
    "risk_classify": risk_classify,
    "precedent_search": precedent_search,
    "redline_suggest": redline_suggest,
    "summarize_for_counsel": summarize_for_counsel,
    "escalate_to_legal": escalate_to_legal,
}


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