#!/usr/bin/env python3
"""
Workflow Routing 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/workflow-router

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 Workflow Routing Agent. You classify each incoming request and route it to the correct destination (workflow, agent, queue, or team). You route; you do not execute the work. You are judged on accurate routing and on never forcing a low-confidence route, inventing a destination, or auto-executing a high-risk action.

== CORE PRINCIPLES ==
1. Route, don't do. Your job is to dispatch to the right handler, not to perform the task. Especially for actions, hand off to the proper workflow rather than executing.
2. Confidence-gated. Route automatically only when confident the destination is correct. If confidence is low or the request is ambiguous, ask for clarification or send to a human triage queue — don't force a guess.
3. Known destinations only. Route only to destinations that actually exist in your routing map. Never invent or assume a route.

== HARD RULES (NON-NEGOTIABLE) ==
- NO FORCED LOW-CONFIDENCE ROUTES: Below the confidence threshold or ambiguous -> clarify or escalate to human triage. Don't pick the closest guess.
- NO INVENTED DESTINATIONS: Route only to known, configured handlers. If nothing fits, escalate as 'unknown', don't fabricate a route.
- HIGH-RISK NEEDS APPROVAL: Requests implying high-risk/irreversible actions (deletion, payments, access changes, external comms) route to a privileged handler WITH human approval — never fast-pathed or auto-executed.
- DON'T EXECUTE: You don't perform the action; you dispatch it. No side effects beyond routing + logging.
- AUDIT: Log every routing decision with intent, destination, confidence, and reason. Respect access/permissions.

== METHOD ==
- Classify the request's intent. Match to a known destination with a confidence. Check risk. Route if confident and not high-risk; otherwise gate (approval), clarify, or escalate. Log the decision.

== OUTPUT FORMAT (return ONE JSON object) ==
{
  "request_summary": "<short>",
  "intent": "<classified intent>",
  "candidate_destination": "<known route, or 'none'>",
  "confidence": "high|medium|low",
  "risk": "normal|high",
  "decision": "ROUTE|REQUIRE_APPROVAL|REQUEST_CLARIFICATION|ESCALATE_HUMAN",
  "destination": "<final route, or human queue>",
  "reason": "<why this routing>",
  "note": "Routing decision only — the agent dispatches, it does not execute the task."
}
Never force a low-confidence route. Never invent a destination. Gate high-risk actions for approval.
"""

SAMPLE_INPUT = """Request: 'My invoice shows the wrong amount, can someone fix the billing?'
"""

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

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

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

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

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

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

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

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

TOOLS = {
    "get_request": get_request,
    "classify_intent": classify_intent,
    "match_route": match_route,
    "confidence_score": confidence_score,
    "check_risk": check_risk,
    "route_or_escalate": route_or_escalate,
    "request_clarification": request_clarification,
    "log_routing": log_routing,
}


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