infer
infer is a function passed into onCheckpoint on CheckpointContext. It runs an inference request bound to the just-saved checkpoint adapter and returns the raw Response. There is no top-level infer export; the SDK exposes it as a callback argument so that the call is automatically scoped to the right job + checkpoint step.
Signature
Parameters
Tool calling example
Structured-output example
Choosing between responseFormat modes
responseFormat is OpenAI-compatible and is the right knob for ~all “give me JSON” cases. Reach for structuredOutputs only for constraints responseFormat cannot express.
strict: true requires additionalProperties: false on every object schema. OpenAI’s strict mode is satisfied only when each type: "object" schema (the root, plus every nested object) explicitly sets additionalProperties: false and lists every property in required. Schemas that omit it are rejected by the backend with a 400 invalid_schema. The triage example above (and the cookbook recipe) follow this rule; copy them when you write your own schema.
structuredOutputs examples
The vLLM-specific extension. Exactly one of json / regex / choice / grammar / json_object per call: the TypeScript type rejects two at once. Don’t combine with a responseFormat constraint either: that would put two constraints into vLLM’s sampling params and the request is rejected at ingress.
Fixed choice list. Forces the response to one of a small enumerated set. Useful for classifier-style outputs where any prefix is a regression.
json_object: true. The structuredOutputs equivalent of responseFormat: { type: "json_object" }, present for parity with vLLM’s wire format. Only true is accepted; false is rejected at compile time (the type literal) and at ingress (vLLM only flips into JSON-object mode on a truthy value, so false would silently produce an unconstrained generation). Follows the same one-constraint-per-call rule as the others.
Tool calling round-trip
After the model emitstool_calls, run the tool yourself, append the result as a tool message, and call infer again. Pass the same tools and toolChoice so the second turn sees the same surface.
tool_calls[i].function.arguments is a JSON-encoded string, not a parsed object. JSON.parse it on receipt. tool_call_id on the tool message must match the id from the assistant’s prior tool_calls[i] so the model can attribute the result to the right call.
Type definitions
The supporting types are exported fromarkor. Inlined here for reference:
{ role: "assistant" } with neither content nor tool_calls does not type-check; at least one must be present. The [ToolCall, ...ToolCall[]] form encodes the non-empty tool_calls constraint at the type level.
Returns
infer returns Promise<Response>: the raw Fetch Response. The SDK does not parse the body; you decide how to consume it:
stream: true (the default), the body is an SSE event stream in the same shape Studio’s Playground consumes. The SDK does not currently expose a frame parser for this stream; if you need decoded text deltas, copy the small extractInferenceDelta helper from packages/studio-app/src/lib/api.ts or write a parser around eventsource-parser.
Response envelope (stream: false)
Non-streaming responses are an OpenAI-compatible chat-completion object:
- No constraint or
responseFormat: { type: "text" }:choices[0].message.contentis plain text. responseFormat: { type: "json_object" }ortype: "json_schema":choices[0].message.contentis a string containing the JSON. You callJSON.parseyourself; the SDK does not pre-parse.structuredOutputs: { json }or{ json_object: true }: same;choices[0].message.contentis a JSON string.JSON.parseit.structuredOutputs: { choice }/{ regex }/{ grammar }:choices[0].message.contentis a string matching the constraint. Not JSON; do not parse.toolsrequest that returned a tool call:choices[0].message.tool_callsis populated;contentis omitted ornull. Eachtool_calls[i].function.argumentsis itself a JSON-encoded string.
finish_reason: "tool_calls" is the signal the model wants to call a function rather than emit a final answer; loop with the tool calling round-trip.
Errors
infer does not hand you a non-OK Response. The SDK calls into CloudApiClient.chat, which throws a CloudApiError whenever the backend returns a non-2xx status. By the time control returns from await infer(...), you’ve either got a successful Response or an exception. Wrap each call in try / catch (or use .catch()) and branch on err instanceof CloudApiError to read err.status and err.message. The class is exported from arkor for that purpose.
An inference error you don’t catch escapes
onCheckpoint, and a throw out of onCheckpoint rejects trainer.wait() immediately; it is not caught by the runtime’s reconnect loop, which retries transport failures only. For non-fatal inference errors, catch inside the callback so a recoverable failure doesn’t abort the whole run.
Constraints
inferlives only onCheckpointContext. There is no equivalent for completed jobs from the SDK side; for that path use the cloud-api directly or trigger the run again. Studio’s Playground is the UI-level route to chat with a completed adapter.- The call is scoped to
{ kind: "checkpoint", jobId, step }. You cannot retarget it to a different checkpoint or a different model from insideonCheckpoint. - The function is not memoized: every call hits the backend.
Use cases
- Sanity check during a run. Compare a checkpoint at step 50 to one at step 100 against a fixed prompt. If the loss curve looks fine but outputs are degraded, you find out before the run finishes.
- Custom early-stopping. Combine with a simple eval prompt: if outputs diverge, abort the run via
controller.abort()(seeabortSignal) and calltrainer.cancel()to stop the backend. See the Early stopping recipe for the full pattern. - Live preview into your own UI. Send the checkpoint output to Slack, an internal review queue, or your own app’s preview channel.
See also
onCheckpointfor the callback that hands youinfer- Mid-run eval recipe for an end-to-end pattern
- Early stopping recipe for tying
inferoutput toabortSignal+cancel() - Trainer control for
abortSignalandcancel()