Zod-like Schema DSL
What is AISDKZodAdapter?
Section titled “What is AISDKZodAdapter?”AISDKZodAdapter is a Swift-native DSL (Domain Specific Language) inspired by TypeScript’s Zod library. It provides a cleaner, more ergonomic way to define JSON schemas for AI tools.
Important: This is not a port of Zod itself—it’s a lightweight Swift API designed to improve developer experience (DX) when working with schemas. Under the hood, it converts your Zod-like syntax into standard JSON Schema that the AI SDK uses internally.
Key characteristics:
- ✅ Zod-inspired syntax (not a full Zod port)
- ✅ Pure Swift implementation
- ✅ Converts to JSON Schema internally
- ✅ No TypeScript/JavaScript dependencies
- ✅ Designed specifically for AI SDK tool schemas
Think of it as syntactic sugar over manual JSON Schema construction—same functionality, better ergonomics.
Why Zod DSL?
Section titled “Why Zod DSL?”The Problem with Manual JSON Schema
Section titled “The Problem with Manual JSON Schema”When defining tools with manual JSON Schema, the code becomes verbose and error-prone:
import AISDKProviderUtils
let weatherTool = tool( description: "Get current weather", inputSchema: FlexibleSchema(jsonSchema( .object([ "type": .string("object"), "properties": .object([ "location": .object([ "type": .string("string"), "description": .string("City name") ]), "units": .object([ "type": .string("string"), "enum": .array([.string("celsius"), .string("fahrenheit")]), "description": .string("Temperature units") ]) ]), "required": .array([.string("location")]), "additionalProperties": .bool(false) ]) )), execute: { input, _ in // Execute weather API call return .value(.string("22°C, sunny")) })Problems:
- ❌ Verbose and hard to read
- ❌ Easy to make syntax errors
- ❌ No compile-time validation of schema structure
- ❌ Difficult to maintain
The Solution: Zod-like DSL
Section titled “The Solution: Zod-like DSL”The same schema with Zod DSL is dramatically cleaner:
import AISDKProviderUtilsimport AISDKZodAdapter
let weatherTool = tool( description: "Get current weather", inputSchema: flexibleSchemaFromZod3(z.object([ "location": z.string(), "units": z.optional(z.string()) ])), execute: { input, _ in // Execute weather API call return .value(.string("22°C, sunny")) })Benefits:
- ✅ Clean, readable syntax
- ✅ Less boilerplate
- ✅ Inspired by popular Zod library (familiar to TypeScript developers)
- ✅ Type-safe schema construction
Available Schema Types
Section titled “Available Schema Types”Basic Types
Section titled “Basic Types”import AISDKZodAdapter
// Stringz.string()z.string(minLength: 3, maxLength: 50)z.string(email: true)z.string(url: true)
// Numberz.number()z.number(min: 0, max: 100)z.number(integer: true)
// Booleanz.boolean()Complex Types
Section titled “Complex Types”// Arrayz.array(of: z.string())z.array(of: z.number(), minItems: 1, maxItems: 10)
// Objectz.object([ "name": z.string(), "age": z.number(integer: true), "email": z.string(email: true)])
// Nested objectsz.object([ "user": z.object([ "name": z.string(), "settings": z.object([ "theme": z.string(), "notifications": z.boolean() ]) ])])Modifiers
Section titled “Modifiers”// Optional (can be undefined)z.optional(z.string())
// Nullable (can be null)z.nullable(z.number())
// Union typesz.union([z.string(), z.number()])Complete Examples
Section titled “Complete Examples”Example 1: Simple Calculator Tool
Section titled “Example 1: Simple Calculator Tool”import SwiftAISDKimport OpenAIProviderimport AISDKProviderUtilsimport AISDKZodAdapter
let calculator = tool( description: "Perform basic arithmetic operations", inputSchema: flexibleSchemaFromZod3(z.object([ "operation": z.string(), // "add", "subtract", "multiply", "divide" "a": z.number(), "b": z.number() ])), execute: { input, _ in guard case .object(let obj) = input, case .string(let op) = obj["operation"] ?? .null, case .number(let a) = obj["a"] ?? .null, case .number(let b) = obj["b"] ?? .null else { return .value(.string("Invalid input")) }
let result: Double switch op { case "add": result = a + b case "subtract": result = a - b case "multiply": result = a * b case "divide": result = b != 0 ? a / b : 0 default: return .value(.string("Unknown operation")) }
return .value(.number(result)) })
// Use the toollet result = try await generateText( model: openai("gpt-4o"), tools: ["calculator": calculator], prompt: "What is 234 multiplied by 89? Use the calculator tool.")
print(result.text)// Output: The result of 234 multiplied by 89 is 20,826.Example 2: Database Query Tool
Section titled “Example 2: Database Query Tool”let dbQuery = tool( description: "Query the database", inputSchema: flexibleSchemaFromZod3(z.object([ "table": z.string(), "filters": z.optional(z.object([ "field": z.string(), "value": z.string() ])), "limit": z.optional(z.number(min: 1, max: 100, integer: true)) ])), execute: { input, _ in guard case .object(let obj) = input, case .string(let table) = obj["table"] ?? .null else { return .value(.string("Table name required")) }
// Execute database query // ... (your DB logic here)
return .value(.object([ "count": .number(42), "rows": .array([ .object(["id": .number(1), "name": .string("Alice")]), .object(["id": .number(2), "name": .string("Bob")]) ]) ])) })Example 3: Email Sender Tool
Section titled “Example 3: Email Sender Tool”let sendEmail = tool( description: "Send an email", inputSchema: flexibleSchemaFromZod3(z.object([ "to": z.string(email: true), "subject": z.string(minLength: 1, maxLength: 200), "body": z.string(), "attachments": z.optional(z.array(of: z.string(url: true))) ])), execute: { input, _ in guard case .object(let obj) = input, case .string(let to) = obj["to"] ?? .null, case .string(let subject) = obj["subject"] ?? .null, case .string(let body) = obj["body"] ?? .null else { return .value(.string("Missing required fields")) }
// Send email // ... (your email service logic here)
return .value(.object([ "status": .string("sent"), "messageId": .string("msg_123abc") ])) })Converting to FlexibleSchema
Section titled “Converting to FlexibleSchema”The flexibleSchemaFromZod3() function converts ZodSchema to FlexibleSchema:
// Basic conversionlet schema: FlexibleSchema<JSONValue> = flexibleSchemaFromZod3( z.object([ "name": z.string(), "age": z.number() ]))
// With options (advanced)let schemaWithOptions = flexibleSchemaFromZod3( z.object([ "data": z.array(of: z.string()) ]), options: .partial(PartialOptions( name: "MySchema", refStrategy: .none, nameStrategy: .ref )))Migration Guide
Section titled “Migration Guide”From Manual JSON Schema
Section titled “From Manual JSON Schema”Before:
inputSchema: FlexibleSchema(jsonSchema( .object([ "type": .string("object"), "properties": .object([ "name": .object([ "type": .string("string"), "minLength": .number(1) ]), "age": .object([ "type": .string("number"), "minimum": .number(0) ]) ]), "required": .array([.string("name")]) ])))After:
inputSchema: flexibleSchemaFromZod3(z.object([ "name": z.string(minLength: 1), "age": z.number(min: 0)]))Common Patterns
Section titled “Common Patterns”| Manual JSON Schema | Zod DSL |
|---|---|
"type": .string("string") | z.string() |
"type": .string("number") | z.number() |
"type": .string("boolean") | z.boolean() |
"type": .string("array"), "items": ... | z.array(of: ...) |
"type": .string("object"), "properties": ... | z.object([...]) |
| Optional property | z.optional(...) |
"minimum": .number(0) | z.number(min: 0) |
"minLength": .number(3) | z.string(minLength: 3) |
Best Practices
Section titled “Best Practices”1. Use Descriptive Property Names
Section titled “1. Use Descriptive Property Names”// ❌ Badz.object([ "d": z.string(), "t": z.number()])
// ✅ Goodz.object([ "destination": z.string(), "travelTime": z.number()])2. Add Validation Constraints
Section titled “2. Add Validation Constraints”// ✅ Good - validates inputz.object([ "email": z.string(email: true), "age": z.number(min: 0, max: 150, integer: true), "username": z.string(minLength: 3, maxLength: 20)])3. Use Optional for Non-Required Fields
Section titled “3. Use Optional for Non-Required Fields”z.object([ "required": z.string(), "optional": z.optional(z.string())])4. Nest Objects for Complex Data
Section titled “4. Nest Objects for Complex Data”z.object([ "user": z.object([ "profile": z.object([ "name": z.string(), "bio": z.optional(z.string()) ]), "preferences": z.object([ "theme": z.string(), "language": z.string() ]) ])])Comparison: Manual vs Zod
Section titled “Comparison: Manual vs Zod”Weather Tool Example
Section titled “Weather Tool Example”Manual JSON Schema (verbose):
inputSchema: FlexibleSchema(jsonSchema( .object([ "type": .string("object"), "properties": .object([ "location": .object([ "type": .string("string"), "description": .string("City name") ]), "units": .object([ "type": .string("string"), "enum": .array([.string("celsius"), .string("fahrenheit")]) ]) ]), "required": .array([.string("location")]), "additionalProperties": .bool(false) ])))Zod DSL (clean):
inputSchema: flexibleSchemaFromZod3(z.object([ "location": z.string(), "units": z.optional(z.string())]))Lines of code: 19 → 3 (84% reduction!)
See Also
Section titled “See Also”- Tools and Tool Calling - Complete guide to using tools
- Getting Started - Quick start examples with tools
- Generating Structured Data - Using schemas for structured output
Tip: Always prefer Zod DSL over manual JSON Schema for better readability and maintainability. The two approaches are functionally equivalent, but Zod syntax is much cleaner.