Skip to Content

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.

signature
namespace llm { function parameters< Parameters extends Record<string, any>, Config extends Partial<ILlmSchema.IConfig> = {}, >(): ILlmSchema.IParameters; }

First example

hello-parameters.ts
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:

src/examples/llm.parameters.ts
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

typia
export namespace llm { export function parameters< Parameters extends Record<string, any>, Config extends Partial<ILlmSchema.IConfig> = {}, >(): ILlmSchema.IParameters; }
examples/src/llm/parameters.ts
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">; }

πŸ“– Open in Playground

When to use this

What you needUse
Just the schema (your code does the parsing)parameters<T>()
Schema plus parse/coerce/validate helpersstructuredOutput<T>()
Multiple functions, LLM picks one to callapplication<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:

GoalUse
Parse messy LLM JSON text + coerce typesLlmJson.parse<T>(text, schema)
Coerce types on an already-parsed objectLlmJson.coerce<T>(obj, schema)
Validate against the typetypia.validate<T>(obj)
Format errors back to the LLMLlmJson.stringify(failure)
examples/src/llm/parameters-parse.ts
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

examples/src/llm/parameters-validate.ts
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.

AutoBeTest.IExpression
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 total

Restrictions

LLMs use keyworded arguments. typia enforces this at compile time:

  • T must be an object type with static keys
  • No dynamic keys (Record<string, V>)
  • T may not be nullable or optional
src/examples/llm.parameters.violation.ts
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

Last updated on