DrexDocs

Questions

Choose between noul, choice, and score, shape each one so Drex accepts it, name the ids, and batch questions into one call.

Every request to POST /v1/systemone carries one state and a map of questions. Each question has a type of noul, choice, or score. This guide helps you pick the type, write the JSON that Drex accepts, and fit many questions into one call. To read what comes back, see Reading answers.

Pick the type by the decision you make next

Start from the code that consumes the answer, not from the text you are asking about.

You needTypeExample
A yes or no, with a threshold you setnoulIs the customer asking for a refund?
One label from a fixed set with no orderchoiceIs this ticket about billing, a technical problem, or the account?
A position on an ordered scalescoreHow upset is the customer, from calm to angry?

If the labels have an order, use score and you get a fractional position on the scale. If the labels have no order, use choice. If there are only two outcomes, use noul and compare the probability with a threshold.

Shape a noul question

noul
{
	"type": "noul",
	"instructions": "Is the customer asking for a refund?",
	"criteria": {
		"true": "The customer asks for money back",
		"false": "The customer asks for anything else"
	}
}

Drex applies these rules:

  • instructions is optional, but when present it must be a string. null and JSON objects are rejected with must be a string.
  • criteria is optional. Only the true and false keys are read. Each value is a string or null. Other keys are ignored.
  • The question needs a non-empty instructions or at least one non-empty criterion. Otherwise Drex rejects it with noul needs instructions or criteria.

The TypeSafe SDK helper noul() defaults instructions to null. Drex rejects that, so pass a string: noul("Is the customer asking for a refund?"). See Migrate from TypeSafe.

Shape a choice question

choice
{
	"type": "choice",
	"instructions": "What is this support ticket about?",
	"criteria": {
		"billing": "Payments, invoices, refunds, or charges",
		"technical": "Bugs, errors, or outages",
		"account": "Login, profile, or permissions",
		"other": null
	}
}
  • criteria is required and must be an object. A string or an array is rejected with must be an object with options.
  • It needs at least one label. An empty object is rejected with choice needs at least 1 option.
  • Each description is a string or null. A number or an object is rejected with must be a string or null.

The keys of criteria are the labels that come back in choice and probabilities. Keep them short and machine friendly, such as billing rather than Billing and payments.

Shape a score question

score
{
	"type": "score",
	"instructions": "How upset is the customer?",
	"criteria": ["Calm", "Mildly annoyed", "Frustrated", "Angry"]
}
  • criteria is required and must be an array of strings. Anything else is rejected with must be an array of level labels.
  • The array index is the score. Calm is 0 and Angry is 3. Order the levels from low to high.
  • The API accepts one level or more. An empty array is rejected with score needs at least 1 level. The TypeSafe SDK requires at least two levels, and a one-level scale gives the model nothing to choose between, so use two or more.

Name question ids

The keys of questions become the keys of answers. An id must be non-empty. Drex rejects a blank id with question ids must be non-empty.

Name each id for the field it fills in your code, for example wants_refund, category, sentiment. An id like q1 forces every reader of the answer to look up what q1 asked.

Batch questions into one call

One request accepts up to 512 questions about the same state. Drex bills input tokens, and a batch sends the state once instead of once per question, so one call with several questions costs less than one call per question.

We measured this with a 27-word support ticket. Output is an example and varies slightly per call.

Requestusage.input_tokens
1 noul question54
4 questions (2 noul, 1 choice, 1 score) in one call127
The same 4 questions as 4 separate calls (54 + 78 + 55 + 54)241

The batch used 47% fewer input tokens than the separate calls. The answers matched. wants_replacement was 0.9988 in both runs, the category probabilities were identical to three decimals, and the urgency score was 1.522 in the batch and 1.528 alone.

batch.ts
import { TypeSafeClient, choice, noul, score } from "@typesafe-ai/sdk";

const client = new TypeSafeClient({
	apiKey: process.env.DREX_API_KEY,
	baseURL: "https://console.nace.ai",
	defaultModel: "drex-v1.0",
});

const { answers, usage } = await client.systemOne({
	state:
		"Hi, I ordered a standing desk on March 3 and it arrived with a cracked top. I'd like a replacement, not a refund. Order #48213.",
	questions: {
		wants_replacement: noul("Is the customer asking for a replacement?"),
		category: choice("What is this ticket about?", {
			damaged_item: "Item arrived broken or damaged",
			wrong_item: "A different item was delivered",
			refund: "Customer wants money back",
			other: null,
		}),
		urgency: score("How urgent is this ticket?", ["Low", "Medium", "High"]),
		has_order_id: noul("Does the message include an order number?"),
	},
});

console.log(answers.category.choice, answers.urgency.score, usage.input_tokens);

More than 512 questions is rejected with at most 512 questions allowed. The state must fit the model's state limit, and the state plus your longest question must fit its row limit: 131,072 and 139,264 tokens on drex-v1.5. See State.

Write criteria that move the answer

Descriptions on choice labels change where the probability goes. We asked how to moderate the comment "This is the last time I'm warning you. Stop posting here or else." with the labels allow, flag, and remove, first with null descriptions and then with short ones.

Descriptionsallowflagremoveconfidenceinput_tokens
All null0.180.480.340.2235
remove is "Explicit threat of violence or doxxing", flag is "Possible intimidation, send to a human reviewer", allow is "No policy violation"0.330.520.150.2757

The comment is a vague threat. Once remove was defined as an explicit threat, its probability fell from 0.34 to 0.15 and the mass moved to allow and flag. Write a description for any label whose boundary is not obvious from its name. Each description costs input tokens, so keep descriptions short and skip them for labels like other.

For noul, criteria.true and criteria.false say what counts as yes and what counts as no. We asked whether the review "Would be great if the settings page didn't take five seconds to open every time." is a bug report, with the same instructions each time:

criterianoulinput_tokens
None0.350533
true: "Anything that does not work as expected, including slowness", false: "A request for a new feature"0.969653
true: "A crash, an error, or a broken feature", false: "Slowness, polish, or a wish"0.016754

Without criteria the review is borderline. Each set of criteria settles it in its own direction. Use criteria when your team's definition of yes differs from the everyday meaning of the question. A sentence in instructions also works. "Is this review a bug report? Treat slowness and performance complaints as bugs." returned 0.6552 at 43 input tokens. Criteria state both sides of the boundary, so they moved the answer further here.

Fix a 422

When a question is malformed, Drex returns HTTP 422 with error.type of invalid_request_error. error.issues lists every problem with a path into your request body. error.message repeats the first one as path: message.

422 response
{
	"error": {
		"type": "invalid_request_error",
		"message": "questions.category.criteria: choice needs at least 1 option",
		"issues": [
			{ "path": "questions.category.criteria", "message": "choice needs at least 1 option" }
		]
	},
	"request_id": "req_<32 hex characters>"
}

These are the messages a question mistake produces. a stands for your question id.

Mistakepathmessage
Unknown typequestions.a.typemust be "noul", "choice", or "score"
Question is not an objectquestions.amust be an object
instructions is null or an objectquestions.a.instructionsmust be a string
noul with no instructions and no criteriaquestions.a.instructionsnoul needs instructions or criteria
noul criteria is a stringquestions.a.criteriamust be an object
choice criteria is an array or stringquestions.a.criteriamust be an object with options
choice criteria is {}questions.a.criteriachoice needs at least 1 option
choice description is a numberquestions.a.criteria.<label>must be a string or null
score criteria is not an arrayquestions.a.criteriamust be an array of level labels
score criteria is []questions.a.criteriascore needs at least 1 level
score level is not a stringquestions.a.criteria.<index>must be a string
questions is {}questionsmust contain at least one question
questions is not an objectquestionsmust be an object
More than 512 questionsquestionsat most 512 questions allowed
Blank question idquestionsquestion ids must be non-empty
model is not a drex-* name, for example jev-latestmodelunknown model "jev-latest"; use "drex-v1.0", "drex-v1.5" or "drex-latest"

A 422 is not retryable. Fix the body and send it again. For the other statuses, see Errors and retries and the full error reference.

On this page