# How Ax Generates JSON Schema from Signatures for Structured Output

> Ax generates JSON Schema from signatures for structured LLM output using declarative field definitions and validation constraints. Learn how Ax delivers precise structured data.

- Repository: [Ax/ax](https://github.com/ax-llm/ax)
- Tags: how-to-guide
- Published: 2026-02-25

---

**Ax generates JSON Schema from signatures by parsing declarative field definitions into `AxField` objects, recursively converting them to JSON Schema types via `fieldToSchema`, and enhancing descriptions with validation constraints to guide LLM structured output.**

The Ax framework (ax-llm/ax) enables type-safe structured output from large language models by transforming developer-friendly signatures into rigorous JSON Schemas. This process bridges the gap between human-readable field definitions and the strict schema requirements of function-calling LLM APIs.

## Parsing Signatures into AxField Objects

The journey from signature to schema begins with parsing the declarative definition into structured metadata objects.

### The parseSignature Function

In [`src/ax/dsp/parser.ts`](https://github.com/ax-llm/ax/blob/main/src/ax/dsp/parser.ts), the `parseSignature` function tokenizes signature strings—whether provided as raw text, an `AxSignature` instance, or a fluent builder (`f()`). It extracts field names, descriptions, type annotations, and modifiers.

For example, a signature like `userQuery:string "Question" -> answer:string, confidence:number` is parsed into separate input and output `AxField` arrays.

### AxField Structure and Modifiers

Each `AxField` object captured in [`src/ax/dsp/sig.ts`](https://github.com/ax-llm/ax/blob/main/src/ax/dsp/sig.ts) contains:

- **name**: The field identifier
- **description**: Human-readable context for the LLM
- **type**: An `AxFieldType` enum (string, number, boolean, array, object, class, date, etc.)
- **Modifiers**: `isOptional`, `isInternal` (excluded from schemas), and validation constraints (`minLength`, `maximum`, `pattern`, `format`)

These modifiers directly influence the final JSON Schema constraints.

## Converting AxField to JSON Schema

Once parsed, the `AxField` objects undergo recursive transformation into JSON Schema definitions.

### The toJSONSchema Method

The `AxSignature` class in [`src/ax/dsp/sig.ts`](https://github.com/ax-llm/ax/blob/main/src/ax/dsp/sig.ts) (lines 39-44) exposes `toJSONSchema()`, which concatenates input and output fields and delegates to `toJsonSchema` in [`src/ax/dsp/jsonSchema.ts`](https://github.com/ax-llm/ax/blob/main/src/ax/dsp/jsonSchema.ts).

For generators requiring input validation, `toInputJSONSchema()` filters for input fields only, producing schemas suitable for request validation.

### Recursive Schema Building with fieldToSchema

At line 95 of [`src/ax/dsp/jsonSchema.ts`](https://github.com/ax-llm/ax/blob/main/src/ax/dsp/jsonSchema.ts), the `toJsonSchema` function initializes a root object schema and iterates over fields. For each field, it calls `fieldToSchema`, a recursive function handling:

- **Primitives**: Maps `string`, `number`, `boolean` via `mapAxTypeToJsonSchemaType`
- **Arrays**: Creates `items` schemas, recursing into nested objects when detected
- **Objects**: Builds nested `properties` objects, marking required fields and validating that media types (image, audio, file) never appear inside nested structures (lines 34-45)
- **Classifications**: Translates `class` types into string enums

### Type Mapping and Validation Constraints

The `mapAxTypeToJsonSchemaType` function handles Ax-specific types:

- `string` → `"type": "string"`
- `number` → `"type": "number"`
- `boolean` → `"type": "boolean"`
- `date` → `"type": "string", "format": "date"`
- `email` → `"type": "string", "format": "email"`
- `class` → `"type": "string"` with `enum` array

Validation constraints attach directly to schema properties:
- `minLength`/`maxLength` for strings
- `minimum`/`maximum` for numbers
- `pattern` for regex validation
- `format` for semantic types (email, date, uri)

### Description Enhancement for LLM Guidance

Before finalizing the schema, `enhanceDescriptionWithValidation` (lines 5-92 in [`src/ax/dsp/jsonSchema.ts`](https://github.com/ax-llm/ax/blob/main/src/ax/dsp/jsonSchema.ts)) augments field descriptions with human-readable validation rules. For example, a string field with `minLength: 5` receives the appended text: "Minimum length: 5 characters."

This enrichment ensures the LLM receives explicit guidance about constraints without requiring separate documentation.

## Schema Validation and Generator Integration

The generated schema undergoes final validation before reaching the LLM provider.

### validateJSONSchema Guards

The `validateJSONSchema` function (lines 44-63 in [`src/ax/dsp/jsonSchema.ts`](https://github.com/ax-llm/ax/blob/main/src/ax/dsp/jsonSchema.ts)) performs structural checks:
- Verifies every array defines an `items` schema
- Confirms nested objects contain valid property definitions
- Ensures media types (image, audio, file) only appear at root level

These guards prevent runtime errors when schemas are passed to LLM APIs.

### Integration with AxGenerator

In [`src/ax/dsp/generate.ts`](https://github.com/ax-llm/ax/blob/main/src/ax/dsp/generate.ts), the `AxGenerator` class utilizes the schema at lines 426 and 743. When executing a generation:

1. The signature's `toJSONSchema()` generates the output schema
2. For function-calling providers, this becomes the `parameters` field in the function definition
3. The LLM receives the schema and returns JSON conforming to the exact structure

This integration enables type-safe structured output without manual schema writing.

## Practical Examples

### Building a Signature with Validation Constraints

```typescript
import { f, ax } from '@ax-llm/ax';

// Define a signature with constraints
const signature = f()
  .input('email', f.string().email())
  .input('age', f.number().min(18).max(120))
  .input('tags', f.string('Tag name').array().min(1).max(5))
  .output('profile', f.object({
    name: f.string('Full name').min(3).max(50),
    birthday: f.date(),
    preferences: f.object({
      newsletter: f.boolean(),
      theme: f.class(['light', 'dark'] as const)
    })
  }))
  .build();

// Convert to JSON Schema
const schema = signature.toJSONSchema();

console.log(JSON.stringify(schema, null, 2));

```

**Result (abridged):**

```json
{
  "type": "object",
  "title": "Schema",
  "properties": {
    "email": {
      "type": "string",
      "format": "email",
      "description": "Must be a valid email address format."
    },
    "age": {
      "type": "number",
      "minimum": 18,
      "maximum": 120
    },
    "tags": {
      "type": "array",
      "items": {
        "type": "string",
        "minLength": 1,
        "maxLength": 5
      }
    },
    "profile": {
      "type": "object",
      "properties": {
        "name": {
          "type": "string",
          "minLength": 3,
          "maxLength": 50,
          "description": "Full name. Minimum length: 3 characters, maximum length: 50 characters."
        },
        "birthday": {
          "type": "string",
          "format": "date",
          "description": "Format: YYYY-MM-DD."
        },
        "preferences": {
          "type": "object",
          "properties": {
            "newsletter": { "type": "boolean" },
            "theme": {
              "type": "string",
              "enum": ["light", "dark"]
            }
          },
          "required": ["newsletter", "theme"],
          "additionalProperties": false
        }
      },
      "required": ["name", "birthday", "preferences"],
      "additionalProperties": false
    }
  },
  "required": ["email", "age", "tags", "profile"],
  "additionalProperties": false
}

```

### Using the Schema in a Generator

```typescript
import { ax, agent, s } from '@ax-llm/ax';
import { ai } from '@ax-llm/ax-ai-sdk-provider';

const llm = ai({ name: 'openai', apiKey: process.env.OPENAI_APIKEY! });

const sig = s(`
  userQuery:string "Question from the user"
  -> 
  answer:string "Answer to the question"
  confidence:number "0‑1 confidence score"
`);

const myAgent = agent(sig, {
  name: 'qaAgent',
  description: 'Answer user questions with confidence',
  definition: 'You are a helpful Q&A assistant.',
  ai: llm,
  structured: true,
});

const result = await myAgent.run({ userQuery: 'What is the capital of France?' });
console.log(result);
// → { answer: 'Paris', confidence: 0.99 }

```

The agent automatically calls `sig.toJSONSchema()` and sends that schema to the OpenAI function-calling API, ensuring the model returns an object matching the signature exactly.

## Summary

- **Ax generates JSON Schema from signatures** through a three-stage pipeline: parsing into `AxField` objects, recursive conversion via `fieldToSchema`, and validation enhancement.
- **Parsing occurs in [`src/ax/dsp/parser.ts`](https://github.com/ax-llm/ax/blob/main/src/ax/dsp/parser.ts)**, where `parseSignature` extracts field metadata including types, descriptions, and constraints.
- **Conversion happens in [`src/ax/dsp/jsonSchema.ts`](https://github.com/ax-llm/ax/blob/main/src/ax/dsp/jsonSchema.ts)**, where `toJsonSchema` and `fieldToSchema` map Ax types to JSON Schema primitives, handle nested objects/arrays, and attach validation constraints.
- **Description enhancement** adds human-readable validation rules to field descriptions, giving LLMs explicit guidance on constraints like minimum length or numeric ranges.
- **Integration with `AxGenerator`** in [`src/ax/dsp/generate.ts`](https://github.com/ax-llm/ax/blob/main/src/ax/dsp/generate.ts) passes the final schema to LLM providers as function parameters, enabling type-safe structured output without manual schema authoring.

## Frequently Asked Questions

### How does Ax handle nested objects when generating JSON Schema?

Ax recursively processes nested objects through the `fieldToSchema` function in [`src/ax/dsp/jsonSchema.ts`](https://github.com/ax-llm/ax/blob/main/src/ax/dsp/jsonSchema.ts). When encountering an object type, it builds a nested `properties` definition, marks required fields, and validates that media types (image, audio, file) only appear at the root level. The recursion handles arbitrary nesting depth for complex data structures.

### What validation constraints does Ax support in JSON Schema generation?

Ax supports comprehensive validation constraints including `minLength`/`maxLength` for strings, `minimum`/`maximum` for numbers, `pattern` for regex validation, and `format` for semantic types like email and date. These constraints attach directly to JSON Schema properties, and `enhanceDescriptionWithValidation` appends human-readable explanations to field descriptions to guide the LLM.

### Can I use the generated JSON Schema for input validation as well as output?

Yes. The `AxSignature` class provides both `toJSONSchema()` for output fields and `toInputJSONSchema()` for input fields. When using `ax()` or `new AxGen`, the framework automatically passes the appropriate schema to the LLM provider's function-calling API, ensuring the model receives strict typing for both request parameters and response structures.

### Where does the schema validation happen before sending to the LLM?

Schema validation occurs in `validateJSONSchema` within [`src/ax/dsp/jsonSchema.ts`](https://github.com/ax-llm/ax/blob/main/src/ax/dsp/jsonSchema.ts) (lines 44-63). This function verifies that every array defines an `items` schema, confirms nested objects contain valid property definitions, and ensures media types are correctly positioned. These guards prevent runtime errors when schemas are transmitted to LLM providers like OpenAI or Anthropic.