> ## Documentation Index
> Fetch the complete documentation index at: https://docs.perplexity.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Decider for Mac

> Run pplx-decider-flash-v1.2 on your Mac from the menu bar and call the Decisions API at 127.0.0.1 with no API key.

Some of the content you want to classify can't leave your Mac: customer exports, meeting notes, screenshots of internal tools, logs with tokens in them. Decider for Mac runs `pplx-decider-flash-v1.2`, Perplexity's 4B decision model, on your Mac and serves the Decisions API at `http://127.0.0.1:8080/v1/decisions`. The model reads your content on your machine. There's no API key and no usage bill.

You send the same JSON as the hosted [Decisions API](https://docs.perplexity.ai/docs/decisions/quickstart.md): content as `state`, a named question, and you get back probabilities. Build and test against your Mac, then point the same code at `https://api.perplexity.ai/v1/decisions` when you need hosted scale or the larger `pplx-decider-v1.2` model.

> **[Download Decider for Mac](https://macos-download.perplexity.ai/Decider.dmg)**
>
> Decider.dmg for Macs with Apple silicon running macOS 13 or later. The app downloads the 5.8 GB model on first launch.

## What you get

* **Private content stays on your Mac.** The model reads `state` on your machine, so you can classify files you can't send to a hosted API.
* **No API key and no bill.** Run thousands of labels while you tune questions and thresholds.
* **Text and images.** The menu shows **Ready (text + images)** when the model is loaded, so you can send screenshots and photos as well as text.
* **The same request as the hosted API.** Moving code to the hosted API is a URL change and an `Authorization` header.

Downloading the app and the model needs a network connection.

## Requirements

* A Mac with Apple silicon (M1 or later)
* macOS 13 or later
* 16 GB of memory
* About 6 GB of free disk space

## Install the app

**1. Download the app**

Download [Decider.dmg](https://macos-download.perplexity.ai/Decider.dmg).

**2. Move it to Applications**

Open the DMG and drag Decider to your Applications folder.

**3. Open Decider and wait for the model to download**

Open Decider from Applications. Its icon appears in the menu bar, not the Dock, and the setup window shows the one-time model download (about 5.8 GB). When the download finishes, the window shows **Ready to use** and the menu shows **Ready**. Closing the setup window keeps Decider running; click the menu bar icon to see the status or reopen the window.

If Decider asks for a Hugging Face token, paste one with read access. You can create one in your [Hugging Face token settings](https://huggingface.co/settings/tokens). If you already signed in with `hf auth login`, Decider uses that login instead.

![Set up Decider window pointing to the Decider icon in the menu bar, with The model marked Downloaded and loaded and Ready to use showing the local address http://127.0.0.1:8080/v1/decisions](https://mintcdn.com/perplexity/ZfCnPZnOM2bY-0ki/docs/assets/images/decider/setup-window.png?fit=max\&auto=format\&n=ZfCnPZnOM2bY-0ki\&q=85\&s=0b0602e67f94a7c16ba6e3f7cde15343)

The setup window after the model is downloaded and the local API is ready.

## Send your first request

When the menu shows **Ready**, run this in Terminal. It asks which team should handle a support message:

```bash theme={null}
curl -sS http://127.0.0.1:8080/v1/decisions \
  -H 'Content-Type: application/json' \
  -d '{
    "model": "pplx-decider-flash-v1.2",
    "state": "I was charged twice for my order last week.",
    "questions": {
      "route": {
        "type": "choice",
        "instructions": "Which team should handle this message?",
        "criteria": {"billing": null, "shipping": null, "technical": null}
      }
    }
  }'
```

The answer for `route` has `choice` (the most likely team), `probabilities` (one value per team), and `confidence`. See the [Decisions API quickstart](https://docs.perplexity.ai/docs/decisions/quickstart.md) for how to read each answer type and [Question types](https://docs.perplexity.ai/docs/decisions/quickstart.md#question-types) for `noul` (yes or no), `choice` (one of your options), and `score` (a level on your rubric).

The menu's **Copy Example curl** gives you a ready-to-run request, and **Copy API URL** gives you the address.

Send one question per request. `pplx-decider-flash-v1.2` answers each question on its own; see [Grouping questions in one request](https://docs.perplexity.ai/docs/decisions/quickstart.md#grouping-questions-in-one-request).

## Examples

Every example below uses only what comes with macOS: Python 3 from the Command Line Tools, plus the built-in `screencapture` and `sips` commands. If `python3` isn't installed, macOS offers to install the Command Line Tools the first time you run it. Each script sends one question per request and reads one answer.

### Label a folder of private files

You have a folder of exported support emails, meeting notes, or user feedback, and you want a label on every file without uploading any of it. This script sends each `.txt` file in a folder to Decider and writes the labels to `labels.csv`.

Save it as `label_folder.py` and run `python3 label_folder.py ~/path/to/folder`:

```python theme={null}
import csv
import json
import sys
import urllib.request
from pathlib import Path

URL = "http://127.0.0.1:8080/v1/decisions"
QUESTION = {
    "type": "choice",
    "instructions": "What is this message mainly about?",
    "criteria": {
        "bug": "Something is broken or not working as expected",
        "feature_request": "A request for something new",
        "billing": "Charges, refunds, plans, or invoices",
        "other": None,
    },
}


def decide(state, question):
    body = json.dumps({
        "model": "pplx-decider-flash-v1.2",
        "state": state,
        "questions": {"q": question},
    }).encode()
    request = urllib.request.Request(URL, data=body, headers={"Content-Type": "application/json"})
    with urllib.request.urlopen(request, timeout=60) as response:
        return json.load(response)["answers"]["q"]


folder = Path(sys.argv[1]).expanduser()
with open("labels.csv", "w", newline="") as out:
    writer = csv.writer(out)
    writer.writerow(["file", "label", "confidence"])
    for path in sorted(folder.glob("*.txt")):
        answer = decide(path.read_text(errors="replace"), QUESTION)
        writer.writerow([path.name, answer["choice"], round(answer["confidence"], 3)])
        print(path.name, answer["choice"], round(answer["confidence"], 3))
```

Sort `labels.csv` by `confidence` and read the lowest rows first. Those are the files where the model was least sure, which is where your category descriptions need work. Edit the descriptions in `QUESTION` and run it again. Nothing stops you from running it fifty times.

### Block commits that contain secrets

A regex scanner catches secrets with a known format, like an AWS key or a private key header. It misses the ones without a format: a database password in a config file or a token pasted into a comment. This pre-commit hook runs both checks on the lines you're adding. A pattern match blocks the commit on its own, and Decider is asked about everything else.

Save this as `.git/hooks/pre-commit` in your repository and run `chmod +x .git/hooks/pre-commit`:

```python theme={null}
#!/usr/bin/env python3
import json
import re
import subprocess
import sys
import urllib.request

URL = "http://127.0.0.1:8080/v1/decisions"
THRESHOLD = 0.2
QUESTION = {
    "type": "noul",
    "instructions": "Does this text contain a real password, API key, access token, or private key? Placeholders like YOUR_API_KEY and environment variable names don't count.",
}
PATTERNS = {
    "private key": r"-----BEGIN [A-Z ]*PRIVATE KEY-----",
    "AWS access key": r"\b(AKIA|ASIA)[0-9A-Z]{16}\b",
    "GitHub token": r"\b(gh[pousr]_[A-Za-z0-9]{36,}|github_pat_[A-Za-z0-9_]{60,})",
    "Slack token": r"\bxox[abprs]-[A-Za-z0-9-]{10,}",
    "Perplexity API key": r"\bpplx-[A-Za-z0-9]{32,}",
}


def git(*args):
    return subprocess.run(["git", *args], capture_output=True, check=True).stdout.decode(errors="replace")


def added_lines(path):
    diff = git("diff", "--cached", "--unified=0", "--no-color", "--", path)
    hunks = diff.split("\n@@", 1)[1] if "\n@@" in diff else ""
    return "\n".join(line[1:] for line in hunks.splitlines() if line.startswith("+"))


def p_secret(text):
    body = json.dumps({
        "model": "pplx-decider-flash-v1.2",
        "state": text,
        "questions": {"secret": QUESTION},
    }).encode()
    request = urllib.request.Request(URL, data=body, headers={"Content-Type": "application/json"})
    with urllib.request.urlopen(request, timeout=60) as response:
        return json.load(response)["answers"]["secret"]["noul"]


problems = []
for path in git("diff", "--cached", "--name-only", "--diff-filter=ACMR", "-z").split("\0"):
    text = added_lines(path) if path else ""
    if not text.strip():
        continue
    for label, pattern in PATTERNS.items():
        if re.search(pattern, text):
            problems.append(f"{path}: matches the {label} pattern")
    try:
        p = p_secret(text)
    except Exception as error:
        problems.append(f"{path}: not checked, Decider didn't answer ({error})")
        continue
    if p >= THRESHOLD:
        problems.append(f"{path}: probability of a secret {p:.2f}")

if problems:
    print("Commit blocked:\n  " + "\n  ".join(problems))
    sys.exit(1)
```

Each staged file's added lines go to Decider as one `noul` question, and the commit stops if the probability of a secret is `THRESHOLD` or higher. A false positive costs you a second look, but a missed secret ends up in your Git history, so the cutoff starts low at 0.2. Run the hook on commits you've already reviewed and raise the cutoff only if it flags too many clean ones.

The hook fails closed. If Decider isn't running or can't answer for a file, the commit stops and names the file. When you've checked a flagged file yourself, commit with `git commit --no-verify` to skip the hook once.

The hook doesn't read binary files, and it only sees what you commit from this Mac. For full coverage, run a dedicated secret scanner in CI as well.

### Sort a folder of screenshots

Screenshots pile up: receipts, error dialogs, chat threads, code. Decider reads images, so it can sort them by what's in them. This script asks one `choice` question about each image and moves it into a folder named after the answer.

Save it as `sort_screenshots.py` and run `python3 sort_screenshots.py ~/Desktop/screenshots`:

```python theme={null}
import base64
import json
import subprocess
import sys
import tempfile
import urllib.request
from pathlib import Path

URL = "http://127.0.0.1:8080/v1/decisions"
QUESTION = {
    "type": "choice",
    "instructions": "What does this screenshot mainly show?",
    "criteria": {
        "receipt": "A receipt, invoice, order confirmation, or payment",
        "error": "An error message, stack trace, or crash dialog",
        "conversation": "A chat, email, or social media thread",
        "code": "Source code, a terminal, or a diff",
        "other": None,
    },
}


def classify(image_path):
    small = Path(tempfile.mkdtemp()) / image_path.name
    subprocess.run(["sips", "-Z", "1440", str(image_path), "--out", str(small)], check=True, capture_output=True)
    mime = "image/png" if image_path.suffix.lower() == ".png" else "image/jpeg"
    image_url = f"data:{mime};base64," + base64.b64encode(small.read_bytes()).decode()
    body = json.dumps({
        "model": "pplx-decider-flash-v1.2",
        "state": {"type": "image_url", "image_url": {"url": image_url}},
        "questions": {"q": QUESTION},
    }).encode()
    request = urllib.request.Request(URL, data=body, headers={"Content-Type": "application/json"})
    with urllib.request.urlopen(request, timeout=120) as response:
        return json.load(response)["answers"]["q"]


folder = Path(sys.argv[1]).expanduser()
for path in sorted(folder.iterdir()):
    if path.suffix.lower() not in {".png", ".jpg", ".jpeg"}:
        continue
    answer = classify(path)
    target = folder / answer["choice"]
    target.mkdir(exist_ok=True)
    path.rename(target / path.name)
    print(f"{path.name} -> {answer['choice']}/ ({answer['confidence']:.2f})")
```

`sips -Z 1440 ... --out` writes a copy with its longest side at 1440 pixels, which keeps every image inside the [image size limit](https://docs.perplexity.ai/docs/decisions/quickstart.md#images-in-state) and keeps the request small. Your originals move, but they're never resized. Add a category by adding a line to `criteria`. Your screenshots never leave your Mac.

### Watch your screen for an error

Decider can answer questions about what's on your screen right now. This script takes a screenshot, shrinks it, and asks whether an error is showing.

Save it as `check_screen.py` and run `python3 check_screen.py`:

```python theme={null}
import base64
import json
import subprocess
import tempfile
import urllib.request
from pathlib import Path

shot = Path(tempfile.mkdtemp()) / "screen.jpg"
subprocess.run(["screencapture", "-x", "-t", "jpg", str(shot)], check=True)
subprocess.run(["sips", "-Z", "1440", str(shot)], check=True, capture_output=True)
image_url = "data:image/jpeg;base64," + base64.b64encode(shot.read_bytes()).decode()

body = json.dumps({
    "model": "pplx-decider-flash-v1.2",
    "state": {"type": "image_url", "image_url": {"url": image_url}},
    "questions": {
        "error": {
            "type": "noul",
            "instructions": "Does the screen show an error message, failed build, or crash dialog?",
        }
    },
}).encode()
request = urllib.request.Request(
    "http://127.0.0.1:8080/v1/decisions",
    data=body,
    headers={"Content-Type": "application/json"},
)
with urllib.request.urlopen(request, timeout=120) as response:
    print(f"Probability of an error: {json.load(response)['answers']['error']['noul']:.2f}")
```

`screencapture -x` saves the screen without the shutter sound, and `sips -Z 1440` shrinks the file in place. The first time you run it, macOS asks you to allow Screen Recording for Terminal under **System Settings > Privacy & Security**. Without that permission, the screenshot contains only your desktop background.

Run it in a loop to watch a long build or a flaky deploy, and change the question to check for whatever you care about. The screenshots never leave your Mac.

### Move the same code to the hosted API

When you need hosted throughput or the larger `pplx-decider-v1.2` model, keep the request and change two things: the URL and an `Authorization` header with your [API key](https://console.perplexity.ai/project/keys).

```python theme={null}
import json
import os
import urllib.request

LOCAL = "http://127.0.0.1:8080/v1/decisions"
HOSTED = "https://api.perplexity.ai/v1/decisions"


def decide(state, question, hosted=False):
    headers = {"Content-Type": "application/json"}
    if hosted:
        headers["Authorization"] = f"Bearer {os.environ['PERPLEXITY_API_KEY']}"
    body = json.dumps({
        "model": "pplx-decider-flash-v1.2",
        "state": state,
        "questions": {"q": question},
    }).encode()
    request = urllib.request.Request(HOSTED if hosted else LOCAL, data=body, headers=headers)
    with urllib.request.urlopen(request, timeout=60) as response:
        return json.load(response)["answers"]["q"]


question = {"type": "noul", "instructions": "Does this review report a product defect?"}
review = "The battery stopped charging after two weeks."
print("local: ", decide(review, question)["noul"])
print("hosted:", decide(review, question, hosted=True)["noul"])
```

The weights on your Mac are a `q8_0` GGUF build of the model, not the hosted deployment. If you tuned a threshold locally, check it on a sample of your data against the hosted model before you rely on it in production.

## The menu

![Decider menu showing Ready (text + images), the address http://127.0.0.1:8080/v1/decisions, Retry, Set Up Decider, Copy API URL, Copy Example curl, Port 8080, Allow Network Access, Start at Login, Set Hugging Face Token, Open Config File, Show Model Files, and Quit Decider](https://mintcdn.com/perplexity/ZfCnPZnOM2bY-0ki/docs/assets/images/decider/menu.png?fit=max\&auto=format\&n=ZfCnPZnOM2bY-0ki\&q=85\&s=42deeb8a2b2d8dfefb1e207257183ebd)

The Decider menu when the model is loaded.

| Item | What it does |
| - | - |
| **Status** | Whether the model is loaded. **Ready (text + images)** means you can send text and image requests. |
| **Address** | The local endpoint. On the default port it's `http://127.0.0.1:8080/v1/decisions`. |
| **Retry** | Tries to load the model again. It's grayed out while Decider is ready. |
| **Set Up Decider…** | Reopens the setup window. |
| **Copy API URL** | Copies the endpoint address. |
| **Copy Example curl** | Copies a ready-to-run curl request. |
| **Port** | Picks the port: 8080 (default), 8000, 8888, 9000, 11435, or **Custom…** for any other. If you change it, use the new port instead of `8080` in every example on this page. |
| **Start at Login** | Starts Decider when you log in to your Mac. |
| **Set Hugging Face Token…** | Sets the token Decider uses to download the model from Hugging Face. |
| **Open Config File** | Opens `config.toml`. See [Config file](#config-file). |
| **Show Model Files** | Opens the folder with the downloaded model files. See [Model files](#model-files). |
| **Quit Decider** | Quits the app. The local API stops with it. |

## Config file

**Open Config File** opens `~/Library/Application Support/ai.perplexity.decider/config.toml`. You don't need to edit it: the menu sets the port for you. These are the defaults:

```toml theme={null}
host = "127.0.0.1"
port = 8080
mode = "per-question"
max_tokens = 32768
base_tokens = 8192
aliases = []
accept_any_model = false

[model]
no_vision = false
hf_repo = "perplexity-ai/pplx-decider-flash-gguf-v1.2"
hf_revision = "main"
hf_file = "decider-flash-v1.2-q8_0.gguf"
hf_mmproj_file = "mmproj-decider-flash-v1.2-f16.gguf"

[voice]
enabled = true
language = "auto"
```

`host` and `port` are the address the server listens on; **Port** in the menu sets the port. Leave `host` at `127.0.0.1`. The `[model]` table names the Hugging Face repo and the two files Decider downloads, which are the files you see under **Show Model Files**. Leave the other keys at their defaults.

## Model files

**Show Model Files** opens the folder with the two files Decider downloaded:

* `decider-flash-v1.2-q8_0.gguf`: the model weights, a `q8_0` GGUF build of `pplx-decider-flash-v1.2`.
* `mmproj-decider-flash-v1.2-f16.gguf`: the multimodal projector, the part that lets the model read images.

Each file has a matching `.lock` file. Leave all four in place while Decider is running.

## Not supported

Decider for Mac requires Apple silicon. On Windows, Linux, or an Intel Mac, use the hosted [Decisions API](https://docs.perplexity.ai/docs/decisions/quickstart.md) instead.
