Jev AI

Support triage with speculative fan-out

Route a ticket to the right queue while collecting the signals that may matter later: intent, urgency, frustration, bug severity, reproducibility, and refund intent.

NOUL×3SCORE×2CHOICE×1
UNSTRUCTUREDSTATEJEVCHOICE+ confidenceSCORE+ confidenceNOUL+ confidenceCODE-OWNEDROUTINGACTESCALATEREVIEW
One request, 6 parallel answers (three shown). The model never performs the side effect — your code reads the confidences and decides.
01
STATE
state = {
    "ticket": {
        "subject": "Payments fail at checkout",
        "messages": [
            {"from": "customer", "text": "Checkout has failed for three days. We are losing sales."},
        ],
    },
    "account": {"tier": "business", "region": "us"},
    "product": {"known_incidents": ["stripe-connect-degraded"]},
}

Use a named object when the decision depends on more than one piece of context.

1 リクエスト / 6 回答
このパターンを一行で
02
QUESTIONS
from typesafe_sdk import Choice, Noul, Score

questions = {
    "intent": Choice(
        instructions="What is the primary reason for contact in `ticket.messages[0].text`?",
        criteria={
            "bug_report": "A product defect, outage, or integration failure.",
            "billing": "A charge, refund, invoice, or subscription issue.",
            "feature_request": "A request for a capability that does not exist yet.",
            "information": "A question that can be answered without an incident workflow.",
            "other": "None of the options clearly fits.",
        },
    ),
    "is_urgent": Noul(
        instructions="Does `ticket.messages[0].text` explicitly communicate time pressure or immediate business impact?",
    ),
    "frustration": Score(
        instructions="How frustrated does the customer appear in `ticket.messages[0].text`?",
        criteria=[
            "Calm and neutral",
            "Concerned but civil",
            "Very angry or using strong language",
        ],
    ),
    # These are intentionally speculative: code will ignore them for non-bug tickets.
    "bug_severity": Score(
        instructions="If this is a bug report, how severe is the user impact?",
        criteria=[
            "Minor inconvenience or workaround available",
            "Material degradation affecting some users",
            "Critical outage blocking a core workflow",
        ],
    ),
    "has_reproducible_steps": Noul(
        instructions="Does `ticket.messages` contain enough steps or evidence for an engineer to reproduce the issue?",
    ),
    "refund_requested": Noul(
        instructions="Does `ticket.messages` request a refund or reversal of a charge?",
    ),
}
03
CODE-OWNED ROUTING
def route_ticket(response) -> str:
    intent = response.answers["intent"]
    if intent.confidence < 0.6:
        return "human_review"

    if intent.choice == "bug_report":
        severity = response.answers["bug_severity"].score
        reproducible = response.answers["has_reproducible_steps"].noul
        if severity >= 1.5 and reproducible >= 0.6:
            return "engineering_escalation"
        return "bug_backlog"

    if intent.choice == "billing":
        return "billing_with_refund_flag" if response.answers["refund_requested"].noul >= 0.7 else "billing"

    if intent.choice == "feature_request":
        return "product_feedback"
    if intent.choice == "information":
        return "self_serve_or_support_llm"
    return "human_review"

The fan-out avoids serial “classify, then ask about severity, then ask about refund” calls. The unanswered dimensions are cheap to ignore when they are irrelevant. See the official speculative fan-out pattern.