5.3 KiB
@hanzo/kai
TypeScript client for Kai decisions on api.hanzo.ai. Ask typed questions about a state — pick a label, judge a
statement, rate on a scale — and get calibrated probabilities back, each answer typed by the question that asked it.
Docs: https://docs.hanzo.ai/docs/kai · Moving from TypeSafe's Jev: https://docs.hanzo.ai/docs/guides/migrate/jev
Install
npm i @hanzo/kai
ESM and CommonJS, types included, no dependencies. Node 20 or later, or any runtime with fetch.
Quickstart
import { Kai, choice, noul, score } from "@hanzo/kai";
const kai = new Kai(); // reads HANZO_API_KEY
const d = await kai.decide({
state: { subject: "Charged twice for March", body: "Two charges of $49 on my card. Please refund one." },
questions: {
team: choice("Which team should handle this ticket?", {
billing: "charges, invoices, refunds and payment methods",
technical: "bugs, errors, outages and integrations",
}),
billing: noul("This ticket is about billing.", {
true: "it concerns charges, invoices or refunds",
false: "it concerns something else",
}),
urgency: score("How urgent is this ticket?", ["can wait", "this week", "today", "right now"]),
},
});
d.answers.team.choice; // "billing" | "technical"
d.answers.team.probabilities.billing; // 0 to 1
d.answers.billing.noul; // P(true)
d.answers.urgency.score; // expected level, 0 to 3
d.answers.urgency.legend[3]; // "right now"
examples/decide.ts runs this against the live API.
Questions and answers
| builder | criteria | answer |
|---|---|---|
choice(instructions, criteria) |
{ label: description | null } or [label, …] |
choice, confidence, probabilities by label |
noul(instructions?, criteria?) |
{ true?: description, false?: description } |
noul: P(true); confidence: |2p − 1| |
score(instructions, criteria) |
[level0, level1, …], lowest first |
score: Σ i·pᵢ; confidence; legend and probabilities keyed "0", "1", … |
A choice's or score's confidence is (n·p_max − 1)/(n − 1): 0 when every option is equally likely, 1 when one takes
all the mass. Answers also carry answer_confidence, the calibrated probability of the answer given.
Instructions are optional; they, descriptions and state take text, a JSON object or a JSON array. Give a noul its criteria, or ask a choice: Kai answers a bare yes/no question less reliably than one whose true and false are described.
Configuration
| option | environment | default |
|---|---|---|
apiKey |
HANZO_API_KEY |
required |
baseURL |
HANZO_BASE_URL |
https://api.hanzo.ai |
model |
KAI_MODEL |
kai |
logLevel |
KAI_LOG_LEVEL |
warn |
timeout |
60000 ms per attempt | |
retry |
see below | |
defaultHeaders, fetch, logger, dangerouslyAllowBrowser |
Each call takes { signal, timeout, retry, headers }, overriding the client. The client refuses to run in a browser
page, where the key would be readable, unless dangerouslyAllowBrowser is set.
Errors, retries, timeouts
A status outside 2xx throws its class, carrying the server's sentence as message plus status, code,
requestId, body and retryAfter (seconds):
| status | class |
|---|---|
| 400 malformed JSON, unknown model | BadRequestError |
| 401 bad key | AuthenticationError |
| 402 insufficient balance | PaymentRequiredError |
| 403 key kind not allowed | PermissionDeniedError |
| 404 | NotFoundError |
| 422 outside the schema, or over reach | UnprocessableEntityError |
| 429 rate limited | RateLimitError |
| 5xx, 529 overloaded | InternalServerError |
A refusal over reach names what is over in code, on either path: state_too_long (the state with a question
does not fit what the model reads), question_too_long (a question over half of it), option_too_long (an
option over 512 tokens), request_too_long (a body over 16 MiB).
No response throws APIConnectionError, or APITimeoutError past the timeout; an aborted signal throws
APIUserAbortError. Every class extends KaiError.
Two retries by default, on 408, 409, 429, 5xx (529 included), connection errors and timeouts, backing off from 0.5 s
doubling to 8 s, less up to 25% at random. Retry-After and retry-after-ms are honoured up to 60 s. Each retry
sends X-Kai-Retry-Count.
const kai = new Kai({ retry: { maxRetries: 4, httpStatuses: new Set([429, 503]) } });
await kai.decide(request, { timeout: 5_000, retry: { maxRetries: 0 }, signal });
Raw responses and models
const { data, response, requestId } = await kai.decide(request).withResponse();
const raw = await kai.decide(request).asResponse(); // body unread
const models = await kai.models.list(); // the models that answer decisions: kai, hanzo/kai
Jev compatibility
@hanzo/kai/jev answers in Jev's shapes on /v1/systemone, so a TypeSafe program ports by its import alone:
import { choice, noul, score, Client as TypeSafeClient } from "@hanzo/kai/jev". Model kai answers with Kai's
versioned id, kai- and the first 12 hex digits of its weights' SHA-256, which both paths also take; a bare Jev id
such as jev-latest is refused with 400, while typesafe/jev-1.13 and ~typesafe/jev-latest reach Jev itself,
billed at $0.042 per 1M input tokens. models.list() gives the decision models as Jev's cards.
License
Apache-2.0