Type-safe LLM outputs with Zod: stop guessing what the model returns. A developer describes how to enforce type-safe LLM outputs using Zod schemas, preventing runtime errors from unexpected model responses. The approach covers defining schemas, integrating with Vercel AI SDK and Anthropic SDK, and implementing retry loops for parse failures. I shipped a classifier to production in January. The prompt asked for JSON with a single category field. For three weeks it worked fine. Then the model started returning {"category":"bug","explanation":"this looks like a crash"} and the consumer threw a runtime error because it only expected one key. No schema change, no deploy. The model just decided to be helpful. Zod plus a bit of discipline around the parse step closes that gap. This tutorial walks through defining schemas for LLM output shapes, using them with the Vercel AI SDK and the raw Anthropic SDK, and building a retry loop that handles the cases where the model still gets it wrong. | Step | What | Why | |---|---|---| | Define Zod schema | Describe the shape you want | Single source of truth for your types | | Use generateText with Output.object | Vercel AI SDK path | Schema-enforced, provider-agnostic | | Use tool use with tool choice | Anthropic SDK path | Forces structured output without extra wrappers | | Parse and retry on failure | ZodError catches drift | Recovers without crashing callers | Most LLM tutorials show JSON.parse response and call it a day. The problem is that the model never agreed to your schema. Ask it to return {"category": "bug"} and it might return: {"category": "bug"} correct {"Category": "Bug"} wrong casing {"category": "bug", "confidence": 0.9} extra field {"error": "I cannot classify this"} helpful, but not your schema Without a parse step that actually validates the shape, every one of those paths silently corrupts downstream data. The fix is three lines of Zod plus one .safeParse call. Every technique in this article builds on that pattern, whether you use the Vercel AI SDK, the raw Anthropic SDK, or both. js import { z } from "zod"; const ClassifyResult = z.object { category: z.enum "bug", "feature", "question" , } ; type ClassifyResult = z.infer