Store Chat Messages & State Without Managing Infrastructure.Check Out DialogueDB
Skip to content

Error Handling ​

llm-exe uses LlmExeError for errors created by the library.

ts
import { LlmExeError, isLlmExeError } from "llm-exe";

Error Shape ​

LlmExeError exposes these fields:

ts
try {
  await executor.execute(input);
} catch (error) {
  if (isLlmExeError(error)) {
    console.log(error.code);
    console.log(error.category);
    console.log(error.context);
    console.log(error.cause);
  }
}
FieldDescription
codeMachine-readable error code, such as parser.parse_failed
categoryThe prefix of the code, such as parser, prompt, or llm
contextExtra metadata about the failure
causeOriginal error, when available

Branching on Codes ​

Use isLlmExeError(error, code) to check error codes.

ts
try {
  const result = parser.parse(output);
} catch (error) {
  if (isLlmExeError(error, "parser.schema_validation_failed")) {
    // The output parsed, but did not match the schema.
  }

  if (isLlmExeError(error, "parser.parse_failed")) {
    // The output could not be parsed.
  }
}

You can also pass a list of codes.

ts
if (
  isLlmExeError(error, [
    "llm.provider_rate_limited",
    "llm.provider_unavailable",
  ])
) {
  // Retry, queue, or fall back.
}

Common Categories ​

Known error categories include:

  • configuration
  • parser
  • prompt
  • llm
  • embedding
  • executor
  • callable
  • state
  • request
  • auth
  • template
  • internal

Retry Behavior ​

numOfAttempts does not apply to every failure. Errors in the configuration, prompt, and auth categories are deterministic client-side failures — a bad option, an invalid prompt, or a missing signing input. Retrying cannot change the outcome, so they are thrown on the first attempt instead of burning attempts and backoff delay on a guaranteed re-failure.

Everything else keeps the normal retry behavior, including provider errors such as llm.provider_rate_limited and llm.provider_unavailable (category llm, since the category is the prefix of the code), request errors other than request.timeout, and any error that is not an LlmExeError.

Deterministic codes inside the otherwise-mixed llm and embedding categories are also not retried, via a code-level list beside the category short-circuit: llm.provider_auth_failed, llm.provider_invalid_request, embedding.provider_auth_failed, embedding.provider_invalid_request, embedding.unsupported_input, and embedding.unsupported_dimensions. A rejected credential or a request the provider rejects as invalid re-fails identically on retry, so it surfaces on the first attempt; the transient codes in those categories (provider_rate_limited, provider_unavailable, provider_http_error) still retry.

See Add Retries and Timeouts to LLM Calls for configuring timeout, numOfAttempts, and maxDelay.

Client-side timeouts throw request.timeout with context.timeout in milliseconds and are not retried, including for embeddings. The underlying request may still be running and billed by the provider; timing out does not cancel generation.

Provider Errors ​

HTTP failures from LLM providers use typed llm-exe errors:

CodeTypical cause
llm.provider_rate_limitedProvider returned a rate-limit status
llm.provider_auth_failedMissing, invalid, or unauthorized credentials
llm.provider_invalid_requestProvider rejected the request body or model/options
llm.provider_unavailableTimeout, unavailable provider, or server-side failure
llm.provider_http_errorOther provider HTTP error

Embedding providers use the same pattern with the embedding. prefix.

Provider error context may include:

  • status
  • statusText
  • url
  • providerError
  • providerErrorBody
  • responseHeaders

Secrets are redacted before provider error data is added to context.

Serialization ​

LlmExeError instances implement toJSON(), which returns a structured, JSON-safe payload for logs, queues, or telemetry.

ts
if (isLlmExeError(error)) {
  const payload = error.toJSON();
  logger.error(payload);
}

The payload includes name, message, code, category, context, and a serialized cause when one is set. Secrets in provider error context are redacted before serialization.

serializeLlmExeError ​

error.toJSON() only works when the value you caught is an LlmExeError. In a catch block you rarely know the type ahead of time — the error could be a plain Error, a rejected fetch Response, or something a dependency threw. serializeLlmExeError(error, options?) accepts any value and always returns a JSON-safe payload, so you can log it without a type guard.

ts
import { serializeLlmExeError } from "llm-exe";

try {
  await executor.execute(input);
} catch (error) {
  logger.error(serializeLlmExeError(error));
}
  • Serializes LlmExeError, native Error, Response, and arbitrary objects.
  • Walks the cause chain (up to 5 levels; deeper causes are marked { truncated: true }).
  • Guards against circular references (rendered as "[Circular]") and normalizes values that are not JSON-safe (BigInt, Date, Map, functions, etc.).
  • Secrets in provider error context are redacted.
OptionTypeDefaultDescription
includeStackbooleanfalseInclude the error's stack string in the payload.

formatLlmExeErrorForLog ​

When you want a compact, human-readable single string instead of a structured object — for a plain-text log line, for example — use formatLlmExeErrorForLog(error). It also accepts any value and never throws.

ts
import { formatLlmExeErrorForLog } from "llm-exe";

try {
  await executor.execute(input);
} catch (error) {
  console.error(formatLlmExeErrorForLog(error));
  // LlmExeError [parser.schema_validation_failed]: requires property "name"
  // Caused by: Error: Unexpected token
}

The header is formatted as Name [code]: message (falling back to the category, then just the name when neither is present), followed by one Caused by: line per level of the cause chain (up to 5 levels).