Skip to main content
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

Why structured output matters

Without structured output, LLM responses are free-form text that requires parsing and error handling. With structured output:
Without SchemaWith Schema
Free-form text responseGuaranteed JSON structure
Manual parsing requiredDirect property access
Inconsistent formatsConsistent field names and types
Runtime type errorsTypeScript type safety

Defining a schema

The output_format_schema field accepts a JSON Schema object that defines your expected response structure:
const job = await api.runModelInference({
  data_plane_id: 'dp_your_data_plane_id',
  model: 'anthropic.claude-sonnet-4.5',
  messages: [
    { role: 'user', 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:
{
  "sentiment": "positive",
  "confidence": 0.95
}

Common schema patterns

Simple object with required fields

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

Arrays of items

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

Nested objects

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

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

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:
// 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', 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:
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:
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

PracticeDescription
Keep schemas focusedDefine only the fields you need
Use enums for known valuesConstrain strings to valid options
Set numeric boundsUse minimum/maximum for numbers
Make fields required explicitlyDon’t rely on defaults
Match TypeScript interfacesKeep schema and types in sync
Add descriptionsHelp the model understand field purpose

Adding descriptions for clarity

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

IssueCauseSolution
Missing required fieldsSchema mismatch or unclear promptVerify required array, improve prompt
Wrong typesSchema not enforcedCheck property types match expectations
Empty arraysModel unsure what to includeAdd minItems or clearer prompt
Enum violationsValue not in enum listVerify enum covers all possibilities