Skip to content
Learn Agentic AI
Learn Agentic AI10 min read2 views

Zod for AI Agent Validation: Schema-First Type-Safe Tool Definitions

Master Zod for building type-safe AI agent tools. Learn how to define schemas for tool inputs, validate LLM-generated arguments, parse structured outputs, and handle validation errors gracefully in TypeScript agent applications.

Why Zod Is Essential for AI Agents

LLMs generate structured output that your code must parse and execute. The model might return a function call with arguments like {"city": "San Francisco", "units": "celsius"} — or it might hallucinate malformed JSON, wrong field names, or invalid types. Without validation, these errors propagate silently into your tool execution layer.

Zod solves this by providing a single schema definition that serves as both runtime validator and TypeScript type generator. Define a schema once, and you get compile-time type checking, runtime validation, and JSON Schema generation for the LLM — all from the same source of truth.

Zod Basics for Tool Schemas

Install Zod:

flowchart TD
    START["Zod for AI Agent Validation: Schema-First Type-Sa…"] --> A
    A["Why Zod Is Essential for AI Agents"]
    A --> B
    B["Zod Basics for Tool Schemas"]
    B --> C
    C["Validating LLM-Generated Arguments"]
    C --> D
    D["Generating JSON Schema for LLM Tool Def…"]
    D --> E
    E["Structured Output Parsing"]
    E --> F
    F["Complex Schema Patterns for Agents"]
    F --> G
    G["Error Recovery Pattern"]
    G --> H
    H["FAQ"]
    H --> DONE["Key Takeaways"]
    style START fill:#4f46e5,stroke:#4338ca,color:#fff
    style DONE fill:#059669,stroke:#047857,color:#fff
npm install zod

Define a schema and extract its TypeScript type:

import { z } from "zod";

const WeatherInputSchema = z.object({
  city: z.string().min(1).describe("City name for weather lookup"),
  units: z
    .enum(["celsius", "fahrenheit"])
    .default("celsius")
    .describe("Temperature unit"),
  includeForcast: z
    .boolean()
    .optional()
    .describe("Whether to include a 5-day forecast"),
});

// Extract the TypeScript type automatically
type WeatherInput = z.infer<typeof WeatherInputSchema>;
// Result: { city: string; units: "celsius" | "fahrenheit"; includeForcast?: boolean }

The .describe() calls are critical for AI agents. These descriptions are included in the JSON Schema sent to the LLM, helping the model understand what each parameter expects.

Validating LLM-Generated Arguments

When the LLM returns tool call arguments, validate them before execution:

function executeToolCall(name: string, rawArgs: string) {
  const schemas: Record<string, z.ZodSchema> = {
    get_weather: WeatherInputSchema,
    search_docs: SearchInputSchema,
    create_ticket: TicketInputSchema,
  };

  const schema = schemas[name];
  if (!schema) {
    return { error: `Unknown tool: ${name}` };
  }

  const parsed = schema.safeParse(JSON.parse(rawArgs));

  if (!parsed.success) {
    // Return structured error to the LLM so it can retry
    return {
      error: "Invalid arguments",
      details: parsed.error.issues.map((issue) => ({
        path: issue.path.join("."),
        message: issue.message,
      })),
    };
  }

  // parsed.data is fully typed here
  return toolHandlers[name](parsed.data);
}

Using safeParse instead of parse prevents exceptions from crashing your agent loop. The structured error message can be sent back to the model so it can correct its arguments.

See AI Voice Agents Handle Real Calls

Book a free demo or calculate how much you can save with AI voice automation.

Generating JSON Schema for LLM Tool Definitions

AI providers expect tool parameters as JSON Schema. Zod can generate this automatically using the zod-to-json-schema package:

import { zodToJsonSchema } from "zod-to-json-schema";

const jsonSchema = zodToJsonSchema(WeatherInputSchema, {
  target: "openAi",
});

// Use in OpenAI tool definition
const tool = {
  type: "function" as const,
  function: {
    name: "get_weather",
    description: "Get current weather for a city",
    parameters: jsonSchema,
  },
};

This eliminates the need to manually write and maintain JSON Schema objects. When you update the Zod schema, the tool definition updates automatically.

Structured Output Parsing

Beyond tool inputs, Zod validates structured outputs from the LLM. When you ask the model to return JSON, validate that the response matches your expected format:

const AnalysisOutputSchema = z.object({
  sentiment: z.enum(["positive", "negative", "neutral"]),
  confidence: z.number().min(0).max(1),
  topics: z.array(z.string()).min(1),
  summary: z.string().max(500),
});

async function analyzeText(text: string) {
  const completion = await client.chat.completions.create({
    model: "gpt-4o",
    messages: [
      {
        role: "system",
        content: "Analyze the following text and return JSON with sentiment, confidence, topics, and summary.",
      },
      { role: "user", content: text },
    ],
    response_format: { type: "json_object" },
  });

  const raw = JSON.parse(completion.choices[0].message.content ?? "{}");
  const result = AnalysisOutputSchema.parse(raw);

  return result; // Fully typed: { sentiment, confidence, topics, summary }
}

Complex Schema Patterns for Agents

Real agent tools often need sophisticated schemas. Zod handles unions, recursive types, and transformations:

// Union types for different action kinds
const AgentActionSchema = z.discriminatedUnion("type", [
  z.object({
    type: z.literal("search"),
    query: z.string(),
    filters: z.record(z.string()).optional(),
  }),
  z.object({
    type: z.literal("email"),
    to: z.string().email(),
    subject: z.string(),
    body: z.string(),
  }),
  z.object({
    type: z.literal("schedule"),
    title: z.string(),
    dateTime: z.string().datetime(),
    attendees: z.array(z.string().email()),
  }),
]);

// Transforms to coerce LLM output
const DateRangeSchema = z.object({
  start: z.string().transform((s) => new Date(s)),
  end: z.string().transform((s) => new Date(s)),
}).refine(
  (data) => data.end > data.start,
  { message: "End date must be after start date" }
);

Error Recovery Pattern

When validation fails, feed the error back to the LLM for self-correction:

async function executeWithRetry(
  client: OpenAI,
  messages: ChatCompletionMessageParam[],
  schema: z.ZodSchema,
  maxRetries = 2
): Promise<z.infer<typeof schema>> {
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
    const completion = await client.chat.completions.create({
      model: "gpt-4o",
      messages,
      response_format: { type: "json_object" },
    });

    const raw = JSON.parse(completion.choices[0].message.content ?? "{}");
    const result = schema.safeParse(raw);

    if (result.success) return result.data;

    // Append error as context for retry
    messages.push(
      { role: "assistant", content: completion.choices[0].message.content ?? "" },
      {
        role: "user",
        content: `Your response did not match the expected format. Errors: ${JSON.stringify(result.error.issues)}. Please try again.`,
      }
    );
  }

  throw new Error("Failed to get valid structured output after retries");
}

FAQ

Does Zod add significant runtime overhead?

No. Zod validation is extremely fast for the small payloads typical of tool call arguments (microseconds). The overhead is negligible compared to the LLM API latency, which is measured in seconds.

Should I use Zod or JSON Schema directly for tool definitions?

Use Zod as your single source of truth and generate JSON Schema from it. This eliminates the risk of your TypeScript types drifting out of sync with the schema sent to the LLM. The zod-to-json-schema package handles the conversion reliably.

How do I handle optional fields that the LLM might omit?

Use .optional() or .default() in your Zod schema. The .default() approach is usually better for agent tools because it ensures your execute function always receives a complete object without needing null checks.


#Zod #TypeScript #Validation #Schema #AIAgents #TypeSafety #AgenticAI #LearnAI #AIEngineering

Share
C

Written by

CallSphere Team

Expert insights on AI voice agents and customer communication automation.

Try CallSphere AI Voice Agents

See how AI voice agents work for your industry. Live demo available -- no signup required.

Related Articles You May Like

Use Cases

Automating Client Document Collection: How AI Agents Chase Missing Tax Documents and Reduce Filing Delays

See how AI agents automate tax document collection — chasing missing W-2s, 1099s, and receipts via calls and texts to eliminate the #1 CPA bottleneck.

Learn Agentic AI

API Design for AI Agent Tool Functions: Best Practices and Anti-Patterns

How to design tool functions that LLMs can use effectively with clear naming, enum parameters, structured responses, informative error messages, and documentation.

Learn Agentic AI

AI Agents for IT Helpdesk: L1 Automation, Ticket Routing, and Knowledge Base Integration

Build IT helpdesk AI agents with multi-agent architecture for triage, device, network, and security issues. RAG-powered knowledge base, automated ticket creation, routing, and escalation.

Learn Agentic AI

Computer Use in GPT-5.4: Building AI Agents That Navigate Desktop Applications

Technical guide to GPT-5.4's computer use capabilities for building AI agents that interact with desktop UIs, browser automation, and real-world application workflows.

Learn Agentic AI

Prompt Engineering for AI Agents: System Prompts, Tool Descriptions, and Few-Shot Patterns

Agent-specific prompt engineering techniques: crafting effective system prompts, writing clear tool descriptions for function calling, and few-shot examples that improve complex task performance.

Learn Agentic AI

Google Cloud AI Agent Trends Report 2026: Key Findings and Developer Implications

Analysis of Google Cloud's 2026 AI agent trends report covering Gemini-powered agents, Google ADK, Vertex AI agent builder, and enterprise adoption patterns.