Skip to content

JavaScript

Install the mellix package from npm and call the Decision API, batches and images from Node.

The mellix package on npm is the JavaScript and TypeScript client for the Decision API. It is ESM, includes TypeScript types, has no runtime dependencies and runs on Node 18 or later (it uses the global fetch). It calls the Myles-G models over HTTP; it does not run the CPU models (see Model types).

bash
npm install mellix

This page describes version 0.5.0, the first with batch support. There is also a Python SDK with the same calls.

To let an agent call Myles as a tool rather than from your own code, use the MCP server, which is built on this package.

Keep your API key on a server. The client refuses to start in a browser, because a key in a web page or app is public.

Authentication

The client reads your key from the MELLIX_API_TOKEN environment variable, or you can pass it as apiKey. Get a key in the console.

bash
export MELLIX_API_TOKEN=mellix_...

Ask a question

javascript
import { Mellix, noul } from "mellix";
 
const client = new Mellix(); // reads MELLIX_API_TOKEN
 
const resp = await client.decide(
  "Customer bought the item 12 days ago and has no receipt. Policy: refunds within 30 days with proof of purchase.",
  { refund: noul("Is a refund permitted under the policy?") },
  "myles-g-12b",
);
 
const answer = resp.answers.refund;
console.log(answer.probability);                                    // probability of yes, e.g. 0.04
console.log(answer.decide({ thresholdYes: 0.9, thresholdNo: 0.1 })); // "yes", "no" or "person"
console.log(resp.usage.input_tokens, resp.model);

decide(state, questions, model) returns { model, answers, usage, raw }. Always name the model, "myles-g-12b" or "myles-g-31b": every request should say which model answers it. A request without one is still answered by a default model for now, but do not rely on it. The state can be a string or an object. Answers are typed from the questions you ask, so resp.answers.refund above is a yes-or-no answer.

Question types

javascript
import { noul, choice, score } from "mellix";
 
const resp = await client.decide(state, {
  refund: noul("Is a refund permitted?", { true: "Policy clearly allows it", false: "Policy clearly forbids it" }),
  topic: choice("What is this about?", ["billing", "returns", "other"]), // or { label: description }
  urgency: score("How urgent is it?", ["not urgent", "low", "medium", "high"]), // lowest level first, numbered from 0
}, "myles-g-12b");
 
resp.answers.topic.choice;        // "returns"
resp.answers.topic.probabilities; // { billing: 0.06, returns: 0.89, ... }
resp.answers.urgency.level;       // 2
resp.answers.urgency.score;       // 1.92

The helpers check your questions before anything is sent and throw ValidationError if a request would be refused: at most 256 questions a request, at most 256 options a choice and 4,096 over a request, and instructions that are not empty (from version 0.6.0; version 0.5.0 checked 16 questions and 26 options). See Question types for what each type returns.

Images

From version 0.4.0, the third argument of decide can be options, { model, assets, imageBudget }, as well as a model name. Pass images in assets under a reference and write the reference where the model should see the image (name the model: model: "myles-g-12b" or model: "myles-g-31b"):

javascript
import { Mellix, imageFromPath, noul } from "mellix";
 
const resp = await client.decide(
  { receipt: "receipt_photo" },
  { total_matches: noul("Does the total printed on receipt_photo equal 23.40?") },
  { model: "myles-g-12b", assets: { receipt_photo: await imageFromPath("receipt.jpg") }, imageBudget: 140 }, // budget: 70, 140 or 280
);

imageFromPath and imageFromBytes detect PNG or JPEG from the bytes and throw ValidationError for anything else or anything over 16 MiB; decide throws it for an imageBudget other than 70, 140 or 280. The server's image errors arrive as InvalidRequestError with a type such as image_dimensions. See Images for references, limits and billing.

Batches

From version 0.5.0, the client sends work to the Batch API: many decisions in one submission, answered within 24 hours at half the live price per input token. Each line is a decide body with your own custom_id and the model to answer it, which every line must name. Field names are as they are sent: custom_id and image_budget.

javascript
import { Mellix, noul, choice } from "mellix";
 
const client = new Mellix();
 
const batch = await client.batch([
  { custom_id: "ticket-1001", model: "myles-g-12b", state: "Charged twice for order 4471", questions: { refund: noul("Is a refund due?") } },
  { custom_id: "ticket-1002", model: "myles-g-31b", state: { message: "The app crashes on login" },
    questions: { team: choice("Which team?", ["billing", "returns", "technical", "other"]) } },
]); // { id: "batch_...", status: "queued", lines: 2, estimated_cost, expires_at, data_region }
 
await client.batches.waitFor(batch.id); // checks after 5 s, then 1.5 times longer each time, up to a minute apart
 
for await (const r of client.batches.streamResults(batch.id)) {
  if (r.ok) console.log(r.custom_id, r.response.answers); // typed as decide() answers
  else console.log(r.custom_id, r.error.type);            // for example "expired" or "cancelled"
}
CallWhat it does
batch(lines, { webhookUrl })Checks every line, sends them as one file and returns the queued batch. The same as batches.create().
batches.list()The key's batches, oldest first.
batches.retrieve(id)Status, counts and times.
batches.waitFor(id, options)Waits until the results can be collected. Options: pollInterval (5000 ms), maxInterval (60000 ms), both at least 1000 ms, timeout (25 hours, then BatchTimeoutError), signal to stop waiting and onPoll. It keeps waiting through a brief outage. Giving up never cancels the batch.
batches.streamResults(id)Each result as it arrives, in input order, so a large file is never held in memory.
batches.results(id)Every result in one array.
batches.cancel(id)Stops the batch. Answered lines are kept and billed; the rest are not.

A result is { custom_id, model, status, ok, response, error, usage, raw }, where response is set when the line was answered and error when it was not. The client checks the lines before sending and throws ValidationError naming the line. If the server finds an invalid line it refuses the whole batch with an InvalidRequestError whose type is "invalid_batch", with each line at fault in body.errors. A submission is retried only after a refusal that stored nothing (429 rate_limited or 503 busy), never after a lost connection or a 502, so a retry cannot submit the same batch twice. A batch belongs to the key that submitted it: collect its results with that key.

Other calls

javascript
await client.models(); // [{ id: "myles-g-12b", object: "model", owned_by: "mellix" }]
await client.health(); // { status: "ok" }

models() and health() need no key.

Options

OptionWhat it does
apiKeyYour API key. Defaults to MELLIX_API_TOKEN.
baseURLSet it for an endpoint you host yourself. Defaults to the hosted API.
timeoutMilliseconds allowed for each attempt. Default 30000.
maxRetriesRetries after the first attempt, for 429, 502, 503 and connection errors. Default 3. A batch submission is the exception: it is retried only after 429 rate_limited or 503 busy, never after a 502 or a lost connection, so it is never sent twice.
javascript
const client = new Mellix({ apiKey: "mellix_...", timeout: 10_000, maxRetries: 5 });

Retries wait 0.5, 1 and 2 seconds, or as long as a Retry-After header asks, up to 60 seconds. Other errors are thrown straight away.

Errors

Every error extends MellixError and carries status, serverMessage, body and type, the server's error type such as "out_of_credit".

ErrorWhen
AuthenticationError401, or no key given or found
OutOfCreditError402: the organisation has run out of credit (type "out_of_credit"), or the key's workspace has reached its monthly budget ("budget_exceeded")
PermissionDeniedError403: the key's workspace may not use this model ("model_not_allowed")
RateLimitError429, with retryAfter in seconds when the server sends it
InvalidRequestError400, 411, 413, 422 and other 4xx; ValidationError is the check made before sending
NotFoundError, ConflictError404 and 409, both kinds of InvalidRequestError: for a batch, an id the key does not own ("batch_not_found"), results asked for before the batch has ended ("batch_not_finished") or cancelling a batch that has already ended ("batch_finished")
BatchTimeoutErrorbatches.waitFor() ran out of time; batch is the last status read, and the batch keeps running
APIError5xx
APIConnectionErrorThe endpoint could not be reached, including timeouts
javascript
import { OutOfCreditError, RateLimitError, InvalidRequestError, MellixError } from "mellix";
 
try {
  await client.decide(state, questions, "myles-g-12b");
} catch (e) {
  if (e instanceof OutOfCreditError) console.log(e.type === "budget_exceeded" ? "workspace budget reached" : "top up credit");
  else if (e instanceof RateLimitError) console.log("slow down", e.retryAfter);
  else if (e instanceof InvalidRequestError) console.log(e.status, e.serverMessage);
  else if (e instanceof MellixError) console.log("failed:", e.message);
  else throw e;
}

To update an existing install, run npm install mellix@latest. Batch support needs version 0.5.0 or later; images need 0.4.0 or later.

Python

The Python SDK (pip install mellix) has the same calls, with Python names: client.batch(lines), batches.wait_for(id), batches.stream_results(id) and so on.