Skip to main content

pijev — Permutation Invariant Jev

Better Jev probabilities with theoretical guarantees—all with a one-line import change.

pijev turns Jev's option-order variations into a single averaged prediction, with Brier score and log loss guaranteed no worse than the average across the included orderings. All permutations run in one system_one request. Keep the same calls and official response types; pijev handles the rest.

Problem: Jev's decision is not permutation invariant

The same options can receive different probabilities when reordered. This official SDK call sends two Choices with identical instructions and descriptions, but opposite option orders:

from typesafe_sdk import Choice, TypeSafeClient

instructions = "Which team should handle this support ticket? Choose the best primary team."

with TypeSafeClient() as client:
    response = client.system_one(
        state={
            "ticket": "Since upgrading my plan yesterday, I cannot access the "
                      "dashboard. The payment went through, but the page says "
                      "my subscription is inactive. Please fix this."
        },
        questions={
            "forward": Choice(
                instructions=instructions,
                criteria={
                    "billing": "Payments, charges, invoices, and refunds.",
                    "technical": "Product failures, errors, and troubleshooting.",
                    "account": "Login, account access, and account settings.",
                },
            ),
            "reverse": Choice(
                instructions=instructions,
                criteria={
                    "account": "Login, account access, and account settings.",
                    "technical": "Product failures, errors, and troubleshooting.",
                    "billing": "Payments, charges, invoices, and refunds.",
                },
            ),
        },
    )

print(response.choices["forward"].probabilities)
print(response.choices["reverse"].probabilities)

One request, two questions, one shared state. A live jev-1.13.0 run returned:

Option billing → technical → account account → technical → billing
billing 0.43 0.63
technical 0.21 0.10
account 0.36 0.27

Both selected billing, but its probability changed by 20 percentage points. This run also changes question IDs and does not control model randomness; see the experiment details.

How pijev works

pijev treats option order as a nuisance variable: evaluate different orderings, align predictions by label, and average their probabilities. The highest mean probability determines the returned choice.

For three options, there are six permutations. Here is a separate live jev-1.13.0 run with all six in one request. A = account, B = billing, T = technical.

Option A → B → T A → T → B B → A → T B → T → A T → A → B T → B → A pijev mean
billing 0.58 0.64 0.58 0.46 0.47 0.60 0.5550
technical 0.17 0.08 0.11 0.23 0.12 0.12 0.1383
account 0.25 0.28 0.31 0.31 0.41 0.28 0.3067

The pijev mean column averages all six predictions: billing receives 0.555, compared with 0.46–0.64 across individual orderings. Reproduce this run · Recorded results

Ask once using the same SDK interface; pijev handles the six-question batch and returns one aggregated answer:

from pijev import Choice, TypeSafeClient

with TypeSafeClient() as client:
    response = client.system_one(
        state={
            "ticket": "Since upgrading my plan yesterday, I cannot access the "
                      "dashboard. The payment went through, but the page says "
                      "my subscription is inactive. Please fix this."
        },
        questions={
            "team": Choice(
                instructions="Which team should handle this support ticket? "
                             "Choose the best primary team.",
                criteria={
                    "billing": "Payments, charges, invoices, and refunds.",
                    "technical": "Product failures, errors, and troubleshooting.",
                    "account": "Login, account access, and account settings.",
                },
            ),
        },
    )

answer = response.choices["team"]
print(answer.choice)
print(answer.probabilities)

The result is an official SystemOneResponse with your original question names. Live results may differ from the recorded run.

Usage

Install from PyPI:

pip install pijev

Set your API key:

export TYPESAFE_API_KEY=your-key

Change only the import:

-from typesafe_sdk import TypeSafeClient, Choice, Noul, Score
+from pijev import TypeSafeClient, Choice, Noul, Score

Set an optional permutation budget:

client = TypeSafeClient(n_permutations=16, seed=42)

The default, n_permutations="auto", evaluates a balanced set of orderings (a Williams design): every option takes every position, and follows every other option, equally often. That is $K$ orderings for $K$ options, or $2K$ when $K$ is odd, and they do not depend on the order the criteria were written in. An integer budget samples that many distinct permutations uniformly without replacement (all of them when there are fewer), and n_permutations="all" enumerates every one. The seed controls local sampling only. Batches are capped at 720 expanded questions and remain subject to server limits.

Async usage works the same way: import AsyncTypeSafeClient from pijev, then use async with and await client.system_one(...) as usual.

Guaranteed Better Calibration

Permutation averaging has a proper-scoring-loss guarantee, not an unconditional calibration guarantee. Let $p^{(m)}$ be the label-aligned probability vector for permutation $m$, and let $y$ be the same correct answer under every ordering. Define

$$ \bar p = \frac{1}{M}\sum_{m=1}^{M}p^{(m)}. $$

For any loss convex in the predicted probabilities, Jensen's inequality gives

$$ L(\bar p,y) \leq \frac{1}{M}\sum_{m=1}^{M}L(p^{(m)},y). $$

This holds for the actual set of predictions being averaged, including a sampled subset of permutations. Two useful cases are:

  • Log loss: $-\log\bar p_y \leq \frac{1}{M}\sum_m -\log p_y^{(m)}$.
  • Brier score: writing $e_y$ for the one-hot correct answer, the improvement equals the disagreement removed by averaging:

$$ \frac{1}{M}\sum_m|p^{(m)}-e_y|_2^2 - |\bar p-e_y|_2^2 = \frac{1}{M}\sum_m|p^{(m)}-\bar p|_2^2 \geq 0. $$

The guarantee compares the average prediction with randomly choosing one of the included predictions. Brier improvement is strict when those predictions differ. For uniform sampling and a fixed predictor, the same inequality holds in expectation against a uniformly random ordering. Full enumeration gives the group average; see Chen, Dobriban, and Lee for related symmetry-averaging theory.

Cost analysis

For $K$ options, pijev evaluates $M$ permutations in one HTTP request: $K$, or $2K$ for odd $K$, by default, and $\min(B,K!)$ with an integer budget $B$. Multiple Choices contribute their own permutations; Noul and Score each add one unchanged question.

Workload Questions in one request
3 options, default 6
4 options, default 4
10 options, default 10
Two Choices with 3 options each 12
3 options plus one Noul 7

The state is sent once. For state size $S$ and question size $Q$, request content is approximately $S+MQ$, versus $M(S+Q)$ for separate calls, excluding protocol overhead. This is a payload estimate; use response.usage for reported tokens.

At TypeSafe's published rate of $0.042 per million input tokens, with free output tokens (checked 2026-09-22):

$$ C = \frac{T}{10^6}\times 0.042 $$

Here, $C$ is the estimated cost in USD and $T$ is response.usage.input_tokens.

Recorded run Input tokens Estimated cost
Two permutations 475 $0.00001995
All six permutations 835 $0.00003507

These estimates use reported usage and the published rate, not invoice charges. One request still contains multiple decisions; latency and billing depend on the backend, and retries may add requests.

Notes

  • Only Choice answers are averaged. Noul and Score pass through unchanged.
  • confidence is the selected label's mean probability, not Jev's native confidence score.
  • Sampling approximates full permutation averaging; it does not guarantee exact order invariance or better calibration. The live examples demonstrate probability differences, not accuracy or calibration gains.
  • Official interface: TypeSafe Python SDK. This is a third-party extension, not an official TypeSafe project.

License

Apache License 2.0.

Metadata

Release files for pijev 0.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for pijev 0.2.0
File Size Uploaded Provenance Actions
pijev-0.2.0.tar.gz 14.1 kB Without provenance Details for pijev-0.2.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for pijev 0.2.0
File Interpreter ABI Platform Actions
pijev-0.2.0-py3-none-any.whl Python 3 none any Details for pijev-0.2.0-py3-none-any.whl

Total release size: 26.0 kB

Release files / pijev-0.2.0.tar.gz

Download URL pijev-0.2.0.tar.gz
Size 14.1 kB
Tags Source
SHA-256 checksum
How to use checksums
9a519f389038b53dc81e0cae378232a676e596ac847ccd248c7a7f62e23e407a
BLAKE2b-256 checksum
How to use checksums
d50ce71f2fd33f756788925d483579b15002dd212641acfd8f21f86f256c8864
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.12.10 {"installer":{"name":"uv","version":"0.12.10","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release files / pijev-0.2.0-py3-none-any.whl

Download URL pijev-0.2.0-py3-none-any.whl
Size 11.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
1dc8743ac17b1027039c5ee0192a4064dbd6581a81fce25049e27a529295c0a3
BLAKE2b-256 checksum
How to use checksums
66c9db85e2706e743b1821fa856914465e9c38f68132534599ad8dfdbd61a7f6
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.12.10 {"installer":{"name":"uv","version":"0.12.10","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Security for pijev 0.2.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.2.0 This release

2 release files No provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page