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.