Skip to content

prompts.resolve() ​

Resolves the version of a prompt deployed in your key's environment and binds its variables. Performs no inference.

The first call fetches every prompt deployed in that environment, in one request; after that they are kept in memory and variables are filled in your process, so most calls make no request and your values are never sent.

ts
client.prompts.resolve(
  promptId: string,
  options?: { vars?: Record<string, string>; signal?: AbortSignal },
): Promise<{ prompt: string; promptVersionId: string; version: string }>

Parameters ​

NameType
promptIdstringThe prompt's id — not its name. Any version's page in the app shows it beside the prompt's name, as Prompt ID
options.varsRecord<string, string>Values for the {{variables}} in the body
options.signalAbortSignalStops this call waiting on a fetch

Returns ​

FieldType
promptstringThe ready-to-run text, as the version's benchmark ran it — see below
promptVersionIdstringId of the exact version resolved — worth logging beside the model's output
versionstringLabel of the version deployed in the key's environment, e.g. "v7"

What prompt is ​

The text the version's benchmark gave its model, with your values:

  • A classic prompt has the values written where its {{placeholders}} are.
  • A BRAID graph comes with one sentence in front saying the flowchart is the procedure to follow, then the values listed in <value> blocks, then the flowchart exactly as drawn. The values are not written into its labels: a value carrying ] would close the label it landed in and change the graph.

Run it the way the benchmark did: prompt as the system message, your user's message as the user turn.

Which version you get ​

The one deployed in the key's environment. A key belongs to one environment of one project, and there is no way to ask it for another, so deploying a version is the single action that moves callers. A rollback is deploying the previous version again.

ts
const { prompt, version } = await client.prompts.resolve(INTENT_CLASSIFIER_ID, {
  vars,
});

Letting a caller name a version would let any key reach drafts, and would make production's approval policy advisory. A prompt with nothing deployed in the key's environment fails with 404.

Caching ​

Age of the cached environmentWhat resolve() does
Under 30 secondsAnswers from memory
30 seconds to 5 minutesAnswers from memory at once, and fetches a fresh copy behind the call
Over 5 minutesWaits for a fresh copy

A refresh sends the ETag of what it holds, so an unchanged environment answers 304 with no body. A deploy therefore reaches an active caller within about 30 seconds. A prompt missing from the cache triggers one refresh before 404, so a prompt deployed a moment ago is found without waiting. Change the window with cache: { ttlMs } when constructing the client, or drop the cache immediately — say, from your own deploy hook:

ts
client.prompts.invalidate();

Realtime ​

Pass realtime: true and the client keeps a stream open to hear about deploys the moment they happen, refreshing the cache at once — within a few seconds — instead of when its TTL runs out:

ts
const client = new EvalwrightClient({ apiKey, baseUrl, realtime: true });

process.on("SIGTERM", () => client.close());

It is off by default because an open stream keeps a Node process alive: a long-running server wants it, a script or a serverless function does not. Call close() on shutdown. The stream is only a signal — every change triggers the same ETag-checked fetch — so if it drops, or the server has no realtime configured, the cache TTL keeps working underneath.

If a refresh is refused (401, 403) the cache is dropped: a revoked key stops being served. Its realtime stream is closed when the key is revoked, and the client logs a warning and stops listening. If it fails for any other reason — the network, a 5xx, a timeout — the last copy keeps serving, a warning is logged, and the next attempt waits another 30 seconds.

A request that gets no answer within 10 seconds fails with a TimeoutError; set timeoutMs on the client to change that.

The cache lives in the process. A serverless function that starts cold starts with it empty.

Parallel calls ​

Concurrent calls share one fetch, so batches are just Promise.all:

ts
const resolved = await Promise.all(
  customers.map((c) =>
    client.prompts.resolve(WELCOME_EMAIL_ID, { vars: { name: c.name, plan: c.plan } }),
  ),
);

Cancellation ​

Aborting stops that call waiting. A fetch it started keeps going for any other caller, and fills the cache when it lands.

ts
const controller = new AbortController();
const promise = client.prompts.resolve(SLOW_PROMPT_ID, {
  vars: {},
  signal: controller.signal,
});
setTimeout(() => controller.abort(), 5_000);

Released under the MIT licence.