Decisions
celeris-1-decision answers questions about a piece of data with probabilities
instead of text. You send a state (the data) and named questions, and each
answer comes back as a number you can threshold, rank, or route on.
The endpoint is POST /v1/systemone. It implements TypeSafe's System One API,
so code written for Jev with the
Jev SDK runs against it once you change
the base URL, key, and model name. Each response is one JSON body. There is no
streaming.
A yes/no question
- cURL
- Jev SDK
curl https://inference.celeris.ai/celeris-1-decision/v1/systemone \
-H "Authorization: Bearer $CELERIS_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "celeris-1-decision",
"state": "I was charged twice this month. Please refund one of the payments.",
"questions": {
"refund": {"type": "noul", "instructions": "The customer is asking for a refund."}
}
}'
# pip install typesafe-sdk
import os
from typesafe_sdk import Noul, TypeSafeClient
client = TypeSafeClient(
api_key=os.environ["CELERIS_API_KEY"],
base_url="https://inference.celeris.ai/celeris-1-decision",
model="celeris-1-decision",
)
response = client.system_one(
state="I was charged twice this month. Please refund one of the payments.",
questions={"refund": Noul(instructions="The customer is asking for a refund.")},
)
print(response.nouls["refund"].noul) # 0.91
{
"model": "celeris-1-decision",
"answers": {
"refund": {"type": "noul", "noul": 0.91}
},
"usage": {"input_tokens": 351, "output_tokens": 21}
}
noul is the probability that the answer is yes.
To switch existing code without editing it, set the SDK's environment variables:
export TYPESAFE_BASE_URL=https://inference.celeris.ai/celeris-1-decision
export TYPESAFE_API_KEY=$CELERIS_API_KEY
export TYPESAFE_DEFAULT_MODEL=celeris-1-decision
Question types
Each question needs a type and usually carries instructions. One request
can hold several questions of any mix of types, and state can be a string or
any other JSON value.
type | criteria | Answer |
|---|---|---|
noul | Optional. {"true": ..., "false": ...} describes each outcome. | noul: the probability of yes. |
choice | Required. An object of at least two options. Each key is an option name and each value describes it, or is null. | choice: the most likely option. probabilities: one per option. |
score | Required. An array of at least two levels, lowest first. Levels are numbered from 0. | score: the average level, weighted by probability. probabilities: one per level. legend: each level's description. |
choice and score answers also carry confidence, the top probability
rescaled so that 0 is an even split across the options and 1 is certainty.
Probabilities are rounded to two decimal places and sum to 1.
- cURL
- Jev SDK
curl https://inference.celeris.ai/celeris-1-decision/v1/systemone \
-H "Authorization: Bearer $CELERIS_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "celeris-1-decision",
"state": {
"subject": "Charged twice",
"body": "I was charged twice this month and nobody has answered my emails for a week. Please refund one of the payments."
},
"questions": {
"refund": {"type": "noul", "instructions": "The customer is asking for a refund."},
"team": {
"type": "choice",
"instructions": "Which team should handle this ticket?",
"criteria": {
"billing": "Payments, invoices and refunds.",
"technical": "Bugs and outages.",
"sales": "Upgrades and quotes."
}
},
"frustration": {
"type": "score",
"instructions": "How frustrated is the customer?",
"criteria": ["Calm.", "Annoyed.", "Angry."]
}
}
}'
from typesafe_sdk import Choice, Noul, Score
response = client.system_one(
state={
"subject": "Charged twice",
"body": "I was charged twice this month and nobody has answered my emails for a week. Please refund one of the payments.",
},
questions={
"refund": Noul(instructions="The customer is asking for a refund."),
"team": Choice(
instructions="Which team should handle this ticket?",
criteria={
"billing": "Payments, invoices and refunds.",
"technical": "Bugs and outages.",
"sales": "Upgrades and quotes.",
},
),
"frustration": Score(
instructions="How frustrated is the customer?",
criteria=["Calm.", "Annoyed.", "Angry."],
),
},
)
print(response.nouls["refund"].noul) # 0.91
print(response.choices["team"].choice) # billing
print(response.scores["frustration"].score) # 1.41
{
"model": "celeris-1-decision",
"answers": {
"refund": {"type": "noul", "noul": 0.91},
"team": {
"type": "choice",
"choice": "billing",
"probabilities": {"billing": 0.88, "technical": 0.07, "sales": 0.05},
"confidence": 0.82
},
"frustration": {
"type": "score",
"score": 1.41,
"probabilities": {"0": 0.18, "1": 0.23, "2": 0.59},
"confidence": 0.38,
"legend": {"0": "Calm.", "1": "Annoyed.", "2": "Angry."}
}
},
"usage": {"input_tokens": 673, "output_tokens": 102}
}
Celeris extensions
Celeris adds options that System One does not have. They go in one optional
top-level object, x_celeris. The Jev SDK sends it through extra_body.
Explanations
Set explain to true to get one sentence per question on why the model
picked its answer. The sentences come back in x_celeris.explanations, keyed
by question name. Explanations make the request slower.
- cURL
- Jev SDK
curl https://inference.celeris.ai/celeris-1-decision/v1/systemone \
-H "Authorization: Bearer $CELERIS_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "celeris-1-decision",
"state": "I was charged twice this month and nobody has answered my emails for a week.",
"questions": {
"team": {
"type": "choice",
"instructions": "Which team should handle this ticket?",
"criteria": {
"billing": "Payments, invoices and refunds.",
"technical": "Bugs and outages.",
"sales": "Upgrades and quotes."
}
}
},
"x_celeris": {"explain": true}
}'
from typesafe_sdk import Choice, SystemOneResponse
# The SDK's own response type drops fields it does not declare.
class Explained(SystemOneResponse):
x_celeris: dict | None = None
response = client.system_one(
state="I was charged twice this month and nobody has answered my emails for a week.",
questions={
"team": Choice(
instructions="Which team should handle this ticket?",
criteria={
"billing": "Payments, invoices and refunds.",
"technical": "Bugs and outages.",
"sales": "Upgrades and quotes.",
},
),
},
extra_body={"x_celeris": {"explain": True}},
response_model=Explained,
)
print(response.x_celeris["explanations"]["team"])
{
"model": "celeris-1-decision",
"answers": {
"team": {
"type": "choice",
"choice": "billing",
"probabilities": {"billing": 0.88, "technical": 0.07, "sales": 0.05},
"confidence": 0.82
}
},
"usage": {"input_tokens": 431, "output_tokens": 64},
"x_celeris": {
"explanations": {
"team": "The user is reporting a double charge, which falls directly under payments and invoices."
}
}
}
Images
Attach images in images, an object that maps a name to a
base64 data URL. Images are part of the state,
and your state and questions can refer to one by its name.
max_soft_tokens sets the resolution the model
reads every image in the request at. It takes the values listed there.
- cURL
- Jev SDK
IMAGE_B64=$(base64 < receipt.png | tr -d '\n')
curl https://inference.celeris.ai/celeris-1-decision/v1/systemone \
-H "Authorization: Bearer $CELERIS_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "celeris-1-decision",
"state": {"claim": {"amount": 13.50, "category": "meals"}},
"questions": {
"matches": {"type": "noul", "instructions": "The receipt total matches the claimed amount."}
},
"x_celeris": {
"images": {"receipt": "data:image/png;base64,'"$IMAGE_B64"'"},
"max_soft_tokens": 560
}
}'
import base64
from typesafe_sdk import Noul
with open("receipt.png", "rb") as f:
image_b64 = base64.b64encode(f.read()).decode()
response = client.system_one(
state={"claim": {"amount": 13.50, "category": "meals"}},
questions={
"matches": Noul(instructions="The receipt total matches the claimed amount."),
},
extra_body={
"x_celeris": {
"images": {"receipt": f"data:image/png;base64,{image_b64}"},
"max_soft_tokens": 560,
}
},
)
print(response.nouls["matches"].noul)
Usage and cost
The usage block reports input_tokens and output_tokens. Input tokens are
billed at the celeris-1-decision rate, and output tokens are
free. Images count as input tokens.
input_tokens covers everything the model reads for the request, which is more
than your state and questions alone. Read it from the response instead of
estimating it from your payload. The yes/no request above reported 351 input
tokens, about $0.000014.
Errors
An invalid request returns a 422. A 422 whose msg says the
answers do not fit in one response comes back after the model has run, so its
input tokens are charged.
Statuses such as 401, 402, and 429 behave as described in
Errors, and rate limits apply as for any other
model.