Structured output and reasoning
Request JSON schemas and provider reasoning through normalized completion fields.
Structured output
Use StructuredOutputFormat<T> to pair a JSON Schema with runtime parsing. The same contract works
with completions, Responses, and Messages:
import { type StructuredOutputFormat } from "any-llm-ts";
interface Location {
city: string;
}
const locationFormat: StructuredOutputFormat<Location> = {
name: "location",
jsonSchema: {
type: "object",
properties: { city: { type: "string" } },
required: ["city"],
additionalProperties: false,
},
parse(value) {
if (typeof value !== "object" || value === null || !("city" in value)) {
throw new TypeError("Invalid location output");
}
return { city: String(value.city) };
},
};
const response = await llm.completion({
model: "gpt-4.1-mini",
messages: [{ role: "user", content: "Extract the city: I live in Kolkata." }],
responseFormat: locationFormat,
});
console.log(response.choices[0]?.message.parsed?.city);Use responseFormat for completions and Responses, and outputFormat for Messages. Typed parsing
returns a complete JSON value, so it is non-streaming. Anthropic and Otari can still stream
schema-constrained Messages events when outputFormat is set; those streams are unparsed events
rather than ParsedMessageResponse. LengthFinishReasonError and
ContentFilterFinishReasonError preserve the partial normalized completion when parsing cannot
finish for those reasons.
Provider-compatible formats
You can also pass a provider-compatible response format through the common responseFormat field:
const response = await llm.completion({
model: "gpt-4.1-mini",
messages: [{ role: "user", content: "Extract the city from: I live in Kolkata." }],
responseFormat: {
type: "json_schema",
json_schema: {
name: "location",
strict: true,
schema: {
type: "object",
properties: { city: { type: "string" } },
required: ["city"],
additionalProperties: false,
},
},
},
});For Anthropic, responseFormat.type must be json_schema, and the schema must be available at
responseFormat.json_schema.schema. The adapter translates it to Anthropic's structured-output
configuration.
For Anthropic Claude models on Bedrock, the adapter emulates json_schema output by forcing a
private tool call and returning that tool's JSON input as normal assistant content. This works for
direct and cross-region model IDs containing anthropic.. It is non-streaming and cannot be
combined with enabled extended reasoning; omit reasoningEffort or use none or auto.
Gemini accepts json_schema, json_object, and text. For json_schema, the adapter sends the
schema through the native Google Gen AI SDK and requests the application/json response MIME type.
If Gemini reports truncation or content filtering before structured output is complete, the adapter
rejects with ContextLengthExceededError or ContentFilterError instead of returning partial JSON.
With this lower-level form, the library returns model output as text. Parse and validate it in your application with the schema library of your choice.
Reasoning effort
const response = await llm.completion({
model: "reasoning-model",
messages: [{ role: "user", content: "Solve this problem..." }],
reasoningEffort: "high",
});Supported normalized values are none, minimal, low, medium, high, xhigh, max, and
auto. Providers may support only a subset. Reasoning text, when exposed by the provider, is
normalized to message.reasoning or delta.reasoning.
Treat reasoning as optional
A provider or model may use reasoning internally without returning it. Always handle the
reasoning field as optional and avoid making application correctness depend on it.