typia.llm.parameters β JSON Schema for one type, no helpers
If all you need is the JSON Schema for T (so you can put it in response_format or wherever your LLM SDK accepts a schema), typia.llm.parameters<T>() is the minimum tool. It returns the schema and nothing else β no parse, no coerce, no validate.
namespace llm {
function parameters<
Parameters extends Record<string, any>,
Config extends Partial<ILlmSchema.IConfig> = {},
>(): ILlmSchema.IParameters;
}First example
import typia, { tags } from "typia";
interface IMember {
email: string & tags.Format<"email">;
name: string;
age: number;
hobbies: string[];
}
const schema = typia.llm.parameters<IMember>();
// β ILlmSchema.IParameters (an object schema with $defs)You hand schema to your LLM SDK and the model knows the expected JSON shape. The full pattern with OpenAI:
import OpenAI from "openai";
import typia, { tags } from "typia";
interface IMember {
email: string & tags.Format<"email">;
name: string;
age: number;
hobbies: string[];
joined_at: string & tags.Format<"date">;
}
const main = async (): Promise<void> => {
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: typia.llm.parameters<IMember>() as any,
},
},
});
console.log(JSON.parse(completion.choices[0].message.content!));
};
main().catch(console.error);Terminal output{ email: 'john.doe@example.com', name: 'John Doe', age: 25, hobbies: [ 'playing basketball', 'reading books' ], joined_at: '2022-01-01' }
undefined
export namespace llm {
export function parameters<
Parameters extends Record<string, any>,
Config extends Partial<ILlmSchema.IConfig> = {},
>(): ILlmSchema.IParameters;
}TypeScript Source
import typia, { ILlmSchema, tags } from "typia";
const parameters: ILlmSchema.IParameters = typia.llm.parameters<IMember>();
console.log(parameters);
interface IMember {
email: string & tags.Format<"email">;
name: string;
age: number;
hobbies: string[];
joined_at: string & tags.Format<"date">;
}When to use this
| What you need | Use |
|---|---|
| Just the schema (your code does the parsing) | parameters<T>() |
| Schema plus parse/coerce/validate helpers | structuredOutput<T>() |
| Multiple functions, LLM picks one to call | application<Class>() |
structuredOutput<T>() is essentially parameters<T>() + the parse/coerce/validate methods bundled together. If you donβt need those methods (or youβll wire them up yourself from LlmJson), parameters is the leaner choice.
Function calling harness
parameters is schema-only by design, but you can still get the parse/coerce/validate behavior by composing manually:
| Goal | Use |
|---|---|
| Parse messy LLM JSON text + coerce types | LlmJson.parse<T>(text, schema) |
| Coerce types on an already-parsed object | LlmJson.coerce<T>(obj, schema) |
| Validate against the type | typia.validate<T>(obj) |
| Format errors back to the LLM | LlmJson.stringify(failure) |
Parsing Example
import { LlmJson } from "@typia/utils";
import { dedent } from "@typia/utils";
import typia, { ILlmSchema, tags } from "typia";
const params: ILlmSchema.IParameters = typia.llm.parameters<IProps>();
// LLM sometimes returns malformed JSON with wrong types
const llmOutput = dedent`
> LLM sometimes returns some prefix text with markdown JSON code block.
I'd be happy to help you with your order! π
\`\`\`json
{
"order": {
"payment": "{\"type\":\"card\",\"cardNumber\":\"1234-5678", // unclosed string & bracket
"product": {
name: "Laptop", // unquoted key
price: "1299.99", // wrong type (string instead of number)
quantity: 2, // trailing comma
},
"customer": {
// incomplete keyword + unclosed brackets
"name": "John Doe",
"email": "john@example.com",
vip: tru
\`\`\`
`;
const result = LlmJson.parse(llmOutput, params);
if (result.success) console.log(result.data);
interface IProps {
order: IOrder;
}
interface IOrder {
payment: IPayment;
product: {
name: string;
price: number & tags.Minimum<0>;
quantity: number & tags.Type<"uint32">;
};
customer: {
name: string;
email: string & tags.Format<"email">;
vip: boolean;
};
}
type IPayment =
| { type: "card"; cardNumber: string }
| { type: "bank"; accountNumber: string };Double-stringified objects fail every time without coercion
LLMs frequently emit "{\"x\":1}" (a JSON-stringified value) where the schema expects an object. Without coercion, validation rejects those responses outright. With parse/coerce it descends through the string-wrapped layers and produces typed data. See application for the full picture.
Validation feedback
import { LlmJson } from "@typia/utils";
import typia, { IValidation, tags } from "typia";
// LLM generated invalid data
const input = {
order: {
payment: { type: "card", cardNumber: 12345678 }, // should be string
product: {
name: "Laptop",
price: -100, // violates Minimum<0>
quantity: 2.5, // should be uint32
},
customer: {
name: "John Doe",
email: "invalid-email", // violates Format<"email">
vip: "yes", // should be boolean
},
},
};
// Validate and format errors for LLM feedback
const result: IValidation<IProps> = typia.validate<IProps>(input);
if (result.success === false) {
const feedback: string = LlmJson.stringify(result);
console.log(feedback);
}
interface IProps {
order: IOrder;
}
interface IOrder {
payment: IPayment;
product: {
name: string;
price: number & tags.Minimum<0>;
quantity: number & tags.Type<"uint32">;
};
customer: {
name: string;
email: string & tags.Format<"email">;
vip: boolean;
};
}
type IPayment =
| { type: "card"; cardNumber: string }
| { type: "bank"; accountNumber: string };When validation fails, format the errors with LlmJson.stringify and send them back to the LLM. The formatted output:
{
"name": "John Doe",
"age": -5, // β [{"path":"$input.age","expected":"number & Minimum<0>"}]
"email": "invalid-email", // β [{"path":"$input.email","expected":"string & Format<\"email\">"}]
"hobbies": "reading", // β [{"path":"$input.hobbies","expected":"Array<string>"}]
"joined_at": "not-a-date", // β [{"path":"$input.joined_at","expected":"string & Format<\"date\">"}]
"metadata": {
"level": "platinum", // β [{"path":"$input.metadata","expected":"(IMetadata & { level: \"gold\"; }) | (IMetadata & { level: \"silver\"; })"}]
"points": 1500
}
}In AutoBeΒ , the same pattern took qwen3-coder-next from 6.75% β 100% correct on compiler AST types. Compiler ASTs are the hardest possible target β unlimited unions, unlimited depth, recursive references β so if the harness works there, it works on anything realistic.
export type IExpression =
| IBooleanLiteral
| INumericLiteral
| IStringLiteral
| IArrayLiteralExpression // recursive
| IObjectLiteralExpression // recursive
| INullLiteral
| IUndefinedKeyword
| IIdentifier
| IPropertyAccessExpression // recursive
| IElementAccessExpression // recursive
| ITypeOfExpression // recursive
| IPrefixUnaryExpression // recursive
| IPostfixUnaryExpression // recursive
| IBinaryExpression // recursive
| IArrowFunction // recursive
| ICallExpression // recursive
| INewExpression // recursive
| IConditionalPredicate // recursive
| ... // 30+ expression types totalRestrictions
LLMs use keyworded arguments. typia enforces this at compile time:
Tmust be an object type with static keys- No dynamic keys (
Record<string, V>) Tmay not be nullable or optional
Source That Wonβt Compile
import typia from "typia";
typia.llm.parameters<string>();
typia.llm.parameters<Record<string, boolean>>();
typia.llm.parameters<Array<number>>();For deeper type-level rules (recursion handling, anyOf/$ref behavior) see llm.schema.
Where to go next
- All-in-one helper bundle β
structuredOutput - Multiple methods on a class β
application - Inner-type schemas with
$defsβschema - Parsing / coercion / validation utilities β
LlmJson