NodaroAccount
Nodaro Docs
DocumentationNode ReferenceModelsTemplatesAI Agents (MCP)DevelopersSelf-hostingResearchCompare
TypeScript SDK

LLM and Reduce

Get validated JSON from a language model with client.llm, and choose the best of many results, vote, join or merge them with client.reduce.

client.llm asks a language model for structured output: you send a system prompt, an input and a JSON Schema, and you get back an object that matches the schema. client.reduce runs the Reduce fan-in step on its own: it picks the best of many results, counts them, votes, joins text or merges JSON, the job the Choose Best node does in a workflow. A language-model call in either one is charged for the tokens it uses, at the model's rate. See How LLM calls are charged.

Methods

MethodWhat it does
llm.structured(input)Get a validated object from a language model, in one request
llm.structuredJob(input)The same call as a job you poll
reduce.run(input)Pick, count, vote, join or merge many inputs into one

client.llm

Structured output: your system prompt and JSON Schema in, a validated object out. The platform chooses the model lane, forces JSON output, validates it against your schema, and feeds invalid answers back to the model before it gives up. It is billed as llm-structured, for the tokens the call used, at the model's rate on the lane that served it. A hold is reserved first, and a balance below it throws InsufficientCreditsError. The call is then charged what it used. A failure the provider billed is charged for that usage. Claude Fable 5 is offered. To quote a typical call, pass llmPriceKey("llm-structured", model) to credits.modelCosts().

llm.structured(input)

Asks the model and waits for the answer (POST /v1/llm/structured). A call can take several minutes, which is longer than the client's default 60-second timeout. Create the client with a larger timeoutMs for it, or use structuredJob().

structured<T>(input: LlmStructuredInput): Promise<{
  jobId: string
  output: T
  usage: { inputTokens: number; outputTokens: number }
}>

Prop

Type

type Plan = { title: string; scenes: string[] }

const { output } = await client.llm.structured<Plan>({
  system: "You write production plans for short films.",
  input: "A rainy chase through Rome, 60 seconds.",
  jsonSchema: {
    type: "object",
    properties: {
      title: { type: "string" },
      scenes: { type: "array", items: { type: "string" } },
    },
    required: ["title", "scenes"],
  },
  schemaName: "production_plan",
})
console.log(output.title, output.scenes.length)

llm.structuredJob(input)

The same call as a job (POST /v1/llm/structured/jobs). It returns a jobId at once; poll it with client.jobs.getStatus(). A job can also draft from a video: the platform analyzes the video first, then adds the analysis to your input.

structuredJob(input: LlmStructuredJobInput): Promise<{ jobId: string }>

Prop

Type

const { jobId } = await client.llm.structuredJob({
  system: "You write production plans.",
  input: "A rainy chase through Rome.",
  jsonSchema: { type: "object", properties: { title: { type: "string" } }, required: ["title"] },
  origin: "my-app",
  label: "Rome chase",
})

// later, even from another session
const { data } = await client.jobs.getStatus(jobId)
if (data.status === "completed") {
  console.log((data.output_data as { output: { title: string } }).output.title)
}
const { data: runs } = await client.jobs.list({ type: "llm-structured", origin: "my-app" })

While the job runs, its output_data holds stage: analyzing (for video drafts) or drafting. When it completes, output_data holds output, inputTokens and outputTokens, plus analysisJobId and analysisCredits for a video draft.

  • analysisJobId fails with a 422 when the job is not yours, does not exist, or is not a finished video analysis. The codes are analysis_not_found, not_analysis, analysis_failed, analysis_not_ready and invalid_analysis.
  • A platform without this route throws NotFoundError.
  • A self-hosted instance that sends its language-model calls to Nodaro Cloud answers 503 provider_unavailable. Treat it as unavailable on that instance, not as a passing error.

client.reduce

reduce.run(input)

Reduces many inputs to one. It mirrors the MCP reduce tool and the Choose Best node.

run(input: ReduceInput): Promise<ReduceResult>

Prop

Type

StrategystrategyConfigWhat it returns
pick-best-llm{ criteria, inputKind?, llmModel? }. inputKind is "text" or "image-url". llmModel chooses the judge. The judge's call is charged per token at that model's rate.The input a language model judges best, with its index and reasoning
concat{ separator? }, a blank line by defaultEvery input joined into one text
first-non-emptynoneThe first input that is not empty
countnoneThe number of inputs
vote{ caseSensitive? }, false by defaultThe most frequent input. A tie goes to the first.
merge-json{ strategy? }: "deep" (the default) or "shallow"The JSON inputs merged into one object
const result = await client.reduce.run({
  strategyId: "pick-best-llm",
  strategyConfig: { criteria: "The sharpest image with no artifacts", inputKind: "image-url" },
  inputs: [url1, url2, url3, url4, url5],
})
console.log(result.output)             // the chosen URL
console.log(result.meta.selectedIndex) // 0 to 4
console.log(result.meta.reasoning)     // why the model chose it

The result is { jobId, output, meta }. output is the chosen or combined value, as a string. meta.summary is always set. pick-best-llm and vote set meta.selectedIndex, and pick-best-llm also sets meta.reasoning.

// Majority vote
const winner = await client.reduce.run({ strategyId: "vote", inputs: ["red", "blue", "red"] })

// Deep-merge JSON fragments
const merged = await client.reduce.run({
  strategyId: "merge-json",
  inputs: [JSON.stringify({ a: 1, nested: { x: 1 } }), JSON.stringify({ b: 2, nested: { y: 2 } })],
})
JSON.parse(merged.output) // { a: 1, b: 2, nested: { x: 1, y: 2 } }

When every input is empty or only whitespace, the call fails with a NodaroError whose status is 400 and code is no_valid_inputs. Credits are reserved like every generation, so a short balance throws InsufficientCreditsError.

Frequently asked questions

How do I get JSON that matches my schema from a language model?

Call client.llm.structured with a system prompt, an input and a JSON Schema object. The platform forces the model to answer in that shape, validates it, retries invalid answers, and returns the parsed object.

When should I use structuredJob instead of structured?

Use structuredJob for long calls. structured waits for the answer in one request, which can take minutes, longer than the default 60-second timeout. structuredJob returns a jobId at once.

How do I pick the best of several generated images?

Call client.reduce.run with strategyId pick-best-llm, your criteria and inputKind image-url, and pass the image URLs as inputs. The result names the chosen URL, its index and the model's reasoning.

How much does a structured LLM call cost?

It is charged for the tokens it actually used, at the model's rate on the lane that served it. A hold is reserved first, and the call is then charged what it used. client.credits.modelCosts with the key llm-structured:<model> returns an estimate of a typical call.

Which Reduce strategies need no language model?

concat, first-non-empty, count, vote and merge-json run without a model. Only pick-best-llm asks a language model to judge.

Last updated on

On this page