Skip to content

Commit 2dbc70c

Browse files
committed
feat(website): about LlmJson module and related functions.
1 parent e791620 commit 2dbc70c

File tree

9 files changed

+588
-36
lines changed

9 files changed

+588
-36
lines changed

.vscode/settings.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,4 @@
1010
"[mdx]": {
1111
"editor.wordWrap": "on"
1212
},
13-
"typescript.tsdk": "node_modules\\typescript\\lib"
1413
}

eslint.config.cjs

Lines changed: 0 additions & 28 deletions
This file was deleted.
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import typia, { ILlmApplication, ILlmFunction, tags } from "typia";
2+
3+
const app: ILlmApplication = typia.llm.application<OrderService>();
4+
const func: ILlmFunction = app.functions[0];
5+
6+
// Anthropic, Vercel AI, LangChain, MCP already parse JSON internally.
7+
// However, types are often wrong:
8+
const fromSdk = {
9+
order: {
10+
payment: '{"type":"card","cardNumber":"1234-5678"}', // stringified
11+
product: {
12+
name: "Laptop",
13+
price: "1299.99", // string instead of number
14+
quantity: "2", // string instead of number
15+
},
16+
customer: {
17+
name: "John Doe",
18+
email: "john@example.com",
19+
vip: "true", // string instead of boolean
20+
},
21+
},
22+
};
23+
24+
const result = func.coerce(fromSdk);
25+
console.log(result);
26+
27+
interface IOrder {
28+
payment: IPayment;
29+
product: {
30+
name: string;
31+
price: number & tags.Minimum<0>;
32+
quantity: number & tags.Type<"uint32">;
33+
};
34+
customer: {
35+
name: string;
36+
email: string & tags.Format<"email">;
37+
vip: boolean;
38+
};
39+
}
40+
41+
type IPayment =
42+
| { type: "card"; cardNumber: string }
43+
| { type: "bank"; accountNumber: string };
44+
45+
declare class OrderService {
46+
/**
47+
* Create a new order.
48+
*
49+
* @param props Order properties
50+
*/
51+
createOrder(props: { order: IOrder }): { id: string };
52+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import { dedent } from "@typia/utils";
2+
import typia, { ILlmApplication, ILlmFunction, tags } from "typia";
3+
4+
const app: ILlmApplication = typia.llm.application<OrderService>();
5+
const func: ILlmFunction = app.functions[0];
6+
7+
// LLM often returns malformed JSON with wrong types
8+
const llmOutput = dedent`
9+
Here is your order:
10+
11+
\`\`\`json
12+
{
13+
"order": {
14+
"payment": "{\"type\":\"card\",\"cardNumber\":\"1234-5678\"}"
15+
"product": {
16+
name: "Laptop",
17+
price: "1299.99",
18+
quantity: 2,
19+
},
20+
"customer": {
21+
"name": "John Doe",
22+
"email": "john@example.com",
23+
vip: tru
24+
\`\`\`
25+
`;
26+
27+
const result = func.parse(llmOutput);
28+
if (result.success) console.log(result);
29+
30+
interface IOrder {
31+
payment: IPayment;
32+
product: {
33+
name: string;
34+
price: number & tags.Minimum<0>;
35+
quantity: number & tags.Type<"uint32">;
36+
};
37+
customer: {
38+
name: string;
39+
email: string & tags.Format<"email">;
40+
vip: boolean;
41+
};
42+
}
43+
44+
type IPayment =
45+
| { type: "card"; cardNumber: string }
46+
| { type: "bank"; accountNumber: string };
47+
48+
declare class OrderService {
49+
/**
50+
* Create a new order.
51+
*
52+
* @param props Order properties
53+
*/
54+
createOrder(props: { order: IOrder }): { id: string };
55+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import { LlmJson } from "@typia/utils";
2+
import typia, { ILlmApplication, ILlmFunction, IValidation, tags } from "typia";
3+
4+
const app: ILlmApplication = typia.llm.application<OrderService>();
5+
const func: ILlmFunction = app.functions[0];
6+
7+
// LLM generated invalid data
8+
const input = {
9+
order: {
10+
payment: { type: "card", cardNumber: 12345678 }, // should be string
11+
product: {
12+
name: "Laptop",
13+
price: -100, // violates Minimum<0>
14+
quantity: 2.5, // should be uint32
15+
},
16+
customer: {
17+
name: "John Doe",
18+
email: "invalid-email", // violates Format<"email">
19+
vip: "yes", // should be boolean
20+
},
21+
},
22+
};
23+
24+
// Validate and format errors for LLM feedback
25+
const result: IValidation = func.validate(input);
26+
if (result.success === false) {
27+
const feedback: string = LlmJson.stringify(result);
28+
console.log(feedback);
29+
}
30+
31+
interface IOrder {
32+
payment: IPayment;
33+
product: {
34+
name: string;
35+
price: number & tags.Minimum<0>;
36+
quantity: number & tags.Type<"uint32">;
37+
};
38+
customer: {
39+
name: string;
40+
email: string & tags.Format<"email">;
41+
vip: boolean;
42+
};
43+
}
44+
45+
type IPayment =
46+
| { type: "card"; cardNumber: string }
47+
| { type: "bank"; accountNumber: string };
48+
49+
declare class OrderService {
50+
/**
51+
* Create a new order.
52+
*
53+
* @param props Order properties
54+
*/
55+
createOrder(props: { order: IOrder }): { id: string };
56+
}

packages/utils/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,10 @@ Automatically installed as a dependency of `typia`.
2121
| `HttpLlm` | Create LLM controllers from OpenAPI documents and execute function calls |
2222
| `HttpMigration` | HTTP migration utilities |
2323
| `HttpError` | HTTP error type |
24-
| `LlmSchemaConverter` | Convert JSON Schema to LLM schema |
2524
| `OpenApiConverter` | Convert between OpenAPI versions |
26-
| `LlmTypeChecker` | Type checker for LLM schemas |
2725
| `OpenApiTypeChecker` | Type checker for OpenAPI documents |
26+
| `LlmSchemaConverter` | Convert JSON Schema to LLM schema |
27+
| `LlmTypeChecker` | Type checker for LLM schemas |
2828
| `LlmJson` | Parse lenient JSON and format validation errors for LLM-friendly feedback |
2929

3030
## `LlmJson`

website/src/content/docs/llm/_meta.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ export default {
44
application: "application() functions",
55
parameters: "parameters() function",
66
schema: "schema() function",
7+
json: "LlmJson module",
78
mcp: "Model Context Protocol",
89
vercel: "Vercel AI SDK",
910
langchain: "LangChain",

website/src/content/docs/llm/application.mdx

Lines changed: 58 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -115,16 +115,69 @@ Structured output is another feature of LLM. The "structured output" means that
115115

116116
> [💻 Playground Link](/playground/?script=JYWwDg9gTgLgBAbzgSQDIBsQEExncAYwEMZgIA7OAXzgDMoIQ4AiAAQGciQCALCgeghgApuSJhgzANwAoUJFhwYATwlE6DJsxVrpMuHPDR4SAEIAjdlliF0wgMrCoAN0LDqGxiwB0-C1ZsCO0cXNz0DAgp2eHEwAC4UDGxcfGJSCjgAXiVVYCJvdExvWNSSMnIAHn9rUiCHJ1cCYQA+AAoASllI8nYIOwKIAHNW2M6gA)
117117
118+
## Lenient JSON Parsing
119+
120+
<LocalSource
121+
path="examples/src/llm/application-parse.ts"
122+
filename="examples/src/llm/application-parse.ts"
123+
showLineNumbers />
124+
125+
Each `ILlmFunction` includes a `parse()` method for handling LLM JSON outputs. This parser is specifically designed for the messy reality of LLM responses:
126+
127+
**Lenient JSON Features:**
128+
- Unclosed brackets `{`, `[` and strings
129+
- Trailing commas `[1, 2, 3, ]`
130+
- JavaScript-style comments (`//` and `/* */`)
131+
- Unquoted object keys (JavaScript identifier style)
132+
- Incomplete keywords (`tru`, `fal`, `nul`)
133+
- Markdown code block extraction (` ```json ... ``` `)
134+
- Junk text prefix skipping (explanatory text LLMs often add)
135+
136+
**Type Coercion:**
137+
138+
LLMs frequently return wrong types — numbers as strings, booleans as strings, or even double-stringified JSON objects. `func.parse()` automatically coerces these based on the function's parameter schema.
139+
140+
<Callout type="warning">
141+
**0% → 100% Success Rate on Union Types**
142+
143+
`Qwen3.5` model shows 0% success rate when handling union types with double-stringified JSON objects. With `func.parse()` type coercion, the success rate jumps to 100%.
144+
</Callout>
145+
146+
<Callout type="info">
147+
**For Pre-parsed Objects, Use `func.coerce()`**
148+
149+
Some LLM SDKs (Anthropic, Vercel AI, LangChain, MCP) parse JSON internally and return JavaScript objects directly. In these cases, use `func.coerce()` instead of `func.parse()` to fix types without re-parsing.
150+
151+
For more details, see [JSON Utilities](./json).
152+
</Callout>
153+
118154
## Validation Feedback
119155

120-
`typia.llm.application<App>()` embeds [`typia.validate<T>()`](/docs/validators/validate) in every function for automatic argument validation. When validation fails, the error is returned as text content with inline `// ❌` comments at each invalid property:
156+
<LocalSource
157+
path="examples/src/llm/application-validate.ts"
158+
filename="examples/src/llm/application-validate.ts"
159+
showLineNumbers />
160+
161+
`typia.llm.application<App>()` embeds [`typia.validate<T>()`](/docs/validators/validate) in every function for automatic argument validation. When validation fails, use `LlmJson.stringify()` from `@typia/utils` to format errors with inline `// ❌` comments:
121162

122163
```json
123164
{
124-
"name": "John",
125-
"age": "twenty", // ❌ [{"path":"$input.age","expected":"number"}]
126-
"email": "not-an-email", // ❌ [{"path":"$input.email","expected":"string & Format<\"email\">"}]
127-
"hobbies": "reading" // ❌ [{"path":"$input.hobbies","expected":"Array<string>"}]
165+
"order": {
166+
"payment": {
167+
"type": "card",
168+
"cardNumber": 12345678 // ❌ [{"path":"$input.order.payment.cardNumber","expected":"string"}]
169+
},
170+
"product": {
171+
"name": "Laptop",
172+
"price": -100, // ❌ [{"path":"$input.order.product.price","expected":"number & Minimum<0>"}]
173+
"quantity": 2.5 // ❌ [{"path":"$input.order.product.quantity","expected":"number & Type<\"uint32\">"}]
174+
},
175+
"customer": {
176+
"name": "John Doe",
177+
"email": "invalid-email", // ❌ [{"path":"$input.order.customer.email","expected":"string & Format<\"email\">"}]
178+
"vip": "yes" // ❌ [{"path":"$input.order.customer.vip","expected":"boolean"}]
179+
}
180+
}
128181
}
129182
```
130183

0 commit comments

Comments
 (0)