Python and TypeScript clients
Call a Jeff server from Python or TypeScript, choose an adapter by name, and handle errors.
Both clients speak the same request format, refuse number-like option keys, and never retry or guess. Choose an
adapter with model: on the client, or on any single call. The examples below use the base model zero-shot (jeff-latest); name an adapter, such as support-intents, to use it instead.
The clients arrive with the next Jeff release. The Python client is part of the jeff package
(src/jeff/client.py) and needs only the standard
library. The TypeScript client (@jeff/client,
clients/typescript) has no dependencies and runs in
Node 22 or later, Deno, Bun and browsers. It is not on npm yet: build it from the repository and depend on it by path.
Python
from jeff import Client
from jeff.client import choice_question, yes_no_question
jeff = Client("http://localhost:8765", model="jeff-latest")
answers = jeff.ask(
{"company": "An online furniture shop.", "channel": "email",
"message": "My wardrobe arrived with two doors split. I want a replacement."},
{
"route": choice_question(
{"other": "Not for any of these teams", "k1": "Deliveries", "k2": "Refunds and payments"},
"Which team should handle this message? If it covers several issues, choose the team for the most important one.",
),
"needs_human": yes_no_question("Does this need a person to act on it now, rather than an automatic reply? Answer yes for complaints that could escalate, legal or safety issues, or requests an automatic system cannot resolve."),
},
)
print(answers.choice("route").key, answers.yes_no("needs_human"))One question at a time: jeff.choose(state, options, instructions), jeff.yes_no(state, instructions) and
jeff.score(state, levels, instructions). AsyncClient has the same calls for asyncio.
TypeScript
import { Client, choiceQuestion, yesNoQuestion } from '@jeff/client';
const jeff = new Client({ url: 'http://localhost:8765', model: 'jeff-latest' });
const { route, needs_human } = await jeff.ask(
{ company: 'An online furniture shop.', channel: 'email', message: 'My wardrobe arrived with two doors split.' },
{
route: choiceQuestion(
{ other: 'Not for any of these teams', k1: 'Deliveries', k2: 'Refunds and payments' },
'Which team should handle this message? If it covers several issues, choose the team for the most important one.',
),
needs_human: yesNoQuestion('Does this need a person to act on it now, rather than an automatic reply? Answer yes for complaints that could escalate, legal or safety issues, or requests an automatic system cannot resolve.'),
},
);
console.log(route.key, route.probability, needs_human);Several adapters from one program
One client per adapter, all talking to the same server:
clauses = Client("http://localhost:8765", model="legal-clauses")
intents = clauses.with_model("support-intents")
spam = clauses.with_model("spam")In TypeScript: jeff.withModel('spam'). Every call also takes a model argument to override the client's for that
one request.
Other calls
| Python | TypeScript | Does |
|---|---|---|
prepare(state, questions) |
prepare(state, questions) |
Sends the unchanging part ahead of time; see preparing requests |
decide(request) |
decide(request) |
Sends a raw request and returns the raw response |
health() |
health() |
Whether the model is loaded, its name and its option limit |
models() |
models() |
Every model name the server answers to, adapters included |
Passing orders=2 answers every question twice, the second time with the options reversed, and averages the two.
It evens out a small model's lean towards options by position, at twice the cost.
Errors
Every failure raises (Python) or throws (TypeScript) a subclass of JeffError carrying the status, the server's
explanation and the request id: Unauthorised, UnknownModel, TooManyOptions, InvalidRequest, NotReady,
Busy (with retry_after / retryAfter), ServerError, ConnectionFailed and ProtocolError. Retry Busy
yourself if that suits you.
Next: Serving several adapters
