New to decision models? Start with What is a decision model?
Try it online (sign-in required): System One demos
The short list (October 2026)
No rankings here. Each option gets a "best for" line instead.
Hosted models (the vendor trains and hosts it; you call its API)
- Jev:
POST /v1/systemone, text-only input. Best for: the reference implementation of the Choice / Score / Noul format. system-one.dev offers the Jev model from TypeSafe. - OpenAI Decisions API:
POST /v1/decisions, currently onlygpt-6-luna, in public beta, accepts text and images. Best for: teams already on OpenAI that need image inputs. - Microsoft-Decision-1: deployed in Microsoft Foundry. Microsoft says it is post-trained from Qwen3.5-9B. Best for: teams on Azure that want the model in their own Foundry resource. See Microsoft-Decision-1.
- Clef / Clef Flash: follows the
POST /v1/systemoneformat and reads on system-one.dev: text and images. Best for: multimodal inputs. system-one.dev offers Clef and Clef Flash.
Open weights and open re-implementations (run the model on your own hardware)
- Clef open weights: Clef and Clef Flash weights are published on Hugging Face. Best for: keeping data on your own infrastructure.
- EdgeJev (Apache-2.0): packages open re-implementations (laya, kev, NanoJev, PlayJev and others) into a convert → quantize → deploy flow that runs offline on CPU and serves
POST /v1/systemonelocally. The project says "data never leaves the machine". Best for: offline experiments. - Standalone re-implementations such as jev-clone also exist. All of them state they are not affiliated with Jev's developer.
SDK layer (not a model; sits between your code and the services above)
- System One SDK (this site): define Choice / Score / Noul questions in TypeScript and connect to different model services through adapters. Best for: comparing or switching models without rewriting question definitions.
Open-source SDK ≠ self-hostable model. System One open-sources the SDK; it doesn't ship downloadable model weights.
Why lock-in happens: request format, question definitions, thresholds
The lock-in risk usually isn't which API you call. It's that your question definitions get written in one vendor's request format. The official docs show two different shapes:
| | Jev / Microsoft-Decision-1 / Clef | OpenAI Decisions API |
| --- | --- | --- |
| Input field | state (string or JSON) | input (string, or a user message with text and images) |
| Questions | questions is an object keyed by your question IDs | questions is an array; each question has a name |
| Yes/no | type: "noul" | type: "predicate" |
| Options and levels | both in criteria (object for Choice, ordered array for Score) | Choice in choices: [{ value, description }], Score in levels: [{ label, description }] |
On top of that, confidence is computed differently by each provider, so a threshold tuned on Jev can't be reused as-is on Clef or GPT-6 Luna Decisions.
To stay free to switch, guard three things:
- Question definitions: questions, options and rubric levels live in your code, not scattered across a vendor console or request bodies.
- Thresholds and business rules: policies like "below this score, send to a person" belong to your application and must be revalidated when you switch.
- The call layer: switching models should only change the adapter and credentials.
Every vendor also publishes self-reported "best" results, and those claims can't be checked against each other. Being able to compare several backends on your own labelled data is the real benefit of not being locked in.
Write once, switch backends with the System One SDK
This code is the dev team's example (examples/decision-articles/02-swappable-provider.ts). Questions, the rubric and the action threshold all live in this one file. Switching backends only swaps the adapter and credentials:
hosted: the System One hosted API (@system-one-ai/adapter-system-one). system-one.dev offers Jev, Clef Flash and Clef; get exact model IDs fromGET /v1/models.- The other
ProviderKindvalues use the other adapters imported in the file, each with that service's own credentials.
ts/**
* Article 2 — Keep the problem definition in your code so the provider is swappable.
*
* The SDK is an open-source TypeScript toolkit. Questions, rubrics, and
* action thresholds live in this file. Only the adapter / credentials change.
*
* Adapters this repository actually depends on:
* - @system-one-ai/adapter-system-one → System One hosted API or TypeSafe HTTP
* - @system-one-ai/adapter-openrouter → OpenRouter Decisions
* - @system-one-ai/adapter-cloudflare → Cloudflare REST (needs accountId)
*
* `@system-one-ai/adapter-vercel` is mentioned in docs but is not a dependency
* of this repo, so this file does not import it.
*
* Hosted credits apply only to POST /v1/systemone. TypeSafe, OpenRouter, and
* Cloudflare bill on their own accounts. Adapter support is not the hosted
* model catalog — use GET /v1/models for what this site currently hosts.
*/
import { createSystemOne, choice, score, booleanQuestion } from '@system-one-ai/core';
import type { EvaluationClient } from '@system-one-ai/core';
import { systemOneAdapter } from '@system-one-ai/adapter-system-one';
import { openRouterAdapter } from '@system-one-ai/adapter-openrouter';
import { cloudflareAdapter } from '@system-one-ai/adapter-cloudflare';
import { createFetchTransport } from '@system-one-ai/transport-fetch';
/** Application-owned problem definition. Do not move these labels into the provider dashboard. */
export const reviewQuestions = {
team: choice('Choose the team that should review this message.', {
billing: 'Payments and refunds',
support: 'Technical issues',
review: 'More information or a person is required',
}),
completeness: score('How completely does the draft reply address the message?', [
'Does not address the message',
'Partially addresses the message',
'Fully addresses the message',
]),
};
/** Application-owned policy. Unknown or missing scores are unresolved, not a pass. */
export const REVIEW_IF_SCORE_BELOW = 1.5;
export type ProviderKind = 'hosted' | 'typesafe' | 'openrouter' | 'cloudflare';
export function createProviderClient(kind: ProviderKind): EvaluationClient {
const transport = createFetchTransport();
if (kind === 'hosted') {
const apiKey = process.env.SYSTEM_ONE_API_KEY;
const baseURL = process.env.SYSTEM_ONE_BASE_URL;
const model = process.env.SYSTEM_ONE_MODEL;
if (!apiKey || !baseURL || !model) {
throw new Error('Hosted client needs SYSTEM_ONE_API_KEY, SYSTEM_ONE_BASE_URL, SYSTEM_ONE_MODEL.');
}
return createSystemOne({
apiKey, baseURL, model, adapter: systemOneAdapter, transport, maxRetries: 0, timeoutMs: 30_000,
});
}
if (kind === 'typesafe') {
const apiKey = process.env.TYPESAFE_API_KEY;
const model = process.env.TYPESAFE_MODEL;
if (!apiKey || !model) throw new Error('TypeSafe client needs TYPESAFE_API_KEY and TYPESAFE_MODEL.');
return createSystemOne({
apiKey,
baseURL: 'https://api.typesafe.ai/v1',
model,
adapter: systemOneAdapter,
transport,
maxRetries: 0,
timeoutMs: 30_000,
});
}
if (kind === 'openrouter') {
const apiKey = process.env.OPENROUTER_API_KEY;
const model = process.env.OPENROUTER_MODEL;
if (!apiKey || !model) throw new Error('OpenRouter client needs OPENROUTER_API_KEY and OPENROUTER_MODEL.');
return createSystemOne({
apiKey, model, adapter: openRouterAdapter, transport, maxRetries: 0, timeoutMs: 30_000,
});
}
const apiKey = process.env.CLOUDFLARE_API_TOKEN;
const accountId = process.env.CLOUDFLARE_ACCOUNT_ID;
const model = process.env.CLOUDFLARE_MODEL;
if (!apiKey || !accountId || !model) {
throw new Error('Cloudflare REST client needs CLOUDFLARE_API_TOKEN, CLOUDFLARE_ACCOUNT_ID, CLOUDFLARE_MODEL.');
}
return createSystemOne({
apiKey,
model,
adapter: cloudflareAdapter({ accountId }),
transport,
maxRetries: 0,
timeoutMs: 30_000,
});
}
export type ReviewNext = 'revise_draft' | 'keep_draft' | 'needs_review';
export function applyReviewPolicy(completeness: number | null | undefined): ReviewNext {
if (completeness == null || Number.isNaN(completeness)) {
return 'needs_review';
}
return completeness < REVIEW_IF_SCORE_BELOW ? 'revise_draft' : 'keep_draft';
}
export async function reviewDraft(
client: EvaluationClient,
input: { message: string; draft: string },
) {
const result = await client.evaluate({
state: { message: input.message, answer: input.draft },
questions: reviewQuestions,
});
return {
team: result.answers.team.choice,
completeness: result.answers.completeness.score,
next: applyReviewPolicy(result.answers.completeness.score),
model: result.model,
};
}
async function main() {
const kind = (process.env.EXAMPLE_PROVIDER ?? 'hosted') as ProviderKind;
const allowed: ProviderKind[] = ['hosted', 'typesafe', 'openrouter', 'cloudflare'];
if (!allowed.includes(kind)) {
throw new Error(`EXAMPLE_PROVIDER must be one of ${allowed.join(', ')}.`);
}
const decision = await reviewDraft(createProviderClient(kind), {
message: 'I was charged twice for the same order.',
draft: 'Please send your order number so we can review both charges.',
});
console.log(JSON.stringify(decision, null, 2));
}
if (process.argv[1] && import.meta.url.endsWith(process.argv[1].split(/[\\/]/).pop() ?? '')) {
main().catch((error: unknown) => {
console.error(error instanceof Error ? error.message : error);
process.exitCode = 1;
});
}
What to notice (all from the example's comments and README):
- The application owns the questions. The comment on
reviewQuestionssays not to move these labels into a provider dashboard. - The application owns the threshold.
REVIEW_IF_SCORE_BELOW = 1.5is app policy.applyReviewPolicy()has three outcomes: a missing (undefined/null) orNaNscore returnsneeds_review; a score under 1.5 returnsrevise_draft; anything else returnskeep_draft. Missing or unknown scores count as unresolved, never as a pass. - Billing is separate. System One credits only apply to the System One hosted API (
POST /v1/systemone). Third-party services bill on their own accounts. - Adapter support isn't the hosted catalog. For what this site hosts right now, use
GET /v1/models. A full model ID selects a model, but it can't pin a model version. @system-one-ai/adapter-vercelappears in the docs but isn't a dependency of this repo, so the example doesn't import it.
Choose a backend with EXAMPLE_PROVIDER and provide that service's own credentials. Those calls don't use System One credits.
The @system-one-ai/core npm readme also notes that requests can carry images, but only adapters that declare supportsImages: true accept them. Other adapters throw an error instead of silently downgrading the image to text.
Self-hosting checklist
To keep data inside your network you need a model you can run yourself, not just an open-source SDK. The options we could verify are the Clef open weights and open re-implementations such as EdgeJev. Check:
- Data residency: EdgeJev says inference makes no network requests, has no telemetry, and state and questions never leave the machine (project claim). For hosted services, read each vendor's terms: OpenAI's docs mention ZDR for eligible customers, and Jev's docs say customer requests aren't used for training.
- Accuracy and calibration: the only numbers for open re-implementations are self-reported. EdgeJev publishes its own results in its README along with the test conditions. jev-clone's README says existing public re-implementations open-source the "interface shape" but none has been evaluated for calibration (project claim). Test on your own labelled data.
- Language: The Jev docs say Jev's primary training language is English. Test non-English content whichever option you choose.
- Latency: every published latency figure, from projects or vendors, is self-measured. We haven't measured any.
- Ops cost: conversion, quantization, deployment, monitoring and upgrades are your job. For example, EdgeJev's conversion step needs torch and a network connection to download weights before it can run offline.
- SDK connection: EdgeJev serves
POST /v1/systemone, the same path the System One hosted API uses ({baseURL}/systemone). Whether the System One SDK connects to such a self-hosted server directly hasn't been verified, and we make no promise.
How to choose: a simple order
- Can data leave your network? If not, look at open weights or re-implementations and budget time to validate calibration yourself.
- Do you need images? Per the official docs, Jev is text-only; the OpenAI Decisions API and Clef accept images.
- Do you want to try several? Use an SDK layer and compare them on one set of question definitions.
- Whatever you choose: keep questions and thresholds in your code, and revalidate thresholds on the same labelled data after any switch.
No latency or accuracy columns: vendors and projects only publish self-measured numbers, and we haven't measured any.