Drex
Ask questions
client.systemOne(request, options?) returns APIPromise<SystemOneResult<Q>>, where Q is the type of your questions.
| Parameter | Type | Required | Description |
|---|---|---|---|
request.state | EntryType | Yes | Any JSON value to evaluate: text, a number, a boolean, an object, an array or null. See State. |
request.questions | Questions | Yes | 1 to 512 questions, each keyed by the name its answer comes back under. Build them with noul(), choice() and score(), or write plain objects. See Questions. |
request.model | string | No | The model for this call. Default: the client's defaultModel. |
request.images | readonly ImageInput[] | No | Drex Max only, so set model: "drex-max" with it: up to 8 images that every question sees, in order. See Images. |
options | RequestOptions | No | The second argument, not a field of request: signal, timeout, retry and headers for this call. See Per-call options. |
request is a SystemOneRequest<Q>. Any other field on it is sent in the body as it is.
import { choice, NaceClient, noul, score } from "nace-sdk";
const client = new NaceClient();
const { answers, usage, request_id } = await client.systemOne({
state: "I was charged twice for my March invoice.",
questions: {
wants_refund: noul("Is the customer asking for a refund?"),
topic: choice("Which topic is it?", { billing: null, shipping: null, other: null }),
urgency: score("How urgent is it?", ["low", "medium", "high"]),
},
});
answers.wants_refund.noul; // number
answers.topic.choice; // "billing" | "shipping" | "other"
answers.urgency.probabilities["2"]; // probability of "high"
console.log(usage.input_tokens, request_id);The answer types come from your questions, so a misspelled question name or label is a compile error. A decision is billed per input token, and only when it returns 200, so the client's retries never bill twice.
systemOne checks the questions before it sends anything, and throws a NaceError for:
- no questions, or more than 512;
- a noul with neither
instructionsnor a non-emptytrueorfalsecriterion; - a choice whose criteria aren't a non-empty map of labels;
- a score whose criteria aren't a non-empty list.
An instructions left null is dropped from the body, since Drex refuses null there. A criteria left null is dropped too; on a noul, Drex reads that the same as no criteria.
Question helpers
| Helper | Returns | Description |
|---|---|---|
noul(instructions?, criteria?) | NoulQuestion | A yes/no question. instructions defaults to null. criteria is { true?, false? }, a description of each outcome. |
choice(instructions, criteria) | ChoiceQuestion<T> | Picks one label. criteria maps each label to a description, or to null. Throws a NaceError when criteria is a list. |
score(instructions, criteria) | ScoreQuestion<T> | Rates on an ordered rubric: criteria is a list of descriptions, one per score from zero. Throws a NaceError when criteria is a map. |
instructions is a string or null. Plain objects work too, such as { type: "noul", instructions: "Is it urgent?" }.
Answer types
SystemOneResult<Q> is what systemOne resolves to:
| Field | Type | Description |
|---|---|---|
model | string | The model version that answered. |
answers | { [K in keyof Q]: ResultFor<Q[K]> } | One answer per question, under the question's name. |
usage | Usage | input_tokens, which Drex bills, output_tokens, and optional image_tokens (number), already included in input tokens. |
evaluation_time_ms | number | How long the model took. |
request_id | string | Drex's request id. Quote it in support requests. |
| Type | What it holds |
|---|---|
NoulResponse | type: "noul" and noul, the probability of yes, from 0 to 1. |
ChoiceResponse<T> | type: "choice", choice (one of your labels), confidence and probabilities, keyed by label. |
ScoreResponse<T> | type: "score", score (the expected score, which can fall between levels), confidence, legend (your rubric, as a list) and probabilities, keyed by score. |
ResultFor<T> | The answer type for question type T: NoulResponse, ChoiceResponse or ScoreResponse, keeping your labels. |
ScoreOf<T> | The score keys of rubric T: "0" | "1" | "2" for a three-entry rubric written in the call, number for one whose length isn't known. |
Usage | input_tokens, output_tokens, and optional image_tokens (number). |
See Reading answers for what each number means.
Request types
| Type | What it is |
|---|---|
JsonValue | Any JSON value. |
EntryType | The type of state: a JsonValue. |
Description | A criterion's description: string | null. |
NoulQuestion | { type: "noul", instructions?, criteria?: { true?, false? } | null }. |
ChoiceCriteria | Labels mapped to a Description. |
ChoiceQuestion<T> | { type: "choice", instructions?, criteria: T }. |
ScoreCriteria | readonly [string, ...string[]]: at least one description. |
ScoreQuestion<T> | { type: "score", instructions?, criteria: T }. |
Question | NoulQuestion | ChoiceQuestion | ScoreQuestion. |
Questions | Questions keyed by name. |
SystemOneRequest<Q> | systemOne's first argument: state, questions, optional model and optional images. |
SystemOneRequestPayload | The body sent to POST /v1/systemone: a SystemOneRequest with model set. |
ImageInput | One images entry: string | WorkspaceFileSource. |
Images
Drex Max (drex-max) is the only model that reads images. Pass them in images and refer to them as "Image 1", "Image 2" and so on in the state or the instructions; media inside state or questions returns 422. Each entry, an ImageInput, is one of:
- a
WorkspaceFileSourcefor a file you uploaded withdocuments.upload. It doesn't count toward the 4 MiB request body, so use it for anything but small images; - a standard base64 string, or a data URL such as
data:image/jpeg;base64,....
A file in another account's returns 422, and so does images with any model but drex-max. Images are PNG, JPEG or WebP, up to 8 per request, 16 MiB each and 32 MiB in all. Their tokens are in usage.image_tokens, already counted in input_tokens.
| Helper | Returns | Description |
|---|---|---|
imageInput(content) | ImageInput | content is a Uint8Array, an ArrayBuffer or an UploadedFile. An UploadedFile returns its WorkspaceFileSource, as workspaceFile(file) does. Bytes return a data URL typed from their first bytes; they count toward the 4 MiB request body, base64 expansion included. Throws a NaceError for bytes that aren't a PNG, JPEG or WebP image, or are over 16 MiB. |
import { readFile } from "node:fs/promises";
import { imageInput, NaceClient, noul } from "nace-sdk";
const client = new NaceClient();
const receipt = await client.documents.upload(await readFile("receipt.jpg"), { file_name: "receipt.jpg" });
const { answers, usage } = await client.systemOne({
model: "drex-max",
state: "Image 1 is a scanned receipt. Image 2 is the shop's logo.",
images: [imageInput(receipt), imageInput(await readFile("logo.png"))],
questions: {
same_shop: noul("Was the receipt in Image 1 issued by the shop whose logo is Image 2?"),
},
});
console.log(answers.same_shop.noul, usage.image_tokens);Read the raw response
Most methods return an APIPromise<T>. Await it for the parsed body, or call one of its methods:
| Method | Returns | Description |
|---|---|---|
withResponse() | Promise<WithResponse<T>> | { data, response, requestId }: the parsed body, the Response (its body already read) and the x-request-id header. |
asResponse() | Promise<Response> | The Response with its body unread. Read the body yourself, and don't also await the parsed result. A non-2xx answer still rejects with an APIError. |
map(fn) | APIPromise<U> | A new APIPromise of fn(data), sharing the response and its single parse. |
const { data, response, requestId } = await client
.systemOne({ state: "Refund my order", questions: { refund: noul("Is it a refund request?") } })
.withResponse();
console.log(response.status, requestId, data.answers.refund.noul);systemOne, models.list, the document creates, createUploadGrant, createUploadSession, getUploadSession, deleteFile, jobs.get, jobs.list, jobs.getRequest, jobs.rows, jobs.fileLink and the extractionSchemas reads and creates return an APIPromise. documents.upload, uploadPart, completeUploadSession, abortUploadSession, jobs.wait, jobs.delete, jobs.fetchFile, jobs.download and extractionSchemas.delete return a plain Promise, and the iter, iterVersions and events methods are async generators.
List models
client.models.list(options?) returns APIPromise<ModelCard[]>, the models your key can use. It's free and works at any balance, so it doubles as a key check.
| Field | Type | Description |
|---|---|---|
name | string | The value to pass as model. |
description | string | What the model is. |
release_date | string | When the version was released. |
alias_for | string | null | The version an alias such as drex-latest points to, or the version that now serves a retired one. null for a pinned version. |
for (const model of await client.models.list()) {
console.log(model.name, model.alias_for);
}See Models.