Backend guide · thunc 0.1.3+

Getting started with the Jev backend

Jev is TypeSafe's judgment model. It doesn't write text; it answers yes/no, label and rating questions with calibrated probabilities. Use it for the functions that only decide something.

~0.3 s per call bool · Literal[...] only needs the jev CLI

Set it up

  1. Install thunc

    The jev backend is in thunc 0.1.3 and later. It needs no extra Python packages.

    pip install --upgrade thunc
  2. Get a TypeSafe API key

    Create one in the TypeSafe console. You'll paste it into jev login in step 4, and nowhere else.

  3. Install the jev CLI

    On a Mac with Apple Silicon, use TypeSafe's Homebrew tap. Homebrew asks you to trust a tap's formulas before it installs them; trusting only jev is enough.

    macOS (Apple Silicon)
    brew tap model-clis/packages
    brew trust --formula model-clis/packages/jev
    brew install jev

    On Linux, or a Mac without Homebrew, the install script checks the download's SHA-256 and puts jev in ~/.local/bin. For Windows and other options, see the jev README.

    Linux and macOS
    curl -fsSL https://raw.githubusercontent.com/model-clis/jev/main/scripts/install.sh | sh
  4. Log in

    jev login asks for your key, checks it with TypeSafe and stores it in ~/.model-clis/jev/credentials.json, readable only by you.

    jev login

    In CI or a container, set JEV_API_KEY instead. It takes precedence over the stored login. thunc never sends configure(api_key=...) to Jev, because that is usually the key of another backend.

  5. Check that it works

    One call costs a fraction of a cent. A probability near 1 means the login works.

    jev noul "Is the sky blue?" --state "A clear summer day"
    Output
    {"answers":{"q":{"noul":0.95,"type":"noul"}},"latency_ms":265,"model":"jev-1.13.0","ok":true,...}
  6. Point thunc at it

    Pick Jev for one function with backend="jev", or for everything with thunc.configure(backend="jev") or THUNC_BACKEND=jev. thunc never picks it on its own.

    from typing import Literal
    import thunc
    
    @thunc.function(backend="jev")
    def team(ticket: str) -> Literal["bug", "billing", "feature-request", "other"]:
        """Which team should handle this customer support ticket?"""
        ...
    
    team("I was charged twice this month.")  # -> "billing"

What Jev can answer

Jev picks from answers you define, so only these return types work. Any other type raises ThuncError before a request is sent.

Return typeJev questionYou get
boolyes/no (noul)True when a yes is at least as likely as a no
Literal["bug", "billing", ...]choice, up to 255 labelsThe label Jev chose
Literal[1, 2, 3, 4, 5]score, levels lowest firstThe most likely level, as an int
str, int, float, list, dict, dataclasses, T | NonenoneThuncError; use another backend

For a rating, use Literal[1, 2, 3, 4, 5] instead of int with ensure=.

Use it next to Claude

Jev can't write text, so most programs use it for the decisions and another backend for the writing. Each function picks its own backend. Here Jev filters every message, and Claude drafts a reply only for the ones that need one.

thunc.configure(backend="anthropic")  # the default, for text

@thunc.function(backend="jev")
def needs_reply(message: str) -> bool:
    """Does this message ask a question or report a problem we should answer?"""
    ...

@thunc.function
def draft_reply(message: str) -> str:
    """Write a short first reply to this message. Never promise a refund."""
    ...

for message in inbox:
    if needs_reply(message):  # ~0.3 s, fractions of a cent
        print(draft_reply(message))

How thunc's options behave

inputs
Sent as Jev's state. The docstring or instructions become the question.
system=
Your own system prompt goes before the question as context. thunc's default prompt is written for text models, so it isn't sent.
model=
Ignored. The CLI always uses jev-latest, which the cache and trace record.
ensure=
A rejected answer raises ThuncError at once. Asking again would get the same answer.
cache=, trace=
Work as for any backend.
thunc.map
Works as usual. 8 workers stay under Jev's limit of about 20 requests per second.

Try the examples

From a clone of the repository, after step 4:

python3 -m examples.jev_hello        # a yes/no, a label and a rating
python3 -m examples.jev_inbox        # 8 tickets, 24 calls, about 1 s
python3 -m examples.jev_with_claude  # Jev decides, Claude Code writes the replies

To run thunc's live tests on Jev: THUNC_BACKEND=jev pytest live_tests. Tests that need text answers are skipped.

If something goes wrong

You seeWhat to do
`jev` was not found on PATH.Install the CLI (step 3). If you used the install script, add ~/.local/bin to your PATH.
Refusing to load formula model-clis/packages/jev from untrusted tapRun brew trust --formula model-clis/packages/jev, then brew install jev again.
jev exited 1: Error: API rejected the credentialsRun jev login again. If JEV_API_KEY is set, it wins over the login, so check or unset it.
The jev backend answers bool, Literal of strings ...The function's return type isn't one Jev can answer. Use Literal[...], or give that function another backend.
Unknown backend 'jev'Your thunc is older than 0.1.3: pip install --upgrade thunc.