> ## Documentation Index
> Fetch the complete documentation index at: https://docs.narrative.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Using Structured Output

> Define JSON Schema to get predictable, typed responses from model inference

Structured output ensures that model inference returns data in a predictable format you can parse programmatically. By providing a JSON Schema, you constrain the model to return valid JSON matching your specification.

## Prerequisites

* Familiarity with [Running Model Inference](/guides/sdk/running-model-inference)
* Basic understanding of JSON Schema

## Why structured output matters

Without structured output, LLM responses are free-form text that requires parsing and error handling. With structured output:

| Without Schema          | With Schema                      |
| ----------------------- | -------------------------------- |
| Free-form text response | Guaranteed JSON structure        |
| Manual parsing required | Direct property access           |
| Inconsistent formats    | Consistent field names and types |
| Runtime type errors     | TypeScript type safety           |

## Defining a schema

The `output_format_schema` field accepts a [JSON Schema](https://json-schema.org/) object that defines your expected response structure:

```typescript theme={null}
const job = await api.runModelInference({
  data_plane_id: 'dp_your_data_plane_id',
  model: 'anthropic.claude-sonnet-4.5',
  messages: [
    {
      role: 'user',
      content: [{ type: 'text', text: 'Classify this text: "Great product, fast shipping!"' }]
    }
  ],
  inference_config: {
    output_format_schema: {
      type: 'object',
      properties: {
        sentiment: {
          type: 'string',
          enum: ['positive', 'negative', 'neutral']
        },
        confidence: {
          type: 'number',
          minimum: 0,
          maximum: 1
        }
      },
      required: ['sentiment', 'confidence']
    }
  }
});
```

The model will return:

```json theme={null}
{
  "sentiment": "positive",
  "confidence": 0.95
}
```

## Handling schema-validation misses

After the model responds, the platform validates the payload against
`output_format_schema`. If validation fails, the job still completes — the
non-conforming payload is preserved on `failed_structured_output` instead of
`structured_output`, so downstream steps run and you can decide how to handle it:

```typescript theme={null}
const result = completedJob.result;

if (result.failed_structured_output) {
  // The model returned JSON that did not match the schema. Log or retry.
  console.warn(
    'Schema validation failed:',
    result.failed_structured_output.message
  );
  console.warn('Raw model output:', result.failed_structured_output.raw_output);
  return;
}

// Safe to consume the typed output.
const output = result.structured_output;
```

`structured_output` and `failed_structured_output` are mutually exclusive — at
most one is populated on a completed job. Always check
`failed_structured_output` before reading `structured_output` in code paths
where a schema miss is possible.

## Common schema patterns

### Simple object with required fields

```typescript theme={null}
const schema = {
  type: 'object',
  properties: {
    title: { type: 'string' },
    description: { type: 'string' },
    priority: { type: 'integer', minimum: 1, maximum: 5 }
  },
  required: ['title', 'description']
};
```

### Arrays of items

```typescript theme={null}
const schema = {
  type: 'object',
  properties: {
    categories: {
      type: 'array',
      items: { type: 'string' },
      minItems: 1,
      maxItems: 5
    }
  },
  required: ['categories']
};
```

### Nested objects

```typescript theme={null}
const schema = {
  type: 'object',
  properties: {
    analysis: {
      type: 'object',
      properties: {
        summary: { type: 'string' },
        key_points: {
          type: 'array',
          items: { type: 'string' }
        }
      },
      required: ['summary', 'key_points']
    },
    metadata: {
      type: 'object',
      properties: {
        word_count: { type: 'integer' },
        language: { type: 'string' }
      }
    }
  },
  required: ['analysis']
};
```

### Enum constraints

```typescript theme={null}
const schema = {
  type: 'object',
  properties: {
    category: {
      type: 'string',
      enum: ['retail', 'finance', 'healthcare', 'technology', 'other']
    },
    data_type: {
      type: 'string',
      enum: ['pii', 'aggregated', 'anonymous']
    }
  },
  required: ['category', 'data_type']
};
```

### Array of typed objects

```typescript theme={null}
const schema = {
  type: 'object',
  properties: {
    entities: {
      type: 'array',
      items: {
        type: 'object',
        properties: {
          name: { type: 'string' },
          type: { type: 'string', enum: ['person', 'organization', 'location'] },
          confidence: { type: 'number' }
        },
        required: ['name', 'type']
      }
    }
  },
  required: ['entities']
};
```

## TypeScript integration

Define TypeScript interfaces that match your schema for type-safe access:

```typescript theme={null}
// Define the interface matching your schema
interface ClassificationResult {
  category: 'retail' | 'finance' | 'healthcare' | 'technology' | 'other';
  confidence: number;
  reasoning: string;
  tags: string[];
}

// Define the schema (must match the interface)
const schema = {
  type: 'object',
  properties: {
    category: {
      type: 'string',
      enum: ['retail', 'finance', 'healthcare', 'technology', 'other']
    },
    confidence: { type: 'number', minimum: 0, maximum: 1 },
    reasoning: { type: 'string' },
    tags: { type: 'array', items: { type: 'string' } }
  },
  required: ['category', 'confidence', 'reasoning', 'tags']
};

// Submit the request
const job = await api.runModelInference({
  data_plane_id: 'dp_your_data_plane_id',
  model: 'anthropic.claude-sonnet-4.5',
  messages: [
    { role: 'user', content: [{ type: 'text', text: 'Classify this dataset...' }] }
  ],
  inference_config: { output_format_schema: schema }
});

// After polling for completion, cast the result
import { ModelInferenceRunResult } from '@narrative.io/data-collaboration-sdk-ts';

const result = completedJob.result as ModelInferenceRunResult<ClassificationResult>;

// Now you have full type safety
const category: string = result.structured_output.category;  // 'retail' | 'finance' | ...
const confidence: number = result.structured_output.confidence;
const tags: string[] = result.structured_output.tags;
```

## Handling optional fields

Use `required` array to specify which fields must be present:

```typescript theme={null}
const schema = {
  type: 'object',
  properties: {
    title: { type: 'string' },           // Required
    subtitle: { type: 'string' },        // Optional
    author: { type: 'string' },          // Optional
    published_date: { type: 'string' }   // Required
  },
  required: ['title', 'published_date']  // Only these are required
};

// TypeScript interface
interface ArticleInfo {
  title: string;
  subtitle?: string;
  author?: string;
  published_date: string;
}
```

## Validating responses

While the model is constrained to the schema, you may want additional runtime validation:

```typescript theme={null}
function validateResult<T>(
  result: ModelInferenceRunResult<T>,
  validate: (output: T) => boolean
): T | null {
  const output = result.structured_output;

  if (!validate(output)) {
    console.error('Validation failed for output:', output);
    return null;
  }

  return output;
}

// Usage
const validated = validateResult(result, (output: ClassificationResult) => {
  return output.confidence >= 0.5 && output.tags.length > 0;
});

if (validated) {
  console.log('Valid classification:', validated.category);
}
```

## Best practices

| Practice                        | Description                             |
| ------------------------------- | --------------------------------------- |
| Keep schemas focused            | Define only the fields you need         |
| Use enums for known values      | Constrain strings to valid options      |
| Set numeric bounds              | Use `minimum`/`maximum` for numbers     |
| Make fields required explicitly | Don't rely on defaults                  |
| Match TypeScript interfaces     | Keep schema and types in sync           |
| Add descriptions                | Help the model understand field purpose |

### Adding descriptions for clarity

```typescript theme={null}
const schema = {
  type: 'object',
  properties: {
    sentiment: {
      type: 'string',
      enum: ['positive', 'negative', 'neutral'],
      description: 'Overall sentiment of the text'
    },
    confidence: {
      type: 'number',
      minimum: 0,
      maximum: 1,
      description: 'Confidence score from 0 (uncertain) to 1 (certain)'
    },
    key_phrases: {
      type: 'array',
      items: { type: 'string' },
      description: 'Most important phrases that influenced the sentiment'
    }
  },
  required: ['sentiment', 'confidence', 'key_phrases']
};
```

## Troubleshooting

| Issue                   | Cause                             | Solution                                |
| ----------------------- | --------------------------------- | --------------------------------------- |
| Missing required fields | Schema mismatch or unclear prompt | Verify required array, improve prompt   |
| Wrong types             | Schema not enforced               | Check property types match expectations |
| Empty arrays            | Model unsure what to include      | Add `minItems` or clearer prompt        |
| Enum violations         | Value not in enum list            | Verify enum covers all possibilities    |

## Related content

<CardGroup cols={2}>
  <Card title="JSON Schema Reference" icon="book" href="/reference/model-inference/json-schema-reference">
    Supported JSON Schema features
  </Card>

  <Card title="Running Model Inference" icon="play" href="/guides/sdk/running-model-inference">
    Complete inference guide
  </Card>

  <Card title="Model Inference API" icon="code" href="/reference/sdks/typescript/model-inference-api">
    API reference with all types
  </Card>

  <Card title="Structured Output Concepts" icon="lightbulb" href="/concepts/model-inference/structured-output">
    Why structured output matters
  </Card>
</CardGroup>
