Scoring
Score function runs and steps to track how well your workflows perform over time.
Scoring is in beta. Install the latest SDK with npm install inngest@latest.
inngest.score(options)
Score a run or step. Can be called inside or outside a function.
- Name
name- Type
- string
- Required
- required
- Description
A label for the score. Use consistent names across runs for aggregation. Examples:
"accuracy","user-feedback","guardrail-pass".
- Name
value- Type
- number | boolean
- Required
- required
- Description
The score value. A finite number or a boolean. Use
0-1for percentages, integers for counts, booleans for pass/fail guardrails, or any range that fits your use case.
- Name
runId- Type
- string
- Required
- optional
- Description
The run to score. Required when scoring from outside a function. Omit when scoring the current run from inside a function.
- Name
stepId- Type
- string
- Required
- optional
- Description
The step to score. Optional. When provided, the score attaches to that specific step in the trace view.
// Score the current run (inside a function)
await inngest.score({ name: "accuracy", value: 0.9 });
// Score a specific run (from anywhere)
await inngest.score({ name: "accuracy", value: 0.9, runId: "01ABC..." });
// Score a specific step
await inngest.score({
name: "accuracy",
value: 0.9,
runId: "01ABC...",
stepId: "generate-summary",
});
inngest.score.experiment(options)
Attribute a score to the experiment variant that produced a result. group.experiment() returns an experimentRef, which identifies the experiment and the variant that was served. Pass it here to credit the score to that variant.
The signal you want to score often arrives much later, from somewhere else entirely: a click, a rating, an LLM-judge verdict. Because you pass the experimentRef yourself, you can credit any of these back to the variant that produced the output, even hours later from a separate run.
- Name
name- Type
- string
- Required
- required
- Description
Score label.
- Name
value- Type
- number | boolean
- Required
- required
- Description
Score value. A finite number or a boolean.
- Name
experiment- Type
- ExperimentRef
- Required
- required
- Description
The
experimentRefreturned bygroup.experiment(). Identifies the experiment and the served variant. Shape:{ experimentName: string, variant: string }.
- Name
runId- Type
- string
- Required
- optional
- Description
The run to attach the score to. Required when scoring from outside the run that produced the result; omit to use the current run.
Scoring an experiment from a different run than the one that served the variant? Pass that run's runId so the score shows up in the experiment view — otherwise it attaches to the run you're scoring from and won't appear under the experiment.
// Inside the function that runs the experiment
const { result, experimentRef } = await group.experiment("summary-strategy", {
variants: { /* ... */ },
select: experiment.weighted({ questionLed: 50, factLed: 50 }),
});
// Persist experimentRef + runId (e.g. on your record) so later runs can score it
// From a separate, later run (a click, rating, or deferred judge)
await inngest.score.experiment({
name: "clickthrough",
value: 1,
experiment: experimentRef, // restored from your store
runId: summarizeRunId,
});
step.score(id, options)
Score the current run from within a step context, as a durable, memoized step. runId is automatically set to the current run. Requires scoreMiddleware() (from inngest/experimental) on the client.
- Name
id- Type
- string
- Required
- required
- Description
A durable step ID used to memoize this score write, so a replay or retry can't write it twice. Must be unique within the run.
- Name
options- Type
- object
- Required
- required
- Description
- Properties
- Name
name- Type
- string
- Required
- required
- Description
Score label.
- Name
value- Type
- number | boolean
- Required
- required
- Description
Score value. A finite number or a boolean.
await step.score("score-guardrail-pass", { name: "guardrail-pass", value: true });
createScorer(client, config, handler)
Create a reusable deferred scorer: a separate function, triggered via defer(), that evaluates a result without blocking or slowing the run that produced it. Use it when scoring is expensive or slow, such as an LLM-as-a-judge call or waiting on a signal that arrives later.
Imported from inngest/experimental.
- Name
client- Type
- Inngest
- Required
- required
- Description
Your Inngest client instance.
- Name
config- Type
- object
- Required
- required
- Description
- Properties
- Name
id- Type
- string
- Required
- required
- Description
Unique identifier for the scorer function.
- Name
schema- Type
- StandardSchema
- Required
- optional
- Description
A Standard Schema (e.g. Zod) describing the input data the scorer expects. Optional; when set, the
datapassed todefer()is validated against it and the handler'sevent.datais typed from it.
- Name
handler- Type
- function
- Required
- required
- Description
Async function that receives
event,step, andparents. Return{ name, value }and the score is written for you, attributed to the parent run, and to the served variant when the scorer was deferred with anexperimentref. Returnnullorundefinedto write nothing.
The handler's event.data is the payload sent via defer(), typed from schema. The simplest scorer returns one score and lets the SDK attribute it:
import { createScorer } from "inngest/experimental";
import { z } from "zod";
export const verbosityScorer = createScorer(
inngest,
{ id: "verbosity-scorer", schema: z.object({ text: z.string() }) },
async ({ event }) => {
const wordCount = event.data.text.split(" ").length;
return { name: "verbosity", value: wordCount };
}
);
To emit several scores, or to attribute explicitly, write them yourself with inngest.score.experiment(). The parent run and its served variant are on ctx.parents[0], so there's no need to persist them:
export const judgeScorer = createScorer(
inngest,
{ id: "judge-summary", schema: z.object({ summary: z.string() }) },
async ({ event, step, parents }) => {
const judged = await step.run("judge", () => judge(event.data.summary));
const { experiment, runId } = parents[0];
await inngest.score.experiment({ name: "faithfulness", value: judged.faithfulness, experiment, runId });
await inngest.score.experiment({ name: "spoiler", value: judged.spoiler, experiment, runId });
return null; // scores already written above
}
);
defer(id, options)
Trigger a deferred scorer from inside a function. Available as a handler argument.
- Name
id- Type
- string
- Required
- required
- Description
A deterministic identifier for this deferred call.
- Name
options- Type
- object
- Required
- required
- Description
- Properties
- Name
function- Type
- InngestFunction
- Required
- required
- Description
The scorer function to trigger.
- Name
data- Type
- object
- Required
- required
- Description
Input data matching the scorer's schema.
- Name
experiment- Type
- ExperimentRef
- Required
- optional
- Description
The
experimentReffromgroup.experiment(). Pass it to attribute the scorer's result to the served variant; surfaced on the scorer'sctx.parents[0].experiment.
defer() is fire-and-forget: it returns void, so there's nothing to await.
defer("score-feedback", {
function: feedbackScorer,
data: { ticketId: "tk_123" },
experiment: experimentRef, // optional; credits the score to the served variant
});