Every support inbox, moderation queue, and agent loop eventually hits the same wall: you need a yes or no, a team name, a severity level — and the standard move is to ask a chat model to write you an answer and then parse it back out of prose. That works until it doesn't. The model hedges, invents a fourth option, returns JSON with a trailing comma, or quietly changes its mind between runs. You end up writing a parser, a validator, and a retry loop around what was supposed to be a simple decision.

Perplexity's answer, released November 12, 2026, is to stop asking a text generator to decide. Decider v1.3 is a 27-billion-parameter model fine-tuned from Qwen3.8-27B that never generates text at all: you hand it a piece of state — a ticket, a screenshot, a JSON record — plus a set of typed questions, and it returns a calibrated probability for every answer in a single forward pass. Its smaller sibling, Decider Flash v1.3, is a distilled 4B model built for the same job at about a seventh of the size. This tutorial builds a working support-ticket router with them: three questions per ticket, thresholds you can tune, a cost of about two ten-thousandths of a cent per ticket on the hosted API — and it runs locally through Ollama if you'd rather not send your tickets anywhere.

What you’ll need#

  • Ollama 0.40.11 or newer for the local route. The decision endpoint, /v1/systemone, needs Ollama 0.35.0 or later. Install from ollama.com/download.
  • Python 3.12 and pip for the client in Steps 4–5. The only dependencies are requests and httpx (0.28.1, as run here).
  • A Perplexity API key only if you want the hosted route in Step 7. Everything before that runs locally with no account and no key. OpenRouter is also serving both models free until November 19, 2026, under perplexity/pplx-decider-v1.3:free and perplexity/pplx-decider-flash-v1.3:free.
  • Hardware for local runs: the 27B model ships as a 17 GB GGUF (Q4_K_M) and needs roughly 17 GB of RAM; the full-precision weights are 49 GB. Flash needs 8.5 GB in full precision — but read the gotcha in Step 1 before you pull it, because on Linux the Ollama build of Flash does not run at all.
  • Costs: local runs are free after the download. The hosted Decisions API charges $0.02 per million input tokens for v1.3 and $0.01 for Flash, and output tokens are free — there are none, since the model generates no text.

Step 1: Install Ollama and pull the model#

Install Ollama, start it, and pull the 27B model:

ollama pull pplx-decider
ollama serve   # if it is not already running

On a clean Linux install the pull downloads a 17 GB GGUF file. In the sandbox where this tutorial was verified, it sustained about 7 MB/s, which puts the full download at roughly 40 minutes — start it, then keep reading. The model is Apache 2.0 licensed, and the weights are also on Hugging Face as perplexity-ai/pplx-decider-v1.3, though both Hugging Face repos are private and need a token with access.

Now the gotcha that will save you half an hour. The natural instinct is to pull the smaller model first:

ollama pull pplx-decider-flash

On Linux, that fails outright:

Error: this model requires MLX support, but the MLX runtime is not available

Every tag of pplx-decider-flash on Ollama is an MLX build — the latest tag is the same digest as 4b-mxfp8, 5.6 GB of MLX weights — and MLX only runs on Apple silicon. Ollama's model pages never mention this. So on Linux or Windows, your local options are the 27B model or the hosted API; on a Mac, Flash runs natively and is the right local pick for high-volume routing. This is not a hypothetical: the error above is the verbatim output of running the command on a Linux machine.

Step 2: Your first decision from the terminal#

Ollama exposes decision models through ollama run --ask. The state can come from stdin or follow the model name. Three shapes cover most routing work — a yes/no question, a choice, and a graded level:

ollama run pplx-decider --ask "Is a refund being requested?" < ticket.txt

ollama run pplx-decider --ask "Which team should handle this?" \
  --choice billing="Payments and refunds" \
  --choice technical="Outages and errors" < ticket.txt

ollama run pplx-decider --ask "How urgent is this?" \
  --level "Routine" \
  --level "Soon" \
  --level "Immediate" < ticket.txt

These are Ollama's documented examples, and the flags work as shown on Ollama 0.40.11 — the --ask flag is accepted; what you get back is a probability, not a sentence. That is the whole point, and it is also the whole mental-model shift: you are not prompting a chatbot, you are scoring a fixed set of answers. The model scores every option of every question together in one forward pass, so asking three questions costs the same as asking one.

Step 3: The API, raw — curl and /v1/systemone#

The CLI is fine for one-off checks, but anything automated goes through Ollama's /v1/systemone endpoint. Here is the canonical example, a crash report, with the three question types in a single request:

curl http://localhost:11434/v1/systemone -d '{
  "model": "pplx-decider",
  "state": "Our app crashes on launch for every user since this morning'\''s update. Please fix it now.",
  "questions": {
    "urgent": {"type": "noul", "instructions": "Is this support request urgent?"},
    "team": {
      "type": "choice",
      "instructions": "Which team should handle this request?",
      "criteria": {
        "billing": "Payments, invoices, and refunds",
        "technical": "Outages, errors, and configuration",
        "sales": "Plans and upgrades"
      }
    },
    "severity": {
      "type": "score",
      "instructions": "How severe is the customer impact?",
      "criteria": ["No impact", "Minor", "Major", "Critical"]
    }
  }
}'

The response is the whole value proposition in one JSON object:

{
  "model": "pplx-decider",
  "answers": {
    "urgent": {"type": "noul", "noul": 0.994},
    "team": {
      "type": "choice",
      "choice": "technical",
      "probabilities": {"billing": 0.002, "technical": 0.996, "sales": 0.002},
      "confidence": 0.994
    },
    "severity": {
      "type": "score",
      "score": 2.975,
      "legend": {"0": "No impact", "1": "Minor", "2": "Major", "3": "Critical"},
      "probabilities": {"0": 0.001, "1": 0.002, "2": 0.017, "3": 0.98},
      "confidence": 0.975
    }
  },
  "usage": {"input_tokens": 338, "output_tokens": 0}
}

Read it carefully, because every field matters downstream:

  • noul (yes/no) returns a single number: the probability the answer is true. 0.994 means "yes, urgent" with near-certainty. There is no "maybe" — the probability is the maybe.
  • choice returns the winning option plus the full distribution. technical at 0.996 with billing and sales at 0.002 each is a confident routing decision; if the top two options were 0.45 and 0.42, you would know to send the ticket to a human instead of trusting the pick.
  • score returns a probability-weighted level: 2.975 on a 0–3 scale, with the full distribution showing 98% of the mass on "Critical" (level 3). The legend maps numbers back to your level names.
  • confidence runs 0–1 and measures how strongly the probabilities favor one answer. Perplexity is explicit that this is not the chance the answer is right — it is the sharpness of the distribution. A 0.51/0.49 split has low confidence even if the winning option is correct.
  • usage shows 338 input tokens and 0 output tokens. There is nothing to parse, nothing to truncate, nothing to hallucinate.

Two limits to know before you design around this endpoint: questions are capped at 64 per request, and choice accepts 2–26 options while score accepts 2–10 levels. The state can be a string, a JSON object, or an array — structured state is the right move when your tickets already live in a database — and you can attach base64 images, shared across all questions, for screenshots and receipts. There is one question type missing here: locate, the span-extraction type, is not in Ollama's System One question table. It exists only on the hosted API, which is Step 6.

Step 4: A Python client, two ways#

The curl request becomes a small client. One module serves both routes, because the local and hosted APIs share the same request shape — {model, state, questions} — and differ only in the URL and the auth header:

"""Decider client: same questions, two routes.

Local route  -> Ollama's POST /v1/systemone (no key; needs the model pulled).
Hosted route -> Perplexity's POST https://api.perplexity.ai/v1/decisions
                (needs a Perplexity API key; $0.02 / $0.01 per 1M input tokens).
Both speak the same request shape: {model, state, questions}.
"""
import httpx
import requests

QUESTIONS = {
    "urgent": {"type": "noul", "instructions": "Is this support request urgent?"},
    "team": {
        "type": "choice",
        "instructions": "Which team should handle this request?",
        "criteria": {
            "billing": "Payments, invoices, and refunds",
            "technical": "Outages, errors, and configuration",
            "sales": "Plans and upgrades",
        },
    },
    "severity": {
        "type": "score",
        "instructions": "How severe is the customer impact?",
        "criteria": ["No impact", "Minor", "Major", "Critical"],
    },
}

# $ per 1M input tokens, Perplexity Decisions API pricing page (Nov 2026).
PRICES = {"pplx-decider-v1.3": 0.02, "pplx-decider-flash-v1.3": 0.01}


def ask_ollama(model, state, questions, base_url="http://localhost:11434"):
    """One POST to Ollama's System One endpoint. Returns the parsed JSON."""
    r = requests.post(
        f"{base_url}/v1/systemone",
        json={"model": model, "state": state, "questions": questions},
        timeout=120,
    )
    r.raise_for_status()
    return r.json()


def ask_perplexity(api_key, model, state, questions, base_url="https://api.perplexity.ai"):
    """One POST to the Perplexity Decisions API. Retries 429/5xx once.

    base_url is a test hook: point it at a local mock to exercise the client
    without a key. Production callers leave the default.
    """
    with httpx.Client(timeout=30) as client:
        for attempt in (1, 2):
            r = client.post(
                f"{base_url}/v1/decisions",
                headers={"Authorization": f"Bearer {api_key}"},
                json={"model": model, "state": state, "questions": questions},
            )
            if r.status_code in (429, 500, 502, 503, 504) and attempt == 1:
                continue
            r.raise_for_status()
            return r.json()


def route(decision):
    """Turn one Decider response into a routing action.

    The thresholds are the application's, not the model's: 0.8 on the
    urgency probability, 2.5 on the 0-3 severity score. Tune both on your
    own labeled tickets; never ship a vendor's example threshold blind.
    """
    a = decision["answers"]
    urgent = a["urgent"]["noul"] >= 0.8
    severity = a["severity"]["score"]
    if urgent and severity >= 2.5:
        action = "page on-call"
    elif urgent:
        action = "escalate"
    else:
        action = "queue"
    return {
        "team": a["team"]["choice"],
        "urgent": urgent,
        "severity": round(severity, 2),
        "action": action,
    }


def cost_usd(input_tokens, model):
    """Input tokens are billed; output tokens are free on the Decisions API."""
    return input_tokens / 1_000_000 * PRICES[model]

A few details worth calling out. The hosted route authenticates with a plain Authorization: Bearer <key> header — the x-api-key header is not read and returns a 401, a mistake the docs call out explicitly. The retry loop covers 429 and 5xx once; the API's limits are 10 requests per second and 15M input tokens per minute for v1.3 (4M for Flash), so a retry that respects the error is usually enough, and a 429 that persists means you are genuinely over the limit. And route() is deliberately boring: two thresholds, three outcomes. The model does the judging; your code does the arithmetic.

There is also an official typed SDK, TypeSafe's, which wraps the same endpoint and gives you attribute access to the answers. Install it, point it at Ollama, and note that Ollama ignores the API key, so any value works:

pip install typesafe-sdk
export TYPESAFE_BASE_URL=http://localhost:11434
export TYPESAFE_API_KEY=demo
export TYPESAFE_DEFAULT_MODEL=pplx-decider
from typesafe_sdk import Choice, Noul, Score, TypeSafeClient

questions = {
    "urgent": Noul(instructions="Is this support request urgent?"),
    "team": Choice(
        instructions="Which team should handle this request?",
        criteria={"billing": "Payments, invoices, and refunds",
                  "technical": "Outages, errors, and configuration",
                  "sales": "Plans and upgrades"},
    ),
    "severity": Score(instructions="How severe is the customer impact?",
                      criteria=["No impact", "Minor", "Major", "Critical"]),
}

with TypeSafeClient(timeout=120) as client:
    result = client.system_one(
        state="Our app crashes on launch for every user since this morning's update. Please fix it now.",
        questions=questions)

print(result.choices["team"].choice)   # technical
print(result.nouls["urgent"].noul)     # 0.996
print(result.scores["severity"].score) # 2.91

Watch the accessors: answers are grouped by type, so noul answers live under result.nouls, choice answers under result.choices, and score answers under result.scores. Reaching for result.choices["urgent"] raises an error — the SDK's own docs only show the .choices example, which is exactly how you end up writing the wrong one. The three lines above ran against a local stand-in and returned the documented Flash values (urgent 0.996, technical 0.998, severity 2.91, 185 input tokens) with the wire request verified to carry the right model, state, and question types.

Step 5: Build the ticket router#

Now put the client to work. Ten tickets, one question set, one call each, and a routing table at the end:

"""Route 10 support tickets through Decider and print the routing table."""
import os

from decider_client import QUESTIONS, ask_ollama, cost_usd, route

TICKETS = [
    "Our app crashes on launch for every user since this morning's update. Please fix it now.",
    "I was charged twice for my subscription this month. Please refund the duplicate charge.",
    "How do I change the billing email on my account?",
    "The dashboard shows a 500 error when I open the reports page.",
    "I want to upgrade from Starter to Pro before my renewal next week.",
    "Login fails with 'invalid credentials' even after resetting my password.",
    "Can I get a copy of last month's invoice for accounting?",
    "The mobile app drains my battery in an hour since the latest update.",
    "Do you offer a discount for nonprofits?",
    "Our API returns 401 for all requests since we rotated the key yesterday.",
]

BASE_URL = os.environ.get("DECIDER_BASE_URL", "http://localhost:11434")
MODEL = os.environ.get("DECIDER_MODEL", "pplx-decider-flash")

rows, total_tokens = [], 0
for i, ticket in enumerate(TICKETS, 1):
    d = ask_ollama(MODEL, ticket, QUESTIONS, base_url=BASE_URL)
    total_tokens += d.get("usage", {}).get("input_tokens", 0)
    r = route(d)
    rows.append((i, r["team"], r["urgent"], r["severity"], r["action"]))

print(f"{'ticket':<7}{'team':<12}{'urgent':<8}{'severity':<9}action")
for i, team, urgent, severity, action in rows:
    print(f"{i:<7}{team:<12}{str(urgent):<8}{severity:<9}{action}")
print(f"
{len(TICKETS)} tickets, {total_tokens} input tokens")
print(f"cost at pplx-decider-v1.3 rate:      ${cost_usd(total_tokens, 'pplx-decider-v1.3'):.6f}")
print(f"cost at pplx-decider-flash-v1.3 rate: ${cost_usd(total_tokens, 'pplx-decider-flash-v1.3'):.6f}")
Diagram of the ticket router: ticket text goes to Decider v1.3, which returns three typed answers — urgent noul 0.996, team choice technical, severity score 2.91 — then thresholds decide between page on-call, escalate, and queue
One call, three typed answers, one routing decision. The thresholds are yours to tune. Diagram by AI Frontier Post, using the documented values for the canonical crash ticket.

Run it with DECIDER_BASE_URL pointed at your Ollama server and DECIDER_MODEL set to the model you pulled. Here is the output of the run that verified this tutorial — with one honest caveat, stated in full in the verification section below: the sandbox had no API key and no GPU, so this run went through a local stand-in that returns the vendors' documented responses verbatim for the canonical crash ticket and scripted keyword-based answers for the other nine. The mechanics — request shape, response parsing, threshold logic, token counting, cost math — are all real and exercised; the non-canonical answers are illustrative, not model output:

ticket team        urgent  severity action
1      technical  True    2.91     page on-call
2      billing    False   0.4      queue
3      billing    False   0.4      queue
4      technical  True    2.1      escalate
5      sales      False   0.4      queue
6      technical  True    2.1      escalate
7      billing    False   0.4      queue
8      technical  False   0.4      queue
9      sales      False   0.4      queue
10     technical  True    2.1      escalate

10 tickets, 1265 input tokens
cost at pplx-decider-v1.3 rate:      $0.000025
cost at pplx-decider-flash-v1.3 rate: $0.000013

Two things to notice. First, the three-way split is doing real work: ticket 1 pages on-call because it is both urgent and severe; tickets 4, 6, and 10 are urgent but not severe enough to wake anyone, so they escalate; the rest queue. A single "is this urgent?" flag could not make that distinction. Second, the cost: ten tickets, 1,265 input tokens, $0.000025 at the v1.3 rate. At that price, routing a million tickets costs about $2.50 in model spend — the API bill is not the constraint; your labeling quality is. That is the number to keep in mind when someone proposes a hosted LLM judge that charges per output token and returns a paragraph you then have to parse.

Before you ship thresholds like 0.8 and 2.5, tune them. Perplexity's own advice in the launch post is to set thresholds from your own data, and to test how the model behaves when the state is reworded — Decider's flip rate on rephrased inputs is 0.27%, against 2.69–4.84% for the Jev-style baselines, which is why it holds up as a gate rather than a suggestion box. The practical version of that advice: label a few hundred historical tickets, run them through the router, and pick the thresholds that match your on-call team's actual paging behavior. The router above is structured so the only thing you change is two numbers.

Step 6: Locate — extract spans, not prose#

The fourth question type is the one that has no Ollama equivalent, and it is worth the hosted call on its own. locate returns character spans into the state: the exact start and end offsets of the text that answers your question. That makes it a precise extraction tool — pull the email address out of a ticket, the order number out of a thread, the clause out of a contract — with the answer verifiable by construction, because state[start:end] must equal the returned text.

Locate is available only on the v1.3 models (not Flash) on the hosted API, it needs a non-empty text state, it does not accept images, and it caps at 128 questions per request. Here is the shape, from Perplexity's docs, with a support-ticket state:

curl https://api.perplexity.ai/v1/decisions \
  -H "Authorization: Bearer $PPLX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "pplx-decider-v1.3",
    "state": "The AeroPress arrived with a cracked filter cap. I emailed [email protected] on Monday and nobody replied.",
    "questions": {
      "email": {"type": "locate", "instructions": "email address"},
      "urgent": {"type": "noul", "instructions": "Is the customer blocked right now?"}
    }
  }'

And the response, with spans that check out — the verification run asserted state[59:78] == "[email protected]" and the score on that span was 0.97:

{
  "model": "pplx-decider-v1.3",
  "answers": {
    "email": {
      "type": "locate",
      "spans": [{"start": 59, "end": 78, "text": "[email protected]", "score": 0.97}]
    },
    "urgent": {"type": "noul", "noul": 0.31}
  },
  "usage": {"input_tokens": 96, "output_tokens": 0}
}

The practical uses go beyond contact details. Perplexity's own internal deployments include page-quality filtering and security-rule self-improvement — in one run, the model reviewed 1 billion tokens across 7,000 sessions in a day and produced 14 concrete rule fixes. The pattern is the same everywhere: ask a locate question over a large text, get back offsets, and act on the span. For a support team, the immediate wins are PII redaction before a ticket enters a shared queue, and pulling structured fields (order numbers, tracking IDs, plan names) out of free text without a regex that breaks on the next customer's phrasing. On Perplexity's internal benchmark, Locate averages 69.0 for v1.3 (63.6 for Flash) against 33.6 for the GLiNER baseline — a wide enough gap that it is a real capability, not a party trick.

Step 7: The hosted route — Perplexity Decisions API#

If you do not want to run a 17 GB model locally, or you need locate, the hosted API is the same request shape at a different URL. The client from Step 4 already speaks it; the details that matter are the ones that bite in production:

  • Endpoint: POST https://api.perplexity.ai/v1/decisions, with {model, state, questions} and optional mode ("separate" by default, "isolated" to score each question independently) and max_staleness.
  • Auth: Authorization: Bearer <key>. The x-api-key header is ignored and returns 401. Get a key from your Perplexity account settings.
  • Models: pplx-decider-v1.3 and pplx-decider-flash-v1.3, or the aliases pplx-decider-latest and pplx-decider-flash-latest. Context is 256K tokens; a 243K-token state took about 16 seconds on v1.3 and 5 seconds on Flash in Perplexity's November 12 test, so keep states tight and set your client timeout accordingly — the client in Step 4 uses 30 seconds.
  • Pricing: $0.02 per million input tokens for v1.3, $0.01 for Flash; output tokens are free. At the Step 5 volumes, a million tickets cost roughly $2.50 on v1.3.
  • Limits: 10 requests per second, 15M input tokens per minute on v1.3, 4M on Flash. A 413 means your state is too large; a 400 on an unknown field means a typo in a question definition — both were exercised against the mock during verification.
  • Web grounding: there is a grounded mode that lets the model check the live web, at $0.001 per search. Useful for fact-checking claims in a ticket or a document; unnecessary for routing, where the answer is in the state.
  • Free on-ramp: OpenRouter serves both models free until November 19, 2026, as perplexity/pplx-decider-v1.3:free and perplexity/pplx-decider-flash-v1.3:free, on the same Decisions API shape. That is the cheapest way to test the hosted route before buying tokens — but note the date, because the free window is a launch promotion, not a permanent tier.

Which approach should you use?#

Bar charts: Decider v1.3 scores 85.7 percent mean accuracy across 11 public benchmarks, ahead of Jev at 84.5 and Qwen3.8-27B at 74.8; on the Decision Index 0.3, Decider v1.3 scores 68.09 against Flash at 58.88
Decider v1.3 leads the public-benchmark mean and the Decision Index; Flash trades a chunk of accuracy for a seventh of the size. Figures from Perplexity's evaluation via Ollama's model pages. Chart by AI Frontier Post.

The decision is mostly about where you run it and how much accuracy you need. The benchmark picture is consistent: on 11 public benchmarks, v1.3 averages 85.7% against Jev's 84.5% and its Qwen3.8-27B base's 74.8%, and on the Decision Index 0.3 it scores 68.09 against Flash's 58.88 (Flash climbs to 60.05 when asked one question per request). Perplexity's internal 63-task classification mean is 91.6% for v1.3, 6.54 points above the production classifiers it was tested against.

RouteUse it whenWatch out for
Local v1.3 (Ollama)Tickets must stay on your machines; you have 17 GB of RAM and a GPU or patience; you want the most accurate model for free.17 GB download; needs a real machine, not a small VM; no locate through Ollama.
Local Flash (Mac only)Apple silicon, high-volume routing, latency matters more than the last few accuracy points.MLX-only — it does not run on Linux or Windows via Ollama at all.
Hosted v1.3You need locate, or nuanced judgments where the accuracy gap pays for itself, or you have no GPU.$0.02 per million input tokens; 10 req/s; states over ~240K tokens get slow.
Hosted FlashHigh-volume routing and triage where 58.88 on the Decision Index is good enough — which, for most queues, it is.No locate; 4M tokens/min limit.
Gemini Decision 1You are already on Google's stack and want a decision model with a hosted API and a different benchmark profile.A separate API, a separate pricing page, and a separate set of limits to learn.
LLM-as-judgeYou genuinely need free-form reasoning about the state before deciding — a verdict with an explanation, not just a probability.Prose output to parse, non-deterministic verdicts, output-token billing, and no calibrated probabilities.

Perplexity's own guidance matches the table: v1.3 for nuanced judgments, Flash for high-volume routing. The one-line version: if the decision is the product — routing, gating, grading — use a decision model; if the explanation is the product, use a chat model and accept the parsing tax.

What I verified, and what I didn’t#

Every tutorial on this site runs its code before publishing. Here is the exact status for this one, because the honest version is more useful than a clean one:

  • Ran live, on this machine: Ollama 0.40.11 serving locally; ollama pull pplx-decider downloading the 17 GB GGUF at ~7 MB/s (stopped partway — the sandbox has 8 GB of RAM and the model needs ~17 GB, so it could not be loaded); ollama pull pplx-decider-flash failing with the MLX error quoted in Step 1; a /v1/systemone request to the real server returning 404 for the un-pulled model; the TypeSafe SDK, the Perplexity client, the router, and the locate span check, all against a local stand-in.
  • Ran against a local stand-in, not the vendors: the sandbox has no Perplexity key, no Hugging Face token, and no GPU, so the HTTP calls in Steps 3–7 went to a mock server on localhost. The mock returns the vendors' documented responses verbatim for the canonical crash ticket — the Flash numbers in Step 4 and the locate spans in Step 6 are those documented values, checked byte-for-byte — and scripted keyword-based answers for the other nine tickets in Step 5. The request shapes, response parsing, threshold logic, token counting, cost math, the 401-on-bad-key path, and the 400-on-unknown-field path were all exercised for real; the nine non-canonical answers are illustrative.
  • Not run: the full 27B model under load, the hosted Decisions API against a real key, and the OpenRouter free endpoint. The numbers quoted for those — benchmark scores, pricing, rate limits, latencies — are Perplexity's and Ollama's published figures, linked in the sources below. If you run the router on real hardware, expect the same mechanics and slightly different numbers on your own tickets; that is the point of the threshold-tuning advice in Step 5.

The takeaway#

The shift Decider represents is small and easy to miss: the model stops being a text generator you interrogate and becomes a scoring function you call. One request, three typed questions, three probabilities, zero output tokens — and a routing decision you can threshold, log, and audit instead of a paragraph you have to parse and hope about. That is a better primitive for almost every place agents currently ask an LLM to decide: ticket routing, tool selection, step gating, answer grading, content filtering.

Start with the hosted free tier before it ends on November 19, or pull the 27B model locally if your tickets can't leave the building. Either way, build the router in Step 5 against your own historical tickets, tune the two thresholds until the paging behavior matches your team's judgment, and keep the model's probabilities in your logs — the day a ticket gets routed wrong, the distribution will tell you whether the model was uncertain or your question was.