Skip to content

Schema Builder

The schema builder provides a fluent API for defining JSON Schema objects. It is used in two places:

  1. Structured output — tell the model to return validated JSON
  2. Tool input schema — define what parameters a tool accepts

Basic usage

ts
import { agent } from '@daedalus-ai-dev/ai-sdk';

const response = await agent({
  instructions: 'Extract product details from the text.',
  schema: (s) => ({
    name:     s.string().required(),
    price:    s.number().min(0).required(),
    inStock:  s.boolean().required(),
    tags:     s.array().items(s.string().toSchema()),
  }),
}).prompt<{ name: string; price: number; inStock: boolean; tags: string[] }>(
  'Product: "Blue Widget — $12.99, available, tags: widget, blue"'
);

console.log(response.structured);
// { name: 'Blue Widget', price: 12.99, inStock: true, tags: ['widget', 'blue'] }

Property types

s.string()

ts
s.string()
  .description('Human-readable description for the model')
  .minLength(1)
  .maxLength(500)
  .pattern('^[a-z]+$')  // regex pattern
  .required()

s.integer()

ts
s.integer()
  .description('A whole number')
  .min(0)
  .max(100)
  .required()

s.number()

ts
s.number()
  .description('A decimal number')
  .min(0.0)
  .max(1.0)
  .required()

s.boolean()

ts
s.boolean()
  .description('true or false')
  .required()

s.enum(values)

ts
s.enum(['pending', 'active', 'cancelled'])
  .description('Current status of the order')
  .required()

s.array()

ts
s.array()
  .description('List of tags')
  .items(s.string().toSchema())  // Note: call .toSchema() on nested builders
  .required()

Nested builders

When passing a builder as .items(), call .toSchema() on it to convert it to a plain JSON Schema object. This is because .items() accepts a JsonSchemaProperty, not a builder.

ts
// ✅ Correct
s.array().items(s.string().toSchema())

// ❌ Wrong — passes a builder object, not a schema
s.array().items(s.string())

The required() modifier

Call .required() on any property to mark it as required in the output schema. Properties without .required() are optional.

ts
schema: (s) => ({
  title:    s.string().required(),  // included in "required" array
  subtitle: s.string(),             // optional — model may omit it
})

Descriptions

Add .description() to give the model context about what each field means. This significantly improves extraction accuracy for structured output.

ts
schema: (s) => ({
  sentiment: s.enum(['positive', 'neutral', 'negative'])
    .description('Overall emotional tone of the text')
    .required(),
  confidence: s.number().min(0).max(1)
    .description('Model confidence from 0.0 (uncertain) to 1.0 (certain)')
    .required(),
})

Using buildSchema() directly

You can also call buildSchema() to get a raw JsonSchemaObject:

ts
import { buildSchema } from '@daedalus-ai-dev/ai-sdk';

const schema = buildSchema((s) => ({
  name: s.string().required(),
  age:  s.integer().min(0).required(),
}));

console.log(JSON.stringify(schema, null, 2));
// {
//   "type": "object",
//   "properties": {
//     "name": { "type": "string" },
//     "age":  { "type": "integer", "minimum": 0 }
//   },
//   "required": ["name", "age"],
//   "additionalProperties": false
// }

TypeScript generics

Pass the expected output type as a generic to .prompt<T>() to get full type safety:

ts
type Review = {
  score: number;
  approved: boolean;
  issues: string[];
};

const response = await agent({
  instructions: 'Review the content.',
  schema: (s) => ({
    score:    s.integer().min(1).max(10).required(),
    approved: s.boolean().required(),
    issues:   s.array().items(s.string().toSchema()).required(),
  }),
}).prompt<Review>('Review this article...');

// response.structured is typed as Review
const { score, approved, issues } = response.structured;

TIP

The generic type and the schema definition are independent — TypeScript does not validate that they match at compile time. Ensure they stay in sync, or use a library like Zod to derive both from a single source of truth.

Released under the MIT License.