Using Jev
September 17, 2026 ยท View on GitHub
Complete the Jev setup once, then use these commands
in the activated environment. TypeSafeProvider connects to Jev using
TYPESAFE_API_KEY; the examples below make real API requests.
Terminal sessions
from decido import Binary, Choice, Ordinal, RuntimeLimits, SyncSession
from decido.providers.typesafe import TypeSafeProvider
session = SyncSession(
TypeSafeProvider(),
limits=RuntimeLimits(max_provider_calls=10, timeout_seconds=30),
).start()
state = "The live checkout is down. New purchases fail for every customer."
questions = {
"outage": Binary("Does the message describe an ongoing service outage?"),
"team": Choice(
"Which team should handle this? Use insufficient_evidence if unclear.",
("billing", "technical", "sales", "insufficient_evidence"),
),
"impact": Ordinal(
"How severe is the explicitly described service disruption?",
("no disruption", "partial disruption", "service unavailable"),
),
}
result = session.decide(state=state, questions=questions)
result.binary("outage").p_true
dict(result.choice("team").distribution.probabilities)
result.ordinal("impact").expected_level
result.traces
session.close()
These three questions share one state and fit one Jev request. The session keeps
the event loop and connections alive across calls. start() and close() are
idempotent; a closed session cannot be reopened. Construct a new one when needed.
It creates no browser unless you explicitly pass a backend. Resources with async
context managers are entered and closed by the session; plain protocol objects need
no lifecycle. An injected TypeSafe SDK client remains caller-owned.
Save every probability
import json
from pathlib import Path
from decido import decision_record
record = decision_record(result, state=state, questions=questions, version="1")
record["feature_vector"]
Path("reports").mkdir(exist_ok=True)
Path("reports/decision.json").write_text(json.dumps(record, indent=2), encoding="utf-8")
Use the state and questions from the same call. The record includes both binary probabilities, every choice/ordinal category, derived selections, provider confidence when supplied, provenance, exact inputs, hashes, and traces. Changing an input later does not mutate the record. It contains source text; treat it with the same access rules as its inputs. The exporter checks answer IDs/support, but cannot prove you gave it the original state.
Async applications
DecisionModel is the underlying async API. In a notebook, run the body with
top-level await; do not use SyncSession inside an active event loop.
import asyncio
from decido import Choice, DecisionModel
from decido.providers.typesafe import TypeSafeProvider
async def main():
async with TypeSafeProvider() as provider:
model = DecisionModel(provider)
result = await model.decide(
state="Every API request fails after our signing-key rotation.",
questions={
"team": Choice(
"Which team should handle this? Use insufficient_evidence if unclear.",
("billing", "technical", "insufficient_evidence"),
),
},
)
print(dict(result.choice("team").distribution.probabilities))
if __name__ == "__main__":
asyncio.run(main())
Probability semantics
Binary.p_true is the positive-class probability; the other class has mass
1 - p_true. A binary question has no explicit unknown category. Use Choice
when you need one. Ordinal.expected_level is the weighted zero-based level index;
use it only for a genuinely ordered rubric. Do not insert unknown into that order.
Answers carry provider/model identity, native, elicited, or heuristic_score
semantics, and calibration status. Jev's adapter reports native and unverified:
calibration on your task must be measured. provider_confidence is preserved
separately from the winning category's probability. Unknown token/cost usage is
null, never an invented zero. The offline provider reports zero billable usage.
Support must match the question exactly. Probabilities must be finite, within
[0, 1], and sum to one within 1e-6; invalid responses raise errors rather than
being silently repaired. rank returns independent relevance scores, not a
distribution over candidates. Stable ties preserve supplied candidate order.
Limits and errors
RuntimeLimits defaults to 128 attempted requests per model/session, a 30-second
request timeout, 8,192 questions per operation, and 120,000 characters per compiled
request. The character cap is not a token estimate. Requests execute in sequential
batches; the TypeSafe adapter uses at most 32 questions per batch.
An operation reserves its required request count before starting. Failed or
cancelled attempts consume the budget; requests never attempted are released.
Limits are local to this model/session, not your account. SDK retries are disabled
and there is no automatic paid fallback. Exceptions derive from DecisionError
for provider/runtime failures; invalid configuration may raise ValueError and
timeouts raise TimeoutError. Traces retain metadata, not input bodies or keys.