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.
confidenceis 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
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)
| 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)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| 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.