typia.llm.structuredOutput β schema + parse + coerce + validate, in one shot
When you ask an LLM for structured output (one specific JSON shape, no function selection involved), you typically need four things:
- The schema to put in
response_format/json_schema - A parser that copes with messy LLM output
- A coercer for SDKs that already JSON-parsed
- A validator to confirm the result actually matches your type
typia.llm.structuredOutput<T>() returns all four bundled together for a single TypeScript type T.
export namespace llm {
function structuredOutput<
T extends Record<string, any>,
Config extends Partial<ILlmSchema.IConfig & { equals: boolean }> = {},
>(): ILlmStructuredOutput<T>;
}interface ILlmStructuredOutput<T> {
parameters: ILlmSchema.IParameters; // hand this to the LLM
parse: (input: string) => IJsonParseResult<T>; // raw string in
coerce: (input: unknown) => T; // pre-parsed object in
validate: (input: unknown) => IValidation<T>; // check constraints
}First example
import typia from "typia";
interface IMember {
email: string;
name: string;
age: number;
hobbies: string[];
}
const output = typia.llm.structuredOutput<IMember>();
// hand `output.parameters` to your LLM SDK as JSON schema
// then:
const parsed = output.parse(rawLlmText);
if (parsed.success) {
const v = output.validate(parsed.data);
if (v.success) doSomething(v.data);
}undefined
export namespace llm {
export function structuredOutput<
T extends Record<string, any>,
Config extends Partial<ILlmSchema.IConfig & { equals: boolean }> = {},
>(): ILlmStructuredOutput<T>;
}undefined
import { IJsonParseResult } from "./IJsonParseResult";
import { ILlmSchema } from "./ILlmSchema";
import { IValidation } from "./IValidation";
/**
* LLM structured output schema with parsing and validation utilities.
*
* `ILlmStructuredOutput<T>` is generated by `typia.llm.structuredOutput<T>()`
* to provide everything needed for handling LLM structured outputs: the JSON
* schema for prompting, and functions for parsing, coercing, and validating
* responses.
*
* Structured outputs allow LLMs to generate data conforming to a predefined
* schema instead of free-form text. This is useful for:
*
* - Extracting structured data from conversations
* - Generating typed responses for downstream processing
* - Ensuring consistent output formats across LLM calls
*
* Workflow:
*
* 1. Pass {@link parameters} schema to LLM provider
* 2. Receive LLM response (JSON string or pre-parsed object)
* 3. Use {@link parse} for raw strings or {@link coerce} for pre-parsed objects
* 4. Use {@link validate} to check the result and get detailed errors
*
* @author Jeongho Nam - https://github.com/samchon
*
* @template T The expected output type
*
* @evidence contracts/common.md#principled-implementation The record holds the schema to send to a provider and the parse, coerce and validate functions, typed by T for the results, with parse and coerce going through the same schema and validate checking the final value; this gives the full path from prompt to checked output.
* @evidence contracts/common.md#clear-and-simple-design Four members, one per step of the documented workflow.
* @evidence contracts/common.md#prohibited-implementation-shortcuts The member functions are produced by the transform and are not patched here.
* @evidence contracts/common.md#meaningful-documentation The comment lists use cases and the four-step workflow, and each member explains its behavior.
*/
export interface ILlmStructuredOutput<T = unknown> {
/**
* JSON schema for the structured output.
*
* Pass this schema to LLM providers (OpenAI, Anthropic, Google, etc.) through
* their structured output or response format configuration to constrain the
* output format. The schema includes `$defs` for shared type definitions and
* `properties` for the output structure.
*/
parameters: ILlmSchema.IParameters;
/**
* Lenient JSON parser with schema-based type coercion.
*
* Handles incomplete or malformed JSON commonly produced by LLMs:
*
* - Unclosed brackets, strings, trailing commas
* - JavaScript-style comments (`//` and multi-line)
* - Unquoted object keys, incomplete keywords (`tru`, `fal`, `nul`)
* - Markdown code block extraction, junk prefix skipping
*
* Also coerces double-stringified values based on the schema:
*
* - `"42"` β `42` (when schema expects number)
* - `"true"` β `true` (when schema expects boolean)
* - `"null"` β `null` (when schema expects null)
* - `"{...}"` β `{...}` (when schema expects object)
* - `"[...]"` β `[...]` (when schema expects array)
*
* Type validation is NOT performed β use {@link validate} after parsing.
*
* If the SDK (e.g., LangChain, Vercel AI, MCP) already parses JSON internally
* and provides a pre-parsed object, use {@link coerce} instead.
*
* @param input Raw JSON string from LLM output
*
* @returns Parse result with data on success, or partial data with errors
*
* @evidence contracts/common.md#principled-implementation Takes a raw string and returns a parse result typed by T, which separates success from recovered partial data; it coerces by schema without validating.
* @evidence contracts/common.md#clear-and-simple-design A function-valued property with a single argument.
* @evidence contracts/common.md#prohibited-implementation-shortcuts It states that validation is not performed.
* @evidence contracts/common.md#meaningful-documentation The comment lists the recoverable malformed JSON forms and the coercion examples.
*/
parse: (input: string) => IJsonParseResult<T>;
/**
* Coerce pre-parsed output to match expected schema types.
*
* **Use this only when the SDK already parses JSON internally.** For raw JSON
* strings from LLM output, use {@link parse} instead β it handles both lenient
* parsing and type coercion in one step.
*
* LLMs often return values with incorrect types even after parsing:
*
* - `"42"` β `42` (when schema expects number)
* - `"true"` β `true` (when schema expects boolean)
* - `"null"` β `null` (when schema expects null)
* - `"{...}"` β `{...}` (when schema expects object)
* - `"[...]"` β `[...]` (when schema expects array)
*
* This function recursively coerces these double-stringified values based on
* the {@link parameters} schema.
*
* Type validation is NOT performed β use {@link validate} after coercion.
*
* @param input Pre-parsed output object from SDK
*
* @returns Coerced output under the unchecked static interpretation T
*
* @evidence contracts/common.md#principled-implementation The input is unknown and the declared result is T, but coercion is not validation and may leave malformed values unchanged. T is an unchecked static interpretation; the validate member owns the runtime check before callers rely on that type.
* @evidence contracts/common.md#clear-and-simple-design A function-valued property with a single argument.
* @evidence contracts/common.md#prohibited-implementation-shortcuts It is documented as a coercion and not a validation.
* @evidence contracts/common.md#meaningful-documentation The comment warns to use it only for pre-parsed input and gives coercion examples.
*/
coerce: (input: unknown) => T;
/**
* Validates LLM-generated output against the schema.
*
* LLMs frequently make type errors such as returning strings instead of
* numbers or missing required properties. Use this validator to check output
* before further processing.
*
* When validation fails, use {@link LlmJson.stringify} from `@typia/utils` to
* format the error for LLM feedback. The formatted output shows the invalid
* JSON with inline error comments, helping the LLM understand and correct its
* mistakes in the next turn.
*
* @param input The output generated by the LLM
*
* @returns Validation result with success status and any errors
*
* @evidence contracts/common.md#principled-implementation Takes unknown and returns IValidation of T, which is the type-safe check on the coerced result; failure has structured errors.
* @evidence contracts/common.md#clear-and-simple-design A function-valued property with a single argument.
* @evidence contracts/common.md#prohibited-implementation-shortcuts It reports errors instead of throwing.
* @evidence contracts/common.md#meaningful-documentation The comment explains the feedback use and links the error formatter.
*/
validate: (input: unknown) => IValidation<T>;
}undefined
import { tags } from "../index";
import { IJsonSchemaAttribute } from "./IJsonSchemaAttribute";
/**
* Type schema for LLM function calling.
*
* `ILlmSchema` is a JSON Schema subset designed for LLM function calling
* compatibility. The subset omits tuples and `const`; unions use `anyOf`.
*
* Generated by `typia.llm.schema<T>()` for single types or included in
* {@link ILlmApplication} via `typia.llm.application<Class>()`. Shared type
* definitions use `$defs` with `$ref` references to reduce duplication and
* handle recursive structures.
*
* Set {@link ILlmSchema.IConfig.strict} to `true` for OpenAI's structured output
* mode: optional and dynamic object properties are rejected. Accepted object
* schemas have every property required and `additionalProperties: false`.
*
* @author Jeongho Nam - https://github.com/samchon
*
* @evidence contracts/common.md#principled-implementation Ten variants distinguish typed values, references, anyOf unions and unconstrained values. Tuples and const have no variant. Strict conversion rejects optional and dynamic properties rather than changing which source values are required.
* @evidence contracts/common.md#clear-and-simple-design One alias over the variants in the namespace, each extending the JSON schema attribute family.
* @evidence contracts/common.md#prohibited-implementation-shortcuts It narrows the schema space to what providers accept and does not carry provider-specific escape hatches.
* @evidence contracts/common.md#meaningful-documentation The comment states what is omitted, how definitions are shared with `$defs` and what strict mode changes, and links the guide.
*
* @see https://typia.io/docs/llm/application
*/
export type ILlmSchema =
| ILlmSchema.IBoolean
| ILlmSchema.IInteger
| ILlmSchema.INumber
| ILlmSchema.IString
| ILlmSchema.IArray
| ILlmSchema.IObject
| ILlmSchema.IReference
| ILlmSchema.IAnyOf
| ILlmSchema.INull
| ILlmSchema.IUnknown;
export namespace ILlmSchema {
/**
* Configuration options for LLM schema generation.
*
* Controls how TypeScript types are converted to LLM-compatible JSON schemas.
* These settings affect OpenAI structured output compatibility.
*
* @evidence contracts/common.md#principled-implementation The strict flag selects a conversion that rejects optional and dynamic object properties; accepted objects retain their required properties and disallow additional ones. The boolean record names that policy without implementing it.
* @evidence contracts/common.md#clear-and-simple-design One required boolean field.
* @evidence contracts/common.md#prohibited-implementation-shortcuts A plain configuration record.
* @evidence contracts/common.md#meaningful-documentation The comment gives the default and the effect of the flag.
*/
export interface IConfig {
/**
* Whether to enable strict mode (OpenAI structured output).
*
* When `true`, optional and dynamic object properties are rejected.
* Accepted schemas require every property and set `additionalProperties` to
* `false`.
*
* @default false
*/
strict: boolean;
}
/**
* Function parameters schema with shared type definitions.
*
* Extends the object schema to include a `$defs` map for named type
* definitions that can be referenced via `$ref`. This structure allows
* recursive types and reduces schema duplication.
*
* The `additionalProperties` is always `false` for parameters to ensure
* strict argument validation and prevent unexpected properties.
*
* @evidence contracts/common.md#principled-implementation It extends the object schema without its additionalProperties and redeclares that field as `false`, adding a `$defs` map for named definitions, so arguments cannot include unknown properties and recursive types can be referenced.
* @evidence contracts/common.md#clear-and-simple-design Omit plus two members expresses the delta from IObject precisely.
* @evidence contracts/common.md#prohibited-implementation-shortcuts It does not allow a looser setting than the strict argument contract.
* @evidence contracts/common.md#meaningful-documentation The comment explains `$defs`, the fixed false value and why it is fixed.
*/
export interface IParameters extends Omit<IObject, "additionalProperties"> {
/**
* Named schema definitions for reference.
*
* Contains type definitions that can be referenced throughout the schema
* using `$ref: "#/$defs/TypeName"`. Definition names are encoded as one RFC
* 6901 token in URI-fragment form. Essential for recursive types and shared
* structures.
*/
$defs: Record<string, ILlmSchema>;
/**
* Whether additional properties are allowed.
*
* Always `false` for function parameters to ensure strict type checking.
* This prevents LLMs from hallucinating extra properties.
*/
additionalProperties: false;
}
/**
* Boolean type schema.
*
* Represents a JSON Schema boolean type with optional enum constraints and
* default value. Used for true/false parameters and flags.
*
* @evidence contracts/common.md#principled-implementation Boolean extends the JSON schema boolean attribute with an optional enum of booleans and an optional default.
* @evidence contracts/common.md#clear-and-simple-design Two optional members added to the shared attribute.
* @evidence contracts/common.md#prohibited-implementation-shortcuts A plain data record.
* @evidence contracts/common.md#meaningful-documentation The comment says what the constraints are and notes that enum is rarely used.
*/
export interface IBoolean extends IJsonSchemaAttribute.IBoolean {
/**
* Allowed boolean values.
*
* Restricts the value to specific boolean literals. Typically unused since
* booleans only have two possible values, but supported for consistency
* with other types.
*/
enum?: Array<boolean>;
/**
* Default value when not provided.
*
* The boolean value to use when the LLM omits this parameter.
*/
default?: boolean;
}
/**
* Integer type schema.
*
* Represents a JSON Schema integer type with numeric constraints. Maps to
* TypeScript `number` with integer validation. Supports range constraints,
* enum restrictions, and divisibility checks.
*
* @evidence contracts/common.md#principled-implementation Constraint fields are typed as `number & Type<"int64">`, which restricts the schema to whole numbers in the 64-bit range, and multipleOf additionally requires uint64 and a positive value, since a zero or negative divisor is meaningless.
* @evidence contracts/common.md#clear-and-simple-design The integer constraints repeat across fields by the same tag type, and each is a separate member because they are independent in JSON Schema.
* @evidence contracts/common.md#prohibited-implementation-shortcuts It uses the declared type tags and not custom range logic.
* @evidence contracts/common.md#meaningful-documentation Each member says what it constrains and gives examples for multipleOf.
*/
export interface IInteger extends IJsonSchemaAttribute.IInteger {
/**
* Allowed integer values.
*
* Restricts the value to specific integer literals. Useful for representing
* enum-like integer codes or limited value sets.
*/
enum?: Array<number & tags.Type<"int64">>;
/**
* Default value when not provided.
*
* The integer value to use when the LLM omits this parameter.
*/
default?: number & tags.Type<"int64">;
/**
* Minimum value (inclusive).
*
* The value must be greater than or equal to this number.
*/
minimum?: number & tags.Type<"int64">;
/**
* Maximum value (inclusive).
*
* The value must be less than or equal to this number.
*/
maximum?: number & tags.Type<"int64">;
/**
* Exclusive minimum value.
*
* The value must be strictly greater than this number.
*/
exclusiveMinimum?: number & tags.Type<"int64">;
/**
* Exclusive maximum value.
*
* The value must be strictly less than this number.
*/
exclusiveMaximum?: number & tags.Type<"int64">;
/**
* Value must be divisible by this number.
*
* Used for constraints like "must be even" (multipleOf: 2) or "must be a
* multiple of 100" (multipleOf: 100).
*/
multipleOf?: number & tags.Type<"uint64"> & tags.ExclusiveMinimum<0>;
}
/**
* Number (floating-point) type schema.
*
* Represents a JSON Schema number type for floating-point values. Maps to
* TypeScript `number` type. Supports range constraints, enum restrictions,
* and precision checks via multipleOf.
*
* @evidence contracts/common.md#principled-implementation Number constraints are plain numbers with the same keywords as integer, with multipleOf required to be exclusively above zero.
* @evidence contracts/common.md#clear-and-simple-design Seven optional members with no helper types.
* @evidence contracts/common.md#prohibited-implementation-shortcuts A plain data record.
* @evidence contracts/common.md#meaningful-documentation Each member states its meaning and examples for decimal precision.
*/
export interface INumber extends IJsonSchemaAttribute.INumber {
/**
* Allowed numeric values.
*
* Restricts the value to specific number literals. Useful for representing
* specific valid values like percentages or rates.
*/
enum?: Array<number>;
/**
* Default value when not provided.
*
* The number to use when the LLM omits this parameter.
*/
default?: number;
/**
* Minimum value (inclusive).
*
* The value must be greater than or equal to this number.
*/
minimum?: number;
/**
* Maximum value (inclusive).
*
* The value must be less than or equal to this number.
*/
maximum?: number;
/**
* Exclusive minimum value.
*
* The value must be strictly greater than this number.
*/
exclusiveMinimum?: number;
/**
* Exclusive maximum value.
*
* The value must be strictly less than this number.
*/
exclusiveMaximum?: number;
/**
* Value must be divisible by this number.
*
* Useful for decimal precision constraints like "two decimal places"
* (multipleOf: 0.01) or currency amounts (multipleOf: 0.01).
*/
multipleOf?: number & tags.ExclusiveMinimum<0>;
}
/**
* String type schema.
*
* Represents a JSON Schema string type with format validation, pattern
* matching, and length constraints. Maps to TypeScript `string` type with
* optional semantic format annotations.
*
* @evidence contracts/common.md#principled-implementation It lists optional enum, default, format, pattern, content media type and unsigned length bounds; the format is a union of known names or any other string, which allows custom formats while keeping completion for the known ones.
* @evidence contracts/common.md#clear-and-simple-design One interface for all string constraints.
* @evidence contracts/common.md#prohibited-implementation-shortcuts Length bounds are typed with tags and not enforced in this declaration.
* @evidence contracts/common.md#meaningful-documentation Each member is documented, with a caution that LLMs handle complex patterns poorly.
*/
export interface IString extends IJsonSchemaAttribute.IString {
/**
* Allowed string values.
*
* Restricts the value to specific string literals. Maps directly to
* TypeScript string literal union types.
*/
enum?: Array<string>;
/**
* Default value when not provided.
*
* The string to use when the LLM omits this parameter.
*/
default?: string;
/**
* Semantic format specifier.
*
* Indicates the string represents a specific format like email, UUID, or
* date-time. LLMs may use this to generate appropriate values. Common
* formats include "email", "uri", "uuid", "date-time".
*/
format?:
| "binary"
| "byte"
| "password"
| "regex"
| "uuid"
| "email"
| "hostname"
| "idn-email"
| "idn-hostname"
| "iri"
| "iri-reference"
| "ipv4"
| "ipv6"
| "uri"
| "uri-reference"
| "uri-template"
| "url"
| "date-time"
| "date"
| "time"
| "duration"
| "json-pointer"
| "relative-json-pointer"
| (string & {});
/**
* Regular expression pattern for validation.
*
* The string must match this regex pattern. Note that LLMs may struggle
* with complex regex patterns; simple patterns work best.
*/
pattern?: string;
/**
* MIME type of the string content.
*
* Indicates the content type when the string contains encoded binary data,
* such as "application/json" or "image/png".
*/
contentMediaType?: string;
/**
* Minimum string length.
*
* The string must have at least this many characters.
*/
minLength?: number & tags.Type<"uint64">;
/**
* Maximum string length.
*
* The string must have at most this many characters.
*/
maxLength?: number & tags.Type<"uint64">;
}
/**
* Array type schema.
*
* Represents a JSON Schema array type with item type validation and size
* constraints. Maps to TypeScript `T[]` or `Array<T>` types. Note: Tuple
* types are not supported by LLM schemas.
*
* @evidence contracts/common.md#principled-implementation The array has a required `items` schema, optional uniqueness and unsigned size bounds; heterogeneous elements are expressed with anyOf, and tuples are not representable.
* @evidence contracts/common.md#clear-and-simple-design Four members, with the item schema recursive through the union.
* @evidence contracts/common.md#prohibited-implementation-shortcuts A plain data record.
* @evidence contracts/common.md#meaningful-documentation The comment says tuples are unsupported and each member describes its constraint.
*/
export interface IArray extends IJsonSchemaAttribute.IArray {
/**
* Schema for array elements.
*
* All elements in the array must conform to this schema. For heterogeneous
* arrays, use an `anyOf` schema.
*/
items: ILlmSchema;
/**
* Whether elements must be unique.
*
* When `true`, no two elements may be equal. Maps to TypeScript `Set<T>`
* semantics but represented as an array.
*/
uniqueItems?: boolean;
/**
* Minimum number of elements.
*
* The array must contain at least this many items.
*/
minItems?: number & tags.Type<"uint64">;
/**
* Maximum number of elements.
*
* The array must contain at most this many items.
*/
maxItems?: number & tags.Type<"uint64">;
}
/**
* Object type schema.
*
* Represents a JSON Schema object type with named properties. Maps to
* TypeScript interface or object type. Supports required property
* declarations and dynamic additional properties.
*
* @evidence contracts/common.md#principled-implementation It has required `properties` and `required`, which the generator always writes even when empty, and an optional additionalProperties that is boolean or a schema; strict mode forces it false.
* @evidence contracts/common.md#clear-and-simple-design Three members expressing JSON Schema object semantics directly.
* @evidence contracts/common.md#prohibited-implementation-shortcuts A plain data record.
* @evidence contracts/common.md#meaningful-documentation The comment and member comments explain defaults, strictness and always-present fields.
*/
export interface IObject extends IJsonSchemaAttribute.IObject {
/**
* Property name to schema mapping.
*
* Defines the type of each named property on the object. All properties are
* defined here regardless of whether they are required or optional.
* Generated LLM object schemas always include this field, even when it is
* empty.
*/
properties: Record<string, ILlmSchema>;
/**
* Schema for dynamic/additional properties.
*
* - `false` (default): No additional properties allowed
* - `true`: Any additional properties allowed
* - Schema: Additional properties must match this schema
*
* {@link ILlmSchema.IConfig.strict} forces this to `false`.
*/
additionalProperties?: ILlmSchema | boolean;
/**
* List of required property names.
*
* Properties in this array must be present in the object. Generated LLM
* object schemas always include this field, even when it is empty. In
* strict mode, an optional property is a conversion error.
*/
required: string[];
}
/**
* Reference to a named schema definition.
*
* Points to a schema defined in the `$defs` map using a JSON pointer. Used to
* avoid schema duplication and enable recursive type definitions. The
* reference path format is `#/$defs/TypeName`, where `TypeName` is one RFC
* 6901 token encoded as a URI fragment.
*
* @evidence contracts/common.md#principled-implementation A reference is a `$ref` string pointing to the `$defs` map in the form `#/$defs/Name`, extending only the attribute record, so the reference carries documentation but no structural keywords.
* @evidence contracts/common.md#clear-and-simple-design One field added to the attribute record.
* @evidence contracts/common.md#prohibited-implementation-shortcuts The pointer string is not parsed or resolved by the type.
* @evidence contracts/common.md#meaningful-documentation The comment explains the pointer format and encoding of the type name.
*/
export interface IReference extends IJsonSchemaAttribute {
/**
* JSON pointer to the referenced schema.
*
* Must be in the format `#/$defs/TypeName`, where `TypeName` decodes to a
* key in the parent schema's `$defs` map.
*/
$ref: string;
}
/**
* Union type schema (A | B | C).
*
* Represents a TypeScript union type where the value can match any one of the
* member schemas. Use discriminated unions with `x-discriminator` when
* possible for better LLM comprehension.
*
* @evidence contracts/common.md#principled-implementation Union members are in `anyOf`, whose element type excludes nested anyOf so unions are flattened, with an optional discriminator that names the property and maps values to references.
* @evidence contracts/common.md#clear-and-simple-design Two members and a nested discriminator type in the namespace.
* @evidence contracts/common.md#prohibited-implementation-shortcuts Flattening is enforced by the type and not by a runtime step here.
* @evidence contracts/common.md#meaningful-documentation The comment advises discriminated unions and each member says what it holds.
*/
export interface IAnyOf extends IJsonSchemaAttribute {
/**
* Array of possible schemas.
*
* The value must match at least one of these schemas. Nested `anyOf`
* schemas are flattened to avoid deep nesting.
*/
anyOf: Exclude<ILlmSchema, ILlmSchema.IAnyOf>[];
/**
* Discriminator for tagged/discriminated unions.
*
* Helps LLMs understand which variant to select based on a discriminating
* property value. Improves type inference accuracy.
*/
"x-discriminator"?: IAnyOf.IDiscriminator;
}
export namespace IAnyOf {
/**
* Discriminator configuration for tagged unions.
*
* Specifies which property distinguishes between union variants and maps
* discriminator values to their corresponding schemas.
*
* @evidence contracts/common.md#principled-implementation A property name plus an optional map from literal values to `$ref` strings describes how to select the variant of a tagged union.
* @evidence contracts/common.md#clear-and-simple-design Two members, with the mapping optional because the value may be inferred.
* @evidence contracts/common.md#prohibited-implementation-shortcuts The type only describes the discriminator and does not check that members share the property.
* @evidence contracts/common.md#meaningful-documentation The comment says what the property and mapping are and the requirement for unique literals.
*/
export interface IDiscriminator {
/**
* Name of the discriminating property.
*
* This property must exist on all union member object schemas and contain
* unique literal values that identify each variant.
*/
propertyName: string;
/**
* Mapping from discriminator values to schema references.
*
* Keys are the literal values of the discriminator property, values are
* `$ref` paths to the corresponding schemas.
*/
mapping?: Record<string, string>;
}
}
/**
* Null type schema.
*
* Represents the JSON null value. In TypeScript union types like `T | null`,
* this schema appears in an `anyOf` alongside the T schema.
*
* @evidence contracts/common.md#principled-implementation The null variant inherits the null attribute record, whose literal "null" type identifies the JSON null value; a nullable type appears as an anyOf containing it.
* @evidence contracts/common.md#clear-and-simple-design An empty extension.
* @evidence contracts/common.md#prohibited-implementation-shortcuts It adds no members.
* @evidence contracts/common.md#meaningful-documentation The comment states how nullable unions use it.
*/
export interface INull extends IJsonSchemaAttribute.INull {}
/**
* Unknown/any type schema.
*
* Represents TypeScript `any` or `unknown` types where no specific type
* constraint is defined. Use sparingly as LLMs may generate unexpected values
* for unconstrained types.
*
* @evidence contracts/common.md#principled-implementation The unknown variant inherits the attribute record with no type, so it represents a value without constraint.
* @evidence contracts/common.md#clear-and-simple-design An empty extension.
* @evidence contracts/common.md#prohibited-implementation-shortcuts It adds no members.
* @evidence contracts/common.md#meaningful-documentation The comment says it stands for any or unknown and warns of its looseness.
*/
export interface IUnknown extends IJsonSchemaAttribute.IUnknown {}
}TypeScript Source
import { LlmJson } from "@typia/utils";
import OpenAI from "openai";
import typia, { tags } from "typia";
interface IMember {
email: string & tags.Format<"email">;
name: string;
age: number & tags.Minimum<0>;
hobbies: string[];
joined_at: string & tags.Format<"date">;
}
const main = async (): Promise<void> => {
// Generate structured output interface
const output = typia.llm.structuredOutput<IMember>();
// Use schema with OpenAI
const client: OpenAI = new OpenAI({
apiKey: "<YOUR_OPENAI_API_KEY>",
});
const completion: OpenAI.ChatCompletion =
await client.chat.completions.create({
model: "gpt-4o",
messages: [
{
role: "user",
content: [
"I am a new member of the community.",
"",
"My name is John Doe, and I am 25 years old.",
"I like playing basketball and reading books,",
"and joined to this community at 2022-01-01.",
].join("\n"),
},
],
response_format: {
type: "json_schema",
json_schema: {
name: "member",
schema: output.parameters as any,
},
},
});
// Parse LLM response with type coercion
const parsed = output.parse(completion.choices[0].message.content!);
if (!parsed.success) {
console.error("Parse failed:", parsed.errors);
return;
}
// Validate the parsed data
const validated = output.validate(parsed.data);
if (!validated.success) {
// Format errors for LLM feedback
console.error(LlmJson.stringify(validated));
return;
}
console.log("Success:", validated.data);
};
main().catch(console.error);Compiled JavaScript
import { LlmJson } from "@typia/utils";
import OpenAI from "openai";
import typia from "typia";
import * as _coerceLlmArguments_1 from "typia/lib/internal/_coerceLlmArguments";
import * as _isFormatDate_1 from "typia/lib/internal/_isFormatDate";
import * as _isFormatEmail_1 from "typia/lib/internal/_isFormatEmail";
import * as _parseLlmArguments_1 from "typia/lib/internal/_parseLlmArguments";
import * as _validateReport_1 from "typia/lib/internal/_validateReport";
const main = async () => {
// Generate structured output interface
const output = (() => {
const _io0 = (input) =>
"string" === typeof input.email &&
_isFormatEmail_1._isFormatEmail(input.email) &&
"string" === typeof input.name &&
"number" === typeof input.age &&
0 <= input.age &&
Array.isArray(input.hobbies) &&
input.hobbies.every((elem) => "string" === typeof elem) &&
"string" === typeof input.joined_at &&
_isFormatDate_1._isFormatDate(input.joined_at);
const _vo0 = (input, _path, _exceptionable = true) =>
[
("string" === typeof input.email &&
(_isFormatEmail_1._isFormatEmail(input.email) ||
_report(_exceptionable, {
path: _path + ".email",
expected: 'string & Format<"email">',
value: input.email,
}))) ||
_report(_exceptionable, {
path: _path + ".email",
expected: '(string & Format<"email">)',
value: input.email,
}),
"string" === typeof input.name ||
_report(_exceptionable, {
path: _path + ".name",
expected: "string",
value: input.name,
}),
("number" === typeof input.age &&
(0 <= input.age ||
_report(_exceptionable, {
path: _path + ".age",
expected: "number & Minimum<0>",
value: input.age,
}))) ||
_report(_exceptionable, {
path: _path + ".age",
expected: "(number & Minimum<0>)",
value: input.age,
}),
((Array.isArray(input.hobbies) ||
_report(_exceptionable, {
path: _path + ".hobbies",
expected: "Array<string>",
value: input.hobbies,
})) &&
input.hobbies
.map(
(elem, _index2) =>
"string" === typeof elem ||
_report(_exceptionable, {
path: _path + ".hobbies[" + _index2 + "]",
expected: "string",
value: elem,
}),
)
.every((flag) => flag)) ||
_report(_exceptionable, {
path: _path + ".hobbies",
expected: "Array<string>",
value: input.hobbies,
}),
("string" === typeof input.joined_at &&
(_isFormatDate_1._isFormatDate(input.joined_at) ||
_report(_exceptionable, {
path: _path + ".joined_at",
expected: 'string & Format<"date">',
value: input.joined_at,
}))) ||
_report(_exceptionable, {
path: _path + ".joined_at",
expected: '(string & Format<"date">)',
value: input.joined_at,
}),
].every((flag) => flag);
const __parameters = {
type: "object",
properties: {
email: {
type: "string",
format: "email",
},
name: {
type: "string",
},
age: {
type: "number",
minimum: 0,
},
hobbies: {
type: "array",
items: {
type: "string",
},
},
joined_at: {
type: "string",
format: "date",
},
},
required: ["email", "name", "age", "hobbies", "joined_at"],
additionalProperties: false,
description: "Current Type: {@link IMember}",
$defs: {},
};
const __is = (input) =>
"object" === typeof input && null !== input && _io0(input);
let errors;
let _report;
const __validate = (input) => {
if (false === __is(input)) {
errors = [];
_report = _validateReport_1._validateReport(errors);
((input, _path, _exceptionable = true) =>
((("object" === typeof input && null !== input) ||
_report(true, {
path: _path + "",
expected: "IMember",
value: input,
})) &&
_vo0(input, _path + "", true)) ||
_report(true, {
path: _path + "",
expected: "IMember",
value: input,
}))(input, "$input", true);
const success = 0 === errors.length;
return success
? {
success,
data: input,
}
: {
success,
errors,
data: input,
};
}
return {
success: true,
data: input,
};
};
return {
parameters: __parameters,
parse: (input) =>
_parseLlmArguments_1._parseLlmArguments(input, __parameters),
coerce: (input) =>
_coerceLlmArguments_1._coerceLlmArguments(input, __parameters),
validate: __validate,
};
})();
// Use schema with OpenAI
const client = new OpenAI({
apiKey: "<YOUR_OPENAI_API_KEY>",
});
const completion = await client.chat.completions.create({
model: "gpt-4o",
messages: [
{
role: "user",
content: [
"I am a new member of the community.",
"",
"My name is John Doe, and I am 25 years old.",
"I like playing basketball and reading books,",
"and joined to this community at 2022-01-01.",
].join("\n"),
},
],
response_format: {
type: "json_schema",
json_schema: {
name: "member",
schema: output.parameters,
},
},
});
// Parse LLM response with type coercion
const parsed = output.parse(completion.choices[0].message.content);
if (!parsed.success) {
console.error("Parse failed:", parsed.errors);
return;
}
// Validate the parsed data
const validated = output.validate(parsed.data);
if (!validated.success) {
// Format errors for LLM feedback
console.error(LlmJson.stringify(validated));
return;
}
console.log("Success:", validated.data);
};
main().catch(console.error);When to use this
| If you need⦠| Use |
|---|---|
| One JSON shape with parse + coerce + validate | structuredOutput<T>() |
| One JSON shape, schema only (no helpers) | parameters<T>() |
| Multiple functions the LLM picks between | application<Class>() |
| Schema of an inner sub-type (advanced) | schema<T>() |
Parse and coerce
The same parse / coerce / validate / stringify harness as application:
Parsing (string β T)
const output = typia.llm.structuredOutput<IMember>();
const jsonString = '{"name": "John", "age": "25"}';
const result = output.parse(jsonString);
if (result.success) {
console.log(result.data.age); // 25 β coerced to number
}Coercing (object β T)
const output = typia.llm.structuredOutput<IMember>();
const preParsed = { name: "John", age: "25" };
const coerced = output.coerce(preParsed);
console.log(coerced.age); // 25 β coerced to numberundefined
import { IJsonParseResult } from "./IJsonParseResult";
import { ILlmSchema } from "./ILlmSchema";
import { IValidation } from "./IValidation";
/**
* LLM structured output schema with parsing and validation utilities.
*
* `ILlmStructuredOutput<T>` is generated by `typia.llm.structuredOutput<T>()`
* to provide everything needed for handling LLM structured outputs: the JSON
* schema for prompting, and functions for parsing, coercing, and validating
* responses.
*
* Structured outputs allow LLMs to generate data conforming to a predefined
* schema instead of free-form text. This is useful for:
*
* - Extracting structured data from conversations
* - Generating typed responses for downstream processing
* - Ensuring consistent output formats across LLM calls
*
* Workflow:
*
* 1. Pass {@link parameters} schema to LLM provider
* 2. Receive LLM response (JSON string or pre-parsed object)
* 3. Use {@link parse} for raw strings or {@link coerce} for pre-parsed objects
* 4. Use {@link validate} to check the result and get detailed errors
*
* @author Jeongho Nam - https://github.com/samchon
*
* @template T The expected output type
*
* @evidence contracts/common.md#principled-implementation The record holds the schema to send to a provider and the parse, coerce and validate functions, typed by T for the results, with parse and coerce going through the same schema and validate checking the final value; this gives the full path from prompt to checked output.
* @evidence contracts/common.md#clear-and-simple-design Four members, one per step of the documented workflow.
* @evidence contracts/common.md#prohibited-implementation-shortcuts The member functions are produced by the transform and are not patched here.
* @evidence contracts/common.md#meaningful-documentation The comment lists use cases and the four-step workflow, and each member explains its behavior.
*/
export interface ILlmStructuredOutput<T = unknown> {
/**
* JSON schema for the structured output.
*
* Pass this schema to LLM providers (OpenAI, Anthropic, Google, etc.) through
* their structured output or response format configuration to constrain the
* output format. The schema includes `$defs` for shared type definitions and
* `properties` for the output structure.
*/
parameters: ILlmSchema.IParameters;
/**
* Lenient JSON parser with schema-based type coercion.
*
* Handles incomplete or malformed JSON commonly produced by LLMs:
*
* - Unclosed brackets, strings, trailing commas
* - JavaScript-style comments (`//` and multi-line)
* - Unquoted object keys, incomplete keywords (`tru`, `fal`, `nul`)
* - Markdown code block extraction, junk prefix skipping
*
* Also coerces double-stringified values based on the schema:
*
* - `"42"` β `42` (when schema expects number)
* - `"true"` β `true` (when schema expects boolean)
* - `"null"` β `null` (when schema expects null)
* - `"{...}"` β `{...}` (when schema expects object)
* - `"[...]"` β `[...]` (when schema expects array)
*
* Type validation is NOT performed β use {@link validate} after parsing.
*
* If the SDK (e.g., LangChain, Vercel AI, MCP) already parses JSON internally
* and provides a pre-parsed object, use {@link coerce} instead.
*
* @param input Raw JSON string from LLM output
*
* @returns Parse result with data on success, or partial data with errors
*
* @evidence contracts/common.md#principled-implementation Takes a raw string and returns a parse result typed by T, which separates success from recovered partial data; it coerces by schema without validating.
* @evidence contracts/common.md#clear-and-simple-design A function-valued property with a single argument.
* @evidence contracts/common.md#prohibited-implementation-shortcuts It states that validation is not performed.
* @evidence contracts/common.md#meaningful-documentation The comment lists the recoverable malformed JSON forms and the coercion examples.
*/
parse: (input: string) => IJsonParseResult<T>;
/**
* Coerce pre-parsed output to match expected schema types.
*
* **Use this only when the SDK already parses JSON internally.** For raw JSON
* strings from LLM output, use {@link parse} instead β it handles both lenient
* parsing and type coercion in one step.
*
* LLMs often return values with incorrect types even after parsing:
*
* - `"42"` β `42` (when schema expects number)
* - `"true"` β `true` (when schema expects boolean)
* - `"null"` β `null` (when schema expects null)
* - `"{...}"` β `{...}` (when schema expects object)
* - `"[...]"` β `[...]` (when schema expects array)
*
* This function recursively coerces these double-stringified values based on
* the {@link parameters} schema.
*
* Type validation is NOT performed β use {@link validate} after coercion.
*
* @param input Pre-parsed output object from SDK
*
* @returns Coerced output under the unchecked static interpretation T
*
* @evidence contracts/common.md#principled-implementation The input is unknown and the declared result is T, but coercion is not validation and may leave malformed values unchanged. T is an unchecked static interpretation; the validate member owns the runtime check before callers rely on that type.
* @evidence contracts/common.md#clear-and-simple-design A function-valued property with a single argument.
* @evidence contracts/common.md#prohibited-implementation-shortcuts It is documented as a coercion and not a validation.
* @evidence contracts/common.md#meaningful-documentation The comment warns to use it only for pre-parsed input and gives coercion examples.
*/
coerce: (input: unknown) => T;
/**
* Validates LLM-generated output against the schema.
*
* LLMs frequently make type errors such as returning strings instead of
* numbers or missing required properties. Use this validator to check output
* before further processing.
*
* When validation fails, use {@link LlmJson.stringify} from `@typia/utils` to
* format the error for LLM feedback. The formatted output shows the invalid
* JSON with inline error comments, helping the LLM understand and correct its
* mistakes in the next turn.
*
* @param input The output generated by the LLM
*
* @returns Validation result with success status and any errors
*
* @evidence contracts/common.md#principled-implementation Takes unknown and returns IValidation of T, which is the type-safe check on the coerced result; failure has structured errors.
* @evidence contracts/common.md#clear-and-simple-design A function-valued property with a single argument.
* @evidence contracts/common.md#prohibited-implementation-shortcuts It reports errors instead of throwing.
* @evidence contracts/common.md#meaningful-documentation The comment explains the feedback use and links the error formatter.
*/
validate: (input: unknown) => IValidation<T>;
}Type coercion handles (full list in LlmJson):
"42"β42for number/integer fields"true"/"false"βtrue/falsefor boolean fields"null"βnullfor nullable fields"{...}"β object (double-stringified objects)"[...]"β array (double-stringified arrays)- Nested coercion descends into properties recursively
parse vs coerce β same rule as in application.
- Raw string from the LLM β
parse(text) - Already-parsed object from the SDK β
coerce(obj)
parse is coerce with a lenient JSON parser in front.
Validation feedback
import { LlmJson } from "@typia/utils";
const output = typia.llm.structuredOutput<IMember>();
const parsed = output.parse(llmResponse);
if (parsed.success) {
const v = output.validate(parsed.data);
if (!v.success) {
// Annotated JSON the LLM can read and self-correct from
const feedback = LlmJson.stringify(v);
// Send `feedback` back to the model in the next turn
}
}The formatted output:
{
"name": "John",
"age": -5, // β [{"path":"$input.age","expected":"number & Minimum<0>"}]
"email": "invalid", // β [{"path":"$input.email","expected":"string & Format<\"email\">"}]
}Same pattern, same payoff as in application β qwen3-coder-next jumps from 6.75% raw success to 100% with the harness around it.
Strict mode
const strict = typia.llm.structuredOutput<IMember, { equals: true }>();
strict.validate({
name: "John",
age: 25,
email: "john@example.com",
hobbies: ["reading"],
extraField: "not allowed", // β rejected
});{ equals: true } switches the validator to validateEquals semantics. (The schema-side additionalProperties: false is always emitted on LLM parameter schemas, regardless of the flag β equals only changes how the runtime validator treats stray properties at validation time.)
Restrictions
Same as parameters and schema:
Tmust be a keyworded object type with static keys- No dynamic keys (
Record<string, V>) Tmay not be nullable or optional
A primitive T is rejected by TypeScript itself (TS2344) before the transform runs, because the signature constrains it to Record<string, any>.
Source That Wonβt Compile
import typia from "typia";
typia.llm.structuredOutput<Record<string, boolean>>();
typia.llm.structuredOutput<Array<number>>();Compiler Errors
src/examples/llm.structuredOutput.violation.ts:3:1 - error TS(typia.llm.structuredOutput): typia transform error: unsupported type detected
- Recordstringboolean
- LLM parameters must not have dynamic keys.
3 typia.llm.structuredOutput<Record<string, boolean>>();
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
src/examples/llm.structuredOutput.violation.ts:4:1 - error TS(typia.llm.structuredOutput): typia transform error: unsupported type detected
- Arraynumber
- LLM parameters must be an object type.
4 typia.llm.structuredOutput<Array<number>>();
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~Where to go next
- Multiple functions, LLM picks β
application - Just the schema, no helpers β
parameters - Harness internals (
parse,coerce,validate,stringify) βLlmJson