Free-text parsing is the biggest source of flaky LLM features. Use the provider's structured output: you supply a JSON Schema, the decoder is constrained so invalid tokens cannot be emitted.
ts
import { z } from "zod";
const Invoice = z.object({
vendor: z.string(),
total_cents: z.number().int(),
currency: z.enum(["USD", "EUR", "INR"]),
line_items: z.array(z.object({ label: z.string(), cents: z.number().int() })),
});
const res = await client.chat.completions.create({
model: "gpt-5-mini",
messages,
response_format: { type: "json_schema", json_schema: { name: "invoice", schema: toJsonSchema(Invoice), strict: true } },
});
const invoice = Invoice.parse(JSON.parse(res.choices[0].message.content!));Rules that keep this robust: - Validate again on your side (`Zod`/`Pydantic`) — schema-constrained still means *shape*, not *truth*. - Use integers for money; never floats. - Keep schemas flat and small; deeply nested unions raise refusal and latency.
Gotcha: `strict` schema modes typically forbid optional/unspecified keys and require `additionalProperties: false`. Model your optionality with explicit nullable fields instead.