Drex guides

Migrate from TypeSafe

Point the TypeSafe SDK at Drex, then fix the requests Drex rejects on purpose.

Drex is wire-compatible with TypeSafe's Jev. The @typesafe-ai/sdk package works against https://console.nace.ai without a fork. This guide covers the configuration change, the requests that Drex rejects, and a check that the switch worked.

Or switch to the Nace SDK

Moving to the TypeScript or Python Nace SDK is mostly a rename. systemOne / system_one, the question helpers, the answer types, RetryPolicy, the logging options and the error classes keep their arguments:

TypeSafeDrex
@typesafe-ai/sdk, typesafe-sdknace-sdk on npm and PyPI
TypeSafeClient, AsyncTypeSafeClientNaceClient, AsyncNaceClient
TYPESAFE_API_KEY, TYPESAFE_BASE_URL, TYPESAFE_DEFAULT_MODEL, TYPESAFE_LOG_LEVELNACE_API_KEY, NACE_BASE_URL, NACE_DEFAULT_DECISION_MODEL, NACE_LOG_LEVEL
TypeSafeErrorNaceError
TypeSafe*Error in Python, such as TypeSafeRateLimitErrorDrex*Error, such as NaceRateLimitError

What changes besides the names:

  • Defaults: https://console.nace.ai, drex-latest and a 60-second timeout, so none needs setting.
  • Errors: the Nace SDKs add classes for the statuses Drex adds. See how errors work in the Python and TypeScript SDKs.
  • Retries: The total retry budget is disabled by default: timeoutMs: null in TypeScript and RetryPolicy.timeout=None in Python. Set an explicit budget to limit when retries may start. See Errors and retries.
  • Perception: the Nace SDKs also cover the Perception routes.
  • Workspaces: the Nace SDKs also cover workspaces: create them, upload files, and search or ask questions about them.

The rest of this guide is for code that keeps the TypeSafe SDK. Tools that already integrate with TypeSafe work the same way, with a base URL, your Drex key and an explicit model: see DSPy, MLflow, Opik and Weave.

Change three environment variables

The SDK reads its base URL, key, and default model from the environment when the constructor does not set them.

.env
TYPESAFE_BASE_URL=https://console.nace.ai
TYPESAFE_API_KEY=nace_sk_...
TYPESAFE_DEFAULT_MODEL=drex-v2.0

If your code sets these in the constructor instead, change them there. Explicit options take precedence over environment variables.

Before
import { TypeSafeClient } from "@typesafe-ai/sdk";

const client = new TypeSafeClient({
  apiKey: process.env.TYPESAFE_API_KEY,
});
After
import { TypeSafeClient } from "@typesafe-ai/sdk";

const client = new TypeSafeClient({
  baseURL: "https://console.nace.ai",
  apiKey: process.env.NACE_API_KEY,
  defaultModel: "drex-latest",
  timeout: 60_000,
});

Set defaultModel in one of the two places. Without it, the SDK sends jev-latest, and Drex returns 422. The timeout line is explained under Raise the SDK timeout.

Verify the switch

List the models. The SDK unwraps the { "models": [...] } envelope and returns the array.

const models = await client.models.list();
console.log(models.map((m) => m.name));

The list shows the versions, aliases and retired ids available to your key in the target environment. See Models for the current catalog and retirement mappings. GET /v1/models needs a valid key but works with zero balance, so this check does not spend anything. A 401 here means the key is wrong. See Authentication.

What changes

Work through this list before you send production traffic. Each item is a request that TypeSafe accepted and Drex rejects, or a behavior that differs.

Send a drex-* model name

Drex accepts pinned versions such as drex-v2.0, the drex-latest alias, retired ids, and the legacy nacedm-latest name. Anything else returns 422, including a drex-* name that isn't listed in Models, so a half-migrated client fails on the first call instead of silently using the wrong model. A request with "model": "jev-latest" returns an issue whose message starts with unknown model "jev-latest"; use and then suggests the public active model ids and aliases in that environment. These suggestions omit retired ids and models offered only through staff preview. GET /v1/models returns the full catalog available to your key, including authorized previews and retired ids, so the two lists can differ. This illustrative example shows the suffix when Drex 2.0 is the only publicly available active version; the suggestions can differ by environment:

{
  "path": "model",
  "message": "unknown model \"jev-latest\"; use \"drex-v2.0\" or \"drex-latest\""
}

Search your code for hard-coded jev- model names and for calls that pass model per request.

Don't send box questions

Drex doesn't support box questions: it answers 422 and says so. Remove them before you migrate, or keep them on TypeSafe.

Pass criteria descriptions as text

Every description inside criteria must be text. The TypeSafe SDK's types also allow objects and arrays there, but Drex rejects them. instructions is different: a string or null (read as omitted) is used as is, and any other JSON value is rendered to text the way state is, nested at most 8 levels and at most 262,144 characters once rendered (see Questions).

// Rejected: the description is an object
choice("Which team should handle this?", {
  payments: { topics: ["payouts", "refunds"] },
});

// Accepted: the description is text
choice("Which team should handle this?", {
  payments: "Payouts, charges, or refunds",
});
  • choice and noul: a description can also be null, for a label with no description.
  • score: criteria is a list of text labels, one per level, with no null entries.

A wrong description returns 422 with must be a string, and path names the label or level, such as questions.team.criteria.payments.

Send images to Drex Max

Images work with the TypeSafe clients as they are. @ai-sdk/typesafe-ai, @typesafe-ai/sdk and typesafe-sdk send images as a multipart/form-data upload, and Drex reads that form. The served Drex Max models read images, as do retired ids that resolve to Max v1.1; a model that does not support images returns 422 with path images. Images must be PNG or JPEG, and the whole request, image bytes included, must be at most 4 MiB. See Multipart upload.

The AI SDK needs its baseURL to end in /v1:

import { createTypeSafeAi } from "@ai-sdk/typesafe-ai";
import { experimental_decide as decide } from "ai";

const drex = createTypeSafeAi({
  baseURL: "https://console.nace.ai/v1",
  apiKey: process.env.NACE_API_KEY,
});

const { answers } = await decide({
  model: drex.decisionModel("drex-max"),
  state: [
    { type: "text", text: "Inspect this receipt." },
    { type: "file", mediaType: "image/png", data: receiptPng },
  ],
  questions: {
    isReceipt: { type: "boolean", instructions: "Is this a purchase receipt?" },
  },
});

The AI SDK's boolean question needs no change: its provider sends it to Drex as noul, and the answer's noul field is the probability it reads back. Its box question is not supported. Drex returns 422 with the message "box" questions are not supported; use "noul", "choice", or "score".

The TypeSafe-Danger-Zone-Images: true header that the TypeSafe clients send for images is accepted, and Drex does not require it.

Raise the SDK timeout

The SDK's per-attempt timeout defaults to 10,000 ms. Drex waits up to 55 s for the model on a heavy request, so a large state with many questions can exceed the default and surface as an APITimeoutError. Set timeout: 60_000 on the client, or per call in the second argument to systemOne. The timeout applies per attempt, and the SDK retries APITimeoutError by default. Errors and retries covers retries in detail.

Handle 402 and 529

Drex adds 402 to TypeSafe's statuses. Handle it next to 529, which Drex returns when it cannot serve a request right now.

  • 402 with type insufficient_credit is new in Drex. It means the account has no spendable balance. The SDK raises this as a plain APIError with status 402. It is not retried. Top up or add a card on Billing. 402 with type payment_required means an unpaid invoice; it is not retried either, and the invoice is paid on Billing.
  • 529 with type overloaded means Drex is at capacity or the model did not respond in time. It carries retry-after and retry-after-ms. The SDK treats it as a 5xx, raises InternalServerError if retries run out, and honors the retry-after headers while retrying.

Errors lists every type with its status.

legend is an object for the TypeSafe SDK

By default Drex returns the score answer's legend as an array of level labels. The SDK types describe it as an object keyed by score, and the Python typesafe-sdk validates the response against that, so an array raises a validation error on answers.<name>.legend even though the request succeeded with 200. DSPy's TypeSafe client failed the same way for a Score output, and Opik and Weave wrap the Python client.

Drex now answers the TypeSafe SDK with an object keyed by level index as a string, {"0": "Calm", "1": "Annoyed", "2": "Angry"}, without any change on your side: it recognizes a TypeSafe client by an X-TypeSafe-SDK header with any non-empty value, or by a User-Agent that starts with typesafe-sdk/ or langchainjs-typesafe/. langchain-typesafe sends no X-TypeSafe-SDK, so its User-Agent is what identifies it (read from its source, not tried against Drex). Any other client keeps the array and can send X-Nace-Legend: object. Only the shape of legend changes. To choose the shape yourself, send X-Nace-Legend: object or X-Nace-Legend: array; the header wins over detection in both directions and any other value is ignored. See legend as an array or an object.

This applies to the TypeScript @typesafe-ai/sdk too, which sends the same header. Until now Drex returned it an array, so code that already reads legend with array methods (map, length, Array.isArray, spreading) will see an object once this is deployed. Index access such as legend[2] keeps working. To keep the array, send X-Nace-Legend: array.

What was checked: an object legend validates against the real typesafe-sdk 0.7.5 ScoreAnswer model, and a live Score call through that SDK and through DSPy's TypeSafe client against https://console.nace.ai returned a valid answer. A live call through Opik or Weave, which wrap the same Python client, has not been run.

What stays the same

  • The endpoints, POST /v1/systemone and GET /v1/models.
  • The request shape: state, questions, and optional model. Each question's type is noul, choice, or score; Questions shows the JSON for each, and the API reference has the full body.
  • The response shape: model, answers keyed by question name, and usage with input_tokens and output_tokens. Drex's body also carries evaluation_time_ms and request_id, which the SDK's SystemOneResult type does not declare.
  • The statuses 401, 422, 429, and 5xx, which the SDK maps to AuthenticationError, UnprocessableEntityError, RateLimitError, and InternalServerError.
  • The x-typesafe-request-id header. The SDK reads it into APIError.requestId and withResponse().requestId. Drex sends the same value as x-request-id.
  • The SDK's retry behavior: 2 retries by default, backoff from 500 ms doubling to 5 s, on 408, 429, and 500 to 599, honoring retry-after and retry-after-ms.
  • The noul, choice, and score helpers, and the answer types they infer. A score answer's legend reaches the TypeSafe SDK as an object, as it expects; see legend is an object for the TypeSafe SDK.
  • The multipart upload that the clients use for images, on the served Drex Max models and retired ids that resolve to Max v1.1.

Checklist

  1. Set TYPESAFE_BASE_URL, TYPESAFE_API_KEY, and TYPESAFE_DEFAULT_MODEL, or the matching constructor options.
  2. Remove hard-coded jev-* model names.
  3. Remove box questions.
  4. Make sure that every criteria description is a string, and that score levels contain no null.
  5. Raise timeout for large requests.
  6. Handle 402 and 529 where you handle other APIError cases.
  7. Send images to a served Drex Max model or a retired id that resolves to Max v1.1, as PNG or JPEG.
  8. Run client.models.list() and confirm that it includes drex-v2.0.
  9. If you ask score questions, nothing to change: Drex sends the TypeSafe SDK an object legend. Send X-Nace-Legend: array or object only to override that.

On this page