Console

Guides

Coming from Jev

Wity speaks the same System One request and answer format as Jev, so an existing integration switches over by changing two settings. This guide covers what stays the same, what the response adds, the behaviour differences worth knowing, and a safe way to roll the switch out.

Switch in two settings#

Point your client at Wity's base URL and use a Wity key. If your client sends a model name, set it to wity-1; Wity accepts any value, so this is only for your own logs.

# before
TYPESAFE_ENDPOINT=https://api.typesafe.ai
TYPESAFE_API_KEY=...
# after
TYPESAFE_ENDPOINT=https://wity-proxy-production-2c33.up.railway.app
TYPESAFE_MODEL=wity-1
TYPESAFE_API_KEY=wity_...

Code that already calls /v1/systemone needs no changes. The one thing worth checking is the client timeout: Wity answers most questions in about a tenth of a second, but a close call can take a few seconds while it thinks. Allow at least 60 seconds.

import os, requests
BASE = os.environ["TYPESAFE_ENDPOINT"]
KEY = os.environ["TYPESAFE_API_KEY"]
def decide(state, questions):
r = requests.post(
f"{BASE}/v1/systemone",
headers={"Authorization": f"Bearer {KEY}"},
json={"state": state, "questions": questions},
timeout=60, # Wity may think on close calls; allow time for it
)
r.raise_for_status()
return r.json()["answers"]

What stays the same#

  • The endpoint path /v1/systemone (also served as /v1/system-one) and bearer-token authentication.
  • The request: state as a string, object or array, and questions with type, instructions and criteria. A model field is accepted and ignored.
  • The three question types and their answers: choice with probabilities over every option, noul as the probability of yes, and score with an expected level and level probabilities.
  • Answers come back under the names you gave the questions, so code that reads them by name keeps working.

Here is an unchanged Jev request, model field and all, sent to Wity:

{
"model": "jev-1.13.0",
"state": "Customer says the card reader at the Harbour St store has shown 'connection lost' since this morning. Payments are going through on the backup terminal.",
"questions": {
"team": {
"type": "choice",
"instructions": "Which team should handle this?",
"criteria": {
"payments": "Card terminals, payment failures, refunds",
"network": "Store connectivity, Wi-Fi, routers",
"hardware": "Physical damage or replacement of devices",
"other": "Anything else"
}
},
"outage": {
"type": "noul",
"instructions": "Is the store unable to take payments right now?"
}
}
}

What the response adds#

The answer fields a Jev client reads keep their names and meaning. Wity adds a few fields next to them. A client that ignores fields it does not know, as most JSON clients do, keeps working without changes.

{
"model": "wity-1",
"answers": {
"team": {
"type": "choice",
"choice": "network",
"probabilities": { "payments": 0.31, "network": 0.66, "hardware": 0.02, "other": 0.01 },
"confidence": 0.44,
"direct_probabilities": { "payments": 0.49, "network": 0.47, "hardware": 0.03, "other": 0.01 },
"reasoning": { "mode": "auto", "thought": true, "reason": "close_call", "forecast": false, "thought_tokens": 162 }
},
"outage": {
"type": "noul",
"noul": 0.04,
"reasoning": { "mode": "auto", "thought": false, "reason": null, "forecast": false, "thought_tokens": null }
}
},
"usage": { "input_tokens": 312, "output_tokens": 0 },
"metadata": { "reasoning": "auto", "elapsed_ms": 1620.4 }
}
confidence
choice, score
How concentrated the whole distribution is. Useful for sorting and monitoring; it is not the chance of being right. See Probabilities & confidence.
reasoning
per answer
Whether Wity thought before answering, and why: close_call, order_sensitive, forecast or requested.
direct_probabilities
when it thought
What Wity would have answered without thinking (direct_noul for noul questions).
usage
billing
input_tokens is what you are billed for; output_tokens is always 0 for decisions.
metadata
timing
The reasoning mode used and elapsed_ms on our side.

In the example, the ticket mentions a card reader, which pulls towards payments, but the actual symptom is "connection lost". The direct read was a coin flip (0.49 / 0.47), so Wity thought about it and settled on network. The outage question was clear straight away (payments are still going through on the backup terminal), so it was answered without thinking. Both came back in the same response.

Behaviour differences#

Thinking is on by default

Jev answers every question in a single pass. Wity's reasoning field defaults to "auto": clear questions still get an instant direct answer, and close calls get a short thought first. Your code sees the same answer shape either way; only the latency differs.

  • Keep auto for most workloads. You get Jev-like speed on the easy majority and better answers on the hard minority.
  • Set "off" where every call has a hard latency budget, such as a step in an interactive UI. That matches a single-pass setup exactly.
  • Set "always" for batch jobs where each decision is expensive to get wrong and nobody is waiting.

See Reasoning & auto mode for how auto mode decides.

You can stop decomposing hard questions

Jev's guidance for questions that need reasoning is to break them into simpler steps and chain the calls. That works around a model that cannot think, and it costs a round trip and a request's worth of tokens per step.

# With a single-pass model, a hard question is often broken into steps
days = decide(state, {"days": {"type": "choice", "instructions": "How many days since delivery?", "criteria": DAY_BUCKETS}})
faulty = decide(state, {"faulty": {"type": "noul", "instructions": "Is the item faulty?"}})
eligible = decide({**state, "days": days, "faulty": faulty},
{"eligible": {"type": "noul", "instructions": "Given the above, is a refund due?"}})

With Wity, ask the question you actually have. When the direct answer is uncertain, auto mode reasons through the steps itself, in one call:

# With Wity, ask the question you actually have
answers = decide(state, {
"eligible": {
"type": "noul",
"instructions": "Under the policy in the state, is the customer owed a refund?",
}
})
# reasoning defaults to "auto": Wity thinks through the dates and the policy
# only when the direct answer is not clear.

Keep separate questions when you need the separate answers, for example because your code branches on whether the item was faulty. Just do not split one judgement into steps for the model's sake. See Writing good questions.

Option order no longer matters

Wity checks that each answer holds whichever order the options are presented in, and thinks when it does not (the order_sensitive reason). If you added a workaround for order effects, such as shuffling options and averaging several calls, you can remove it and make one call instead.

Re-tune your thresholds

Wity's probabilities are on the same 0 to 1 scale, but they are not Jev's probabilities. A threshold you tuned on Jev (act on its own above 0.8, send to a person below) is a sensible starting point, not a finished setting. Run your test set through Wity, look at where right and wrong answers fall, and set the thresholds again. See Probabilities & confidence.

What you can use now#

Once you are on Wity, these work from the same key with no other setup:

  • Images. Add an image to a request and every question sees it with the state: a photo of the damage, a screenshot for an agent, a scanned form. See Images.
  • generate. Jev returns decisions only. Wity's /v1/generate writes short text or JSON in a shape you define, from the same state: the value to type after deciding to type. See Generate.
  • Forecasts. How-likely questions come back with worked-out odds instead of a lean. See Forecast questions.
  • Billing on your input only. Thinking and answers are never billed, so auto mode does not change what a request costs. See Pricing.

Sessions, which keep a running state across a whole task instead of one state per request, are coming soon. The /v1/systemone format will stay stable as they arrive.

Rolling it out#

A switch you can trust takes a few steps, not one:

  • Replay your test set. Run the cases you already use to check Jev through Wity with the new settings, and compare answer by answer.
  • Set thresholds and modes. Re-tune thresholds on those results, and pick a reasoning mode for each workflow based on its latency budget.
  • Shadow live traffic. Keep acting on Jev's answers, send a sample of the same requests to Wity, and log where the two disagree. Read the disagreements: they show you which cases change.
  • Switch. Once the disagreements look like improvements, change the two settings in production. Rolling back is the same change in reverse.

Here is a simple shadow wrapper. Production still gets Jev's answer, and a failure on the Wity side is logged and ignored:

import json, random, requests
def call(base, key, body):
r = requests.post(f"{base}/v1/systemone", headers={"Authorization": f"Bearer {key}"}, json=body, timeout=60)
r.raise_for_status()
return r.json()["answers"]
def decide(state, questions):
body = {"state": state, "questions": questions}
live = call(JEV_BASE, JEV_KEY, body) # still what production acts on
if random.random() < 0.10: # shadow 10% of traffic
try:
shadow = call(WITY_BASE, WITY_KEY, body)
for name, a in live.items():
b = shadow[name]
if a.get("choice", a.get("noul")) != b.get("choice", b.get("noul")):
log.info("disagree %s", json.dumps({"q": name, "jev": a, "wity": b}))
except Exception:
log.exception("shadow call failed") # never let the shadow break production
return live

Benchmarks

JevBench's own Jev adapter runs against Wity unchanged with the settings above. We verified it end to end on all 231 public decisions. See the benchmarks for how the two compare.