Skip to content

πŸ“‹ Structured Output Made Simple

Your AI is incredible at generating creative responses, but what if you need perfectly structured, predictable data every time? πŸ“‹

Right now your AI returns natural language text that you have to parse and hope is in the right format. But what if it could return guaranteed JSON data that matches exact schemas you define?

What we’re building: AI that returns validated, structured data in exact formats - perfect for APIs, databases, and reliable data processing!


Current limitation: Your AI returns free-form text that’s hard to parse reliably
New superpower: Your AI returns validated JSON that matches exact schemas!

Before (Unpredictable Output):

User: "Extract info about John Doe, age 30, email john@example.com"
AI: "John Doe seems to be 30 years old and his email might be john@example.com"

After (Structured Output):

User: "Extract info about John Doe, age 30, email john@example.com"
AI: {
"name": "John Doe",
"age": 30,
"email": "john@example.com",
"validation": "passed"
}

The magic: Your AI becomes a reliable data processor that guarantees consistent formats!

Real-world scenarios your structured AI will handle:

  • πŸ“‹ Form processing - Extract structured data from documents and text
  • πŸ—„οΈ Database operations - Generate records that match database schemas
  • πŸ“Š API responses - Return consistent JSON for frontend applications
  • πŸ“ˆ Data analysis - Create standardized reports and analytics
  • πŸ”„ Workflow automation - Trigger actions based on structured data

Unpredictable AI vs. Structured AI:

❌ Unpredictable: "John seems to be around 30 years old"
βœ… Structured: {"age": 30, "confidence": 0.95}
❌ Unpredictable: "The task is probably high priority"
βœ… Structured: {"priority": "high", "category": "urgent"}
❌ Unpredictable: "It looks like an error occurred"
βœ… Structured: {"status": "error", "code": 404, "message": "Not found"}

Before we write any code, let’s understand what structured output actually means and why it transforms your AI applications.

Structured output is like giving your AI a strict form to fill out instead of letting it write freely. Your AI must return data that matches exact schemas you define, with validation ensuring every field is correct.

Real-world analogy: It’s like the difference between asking someone to β€œtell me about this person” (unpredictable response) versus giving them a form with specific fields: β€œName: ___, Age: ___, Email: ___” (structured response).

You already have powerful AI capabilities, but structured output is different:

πŸ’¬ Regular Chat - AI returns natural language text (unpredictable format)
πŸ“‹ Structured Output - AI returns validated JSON data (guaranteed format)

🎀 Voice/Audio - AI processes and generates audio (specific format but not data)
πŸ“‹ Structured Output - AI returns data structures (guaranteed schemas)

The key difference: Structured output guarantees your AI responses match exact data schemas, making them perfect for programmatic use, database storage, and API integration.

Your structured output will use Zod for schema validation:

πŸ›‘οΈ Zod Schema Validation - The Data Format Enforcer

  • Best for: Defining and validating TypeScript-first schemas
  • Strengths: Type safety, runtime validation, detailed error reporting
  • Use cases: API responses, form validation, data processing
  • Think of it as: Your data format quality control system

Key capabilities:

  • Type-safe schemas with automatic TypeScript inference
  • Runtime validation ensuring data matches expected format
  • Error reporting with detailed validation messages
  • Flexible schemas supporting complex nested data structures

πŸ”§ Step 2: Adding Structured Output to Your Backend

Section titled β€œπŸ”§ Step 2: Adding Structured Output to Your Backend”

Let’s add structured output to your existing backend using the same patterns you learned in previous modules. We’ll add new routes to handle structured data generation with Zod validation.

Building on your foundation: You already have a working Node.js server with OpenAI integration. We’re simply adding structured output capabilities to guarantee reliable data formats.

Before writing code, let’s understand what data our structured output system needs to manage:

// 🧠 STRUCTURED OUTPUT STATE CONCEPTS:
// 1. Schema Definitions - Zod schemas defining expected data formats
// 2. Validation Rules - Type checking and constraint enforcement
// 3. Data Generation - AI responses matching exact schemas
// 4. Error Handling - Schema validation failures and recovery
// 5. Format Options - Different output formats and validation levels

Key structured output concepts:

  • Schema Design: Creating Zod schemas that define exact data structures
  • Validation Pipeline: Ensuring AI responses match schema requirements
  • Type Safety: Leveraging TypeScript for compile-time schema checking
  • Error Recovery: Handling validation failures and retry logic

First, add the structured output dependencies to your backend. In your backend folder, run:

Terminal window
npm install zod

What this package does:

  • zod: TypeScript-first schema validation with static type inference

Add these new endpoints to your existing index.js file, right after your web search routes:

import { z } from 'zod';
// πŸ“‹ SCHEMA DEFINITIONS: Predefined Zod schemas for common use cases
// Personal information schema
const PersonSchema = z.object({
name: z.string().min(1, "Name is required"),
age: z.number().int().min(0).max(150),
email: z.string().email("Invalid email format"),
phone: z.string().optional(),
address: z.object({
street: z.string(),
city: z.string(),
state: z.string(),
zipCode: z.string(),
country: z.string()
}).optional()
});
// Event schema
const EventSchema = z.object({
title: z.string().min(1, "Title is required"),
description: z.string().optional(),
startDate: z.string().datetime("Invalid datetime format"),
endDate: z.string().datetime("Invalid datetime format"),
location: z.string().optional(),
attendees: z.array(z.string()).default([]),
category: z.enum(["meeting", "social", "work", "personal", "other"]),
priority: z.enum(["low", "medium", "high"]).default("medium"),
reminders: z.array(z.object({
time: z.string().datetime(),
type: z.enum(["email", "push", "sms"])
})).default([])
});
// Product schema
const ProductSchema = z.object({
name: z.string().min(1, "Product name is required"),
description: z.string(),
price: z.number().positive("Price must be positive"),
category: z.string(),
inStock: z.boolean(),
specifications: z.record(z.string()).optional(),
tags: z.array(z.string()).default([]),
rating: z.number().min(0).max(5).optional(),
reviews: z.array(z.object({
author: z.string(),
rating: z.number().min(1).max(5),
comment: z.string(),
date: z.string().datetime()
})).default([])
});
// Task schema
const TaskSchema = z.object({
title: z.string().min(1, "Task title is required"),
description: z.string().optional(),
status: z.enum(["todo", "in_progress", "completed", "cancelled"]).default("todo"),
priority: z.enum(["low", "medium", "high", "urgent"]).default("medium"),
dueDate: z.string().datetime().optional(),
assignee: z.string().optional(),
tags: z.array(z.string()).default([]),
subtasks: z.array(z.object({
title: z.string(),
completed: z.boolean().default(false)
})).default([]),
estimatedHours: z.number().positive().optional(),
actualHours: z.number().positive().optional()
});
// Analysis schema
const AnalysisSchema = z.object({
summary: z.string().min(10, "Summary must be at least 10 characters"),
keyPoints: z.array(z.string()).min(1, "At least one key point required"),
sentiment: z.enum(["positive", "negative", "neutral", "mixed"]),
confidence: z.number().min(0).max(1),
categories: z.array(z.string()).default([]),
entities: z.array(z.object({
text: z.string(),
type: z.enum(["person", "organization", "location", "date", "other"]),
confidence: z.number().min(0).max(1)
})).default([]),
recommendations: z.array(z.string()).default([])
});
// πŸ”§ HELPER FUNCTIONS: Schema utilities
// Get available schemas
const getAvailableSchemas = () => {
return {
person: {
schema: PersonSchema,
description: "Extract or generate person information with contact details"
},
event: {
schema: EventSchema,
description: "Create calendar events with dates, locations, and attendees"
},
product: {
schema: ProductSchema,
description: "Generate product information with pricing and specifications"
},
task: {
schema: TaskSchema,
description: "Create project tasks with status, priority, and deadlines"
},
analysis: {
schema: AnalysisSchema,
description: "Perform content analysis with sentiment and key insights"
}
};
};
// πŸ“‹ STRUCTURED OUTPUT ENDPOINTS: Add these to your existing server
// Get available schemas
app.get("/api/structured/schemas", (req, res) => {
try {
const schemas = getAvailableSchemas();
const schemaList = Object.keys(schemas).map(key => ({
name: key,
description: schemas[key].description,
example_fields: Object.keys(schemas[key].schema.shape)
}));
res.json({
success: true,
schemas: schemaList,
total_schemas: schemaList.length
});
} catch (error) {
console.error("Schema listing error:", error);
res.status(500).json({
error: "Failed to list schemas",
details: error.message,
success: false
});
}
});
// Generate structured output
app.post("/api/structured/generate", async (req, res) => {
try {
const {
prompt,
schema_name,
max_retries = 3,
temperature = 0.1
} = req.body;
if (!prompt) {
return res.status(400).json({
error: "Prompt is required",
success: false
});
}
console.log(`πŸ“‹ Structured output: ${prompt.substring(0, 50)}... (Schema: ${schema_name})`);
// Get schema
const schemas = getAvailableSchemas();
if (!schema_name || !schemas[schema_name]) {
return res.status(400).json({
error: `Schema '${schema_name}' not found`,
available_schemas: Object.keys(schemas),
success: false
});
}
const targetSchema = schemas[schema_name].schema;
const schemaDescription = schemas[schema_name].description;
let attempt = 0;
let validationErrors = [];
while (attempt < max_retries) {
try {
// 🎯 STRUCTURED GENERATION: Create OpenAI chat with structured output
const response = await openai.chat.completions.create({
model: "gpt-4o-mini",
messages: [
{
role: "system",
content: `You are a data generation expert. Generate structured data that exactly matches the provided schema. Be accurate, detailed, and ensure all required fields are included with appropriate values.
Schema: ${schemaDescription}
Format: JSON object matching the exact schema requirements
Return ONLY valid JSON that matches the schema, no additional text or explanation.`
},
{
role: "user",
content: prompt
}
],
response_format: { type: "json_object" },
temperature
});
// Extract and parse JSON response
const generatedText = response.choices[0].message.content.trim();
const generatedData = JSON.parse(generatedText);
// πŸ›‘οΈ VALIDATION: Validate against schema
const validatedData = targetSchema.parse(generatedData);
// πŸ“€ SUCCESS RESPONSE: Return validated structured data
res.json({
success: true,
data: validatedData,
schema_used: schema_name,
validation: {
passed: true,
attempts: attempt + 1,
errors: []
},
metadata: {
model: "gpt-4o-mini",
temperature,
timestamp: new Date().toISOString()
}
});
return; // Exit successfully
} catch (error) {
attempt++;
if (error instanceof z.ZodError) {
// Schema validation error
validationErrors.push({
attempt,
type: 'validation',
errors: error.errors.map(err => ({
path: err.path.join('.'),
message: err.message,
code: err.code
}))
});
console.warn(`Validation attempt ${attempt} failed:`, error.errors);
if (attempt >= max_retries) {
return res.status(422).json({
error: "Data validation failed after maximum retries",
validation: {
passed: false,
attempts: attempt,
errors: validationErrors
},
success: false
});
}
} else {
// Generation error
console.error(`Generation attempt ${attempt} failed:`, error);
if (attempt >= max_retries) {
return res.status(500).json({
error: "Failed to generate structured data",
details: error.message,
validation: {
passed: false,
attempts: attempt,
errors: validationErrors
},
success: false
});
}
}
}
}
} catch (error) {
console.error("Structured output error:", error);
res.status(500).json({
error: "Failed to process structured output request",
details: error.message,
success: false
});
}
});
// Validate existing data against schema
app.post("/api/structured/validate", (req, res) => {
try {
const { data, schema_name } = req.body;
if (!data) {
return res.status(400).json({
error: "Data is required",
success: false
});
}
// Get schema
const schemas = getAvailableSchemas();
if (!schemas[schema_name]) {
return res.status(400).json({
error: `Schema '${schema_name}' not found`,
available_schemas: Object.keys(schemas),
success: false
});
}
const targetSchema = schemas[schema_name].schema;
// Validate data
try {
const validatedData = targetSchema.parse(data);
res.json({
success: true,
valid: true,
data: validatedData,
schema_used: schema_name,
errors: []
});
} catch (error) {
if (error instanceof z.ZodError) {
res.status(422).json({
success: true,
valid: false,
errors: error.errors.map(err => ({
path: err.path.join('.'),
message: err.message,
code: err.code,
received: err.received
})),
schema_used: schema_name
});
} else {
throw error;
}
}
} catch (error) {
console.error("Validation error:", error);
res.status(500).json({
error: "Failed to validate data",
details: error.message,
success: false
});
}
});

Function breakdown:

  1. Schema definitions - Predefined Zod schemas for common data structures
  2. Schema validation - Ensure generated data meets exact requirements
  3. Data generation - Create structured data matching exact schemas
  4. Validation pipeline - Multiple attempts with detailed error reporting
  5. Error handling - Retry logic and detailed validation error reporting

πŸ”§ Step 3: Building the React Structured Output Component

Section titled β€œπŸ”§ Step 3: Building the React Structured Output Component”

Now let’s create a React component for structured output using the same patterns from your existing components.

Create a new file src/StructuredOutput.jsx:

import { useState, useEffect } from "react";
import { FileText, Check, X, Download, Upload, RefreshCw, Code, Database } from "lucide-react";
function StructuredOutput() {
// 🧠 STATE: Structured output data management
const [schemas, setSchemas] = useState([]); // Available schemas
const [selectedSchema, setSelectedSchema] = useState(""); // Current schema
const [prompt, setPrompt] = useState(""); // Generation prompt
const [isGenerating, setIsGenerating] = useState(false); // Generation status
const [result, setResult] = useState(null); // Generation result
const [error, setError] = useState(null); // Error messages
const [validationData, setValidationData] = useState(""); // Data for validation
const [isValidating, setIsValidating] = useState(false); // Validation status
const [validationResult, setValidationResult] = useState(null); // Validation result
const [activeTab, setActiveTab] = useState("generate"); // Active tab
const [maxRetries, setMaxRetries] = useState(3); // Generation retries
const [temperature, setTemperature] = useState(0.1); // Generation creativity
// πŸ”§ FUNCTIONS: Structured output logic engine
// Load available schemas
const loadSchemas = async () => {
try {
const response = await fetch("http://localhost:8000/api/structured/schemas");
const data = await response.json();
if (!response.ok) {
throw new Error(data.error || 'Failed to load schemas');
}
setSchemas(data.schemas);
if (data.schemas.length > 0 && !selectedSchema) {
setSelectedSchema(data.schemas[0].name);
}
} catch (error) {
console.error('Failed to load schemas:', error);
setError(error.message || 'Could not load schemas');
}
};
// Generate structured data
const generateStructuredData = async () => {
if (!prompt.trim() || !selectedSchema) {
setError('Both prompt and schema selection are required');
return;
}
setIsGenerating(true);
setError(null);
setResult(null);
try {
const response = await fetch("http://localhost:8000/api/structured/generate", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
prompt: prompt.trim(),
schema_name: selectedSchema,
max_retries: maxRetries,
temperature: temperature
})
});
const data = await response.json();
if (!response.ok) {
throw new Error(data.error || 'Failed to generate structured data');
}
setResult(data);
} catch (error) {
console.error('Generation failed:', error);
setError(error.message || 'Could not generate structured data');
} finally {
setIsGenerating(false);
}
};
// Validate data against schema
const validateData = async () => {
if (!validationData.trim() || !selectedSchema) {
setError('Both data and schema selection are required for validation');
return;
}
setIsValidating(true);
setError(null);
setValidationResult(null);
try {
// Parse JSON data
let parsedData;
try {
parsedData = JSON.parse(validationData);
} catch (parseError) {
throw new Error('Invalid JSON format');
}
const response = await fetch("http://localhost:8000/api/structured/validate", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
data: parsedData,
schema_name: selectedSchema
})
});
const data = await response.json();
if (!response.ok && response.status !== 422) {
throw new Error(data.error || 'Failed to validate data');
}
setValidationResult(data);
} catch (error) {
console.error('Validation failed:', error);
setError(error.message || 'Could not validate data');
} finally {
setIsValidating(false);
}
};
// Download result as JSON
const downloadResult = (data, filename) => {
const element = document.createElement('a');
const file = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' });
element.href = URL.createObjectURL(file);
element.download = filename;
document.body.appendChild(element);
element.click();
document.body.removeChild(element);
};
// Clear current results
const clearResults = () => {
setResult(null);
setValidationResult(null);
setError(null);
};
// Handle Enter key press in textarea
const handleKeyPress = (e) => {
if (e.key === 'Enter' && e.ctrlKey) {
e.preventDefault();
if (activeTab === 'generate') {
generateStructuredData();
} else if (activeTab === 'validate') {
validateData();
}
}
};
// Load schemas on component mount
useEffect(() => {
loadSchemas();
}, []);
// Example prompts for different schemas
const getExamplePrompt = (schemaName) => {
const examples = {
person: "Create a person profile for a software engineer named Alice Johnson who lives in San Francisco",
event: "Create a team meeting event for next Friday at 2 PM in the main conference room",
product: "Create a product listing for a wireless Bluetooth headphone with noise cancellation",
task: "Create a high-priority task for completing the quarterly financial report due next week",
analysis: "Analyze this customer feedback: 'The product is amazing but the delivery was slow and customer service was unhelpful'"
};
return examples[schemaName] || "";
};
// 🎨 UI: Interface components
return (
<div className="min-h-screen bg-gradient-to-br from-purple-50 to-indigo-50 flex items-center justify-center p-4">
<div className="bg-white rounded-2xl shadow-2xl w-full max-w-6xl flex flex-col overflow-hidden">
{/* Header */}
<div className="bg-gradient-to-r from-purple-600 to-indigo-600 text-white p-6">
<div className="flex items-center space-x-3">
<div className="w-10 h-10 bg-white bg-opacity-20 rounded-full flex items-center justify-center">
<FileText className="w-5 h-5" />
</div>
<div>
<h1 className="text-xl font-bold">πŸ“‹ Structured Output</h1>
<p className="text-purple-100 text-sm">Generate and validate structured data with Zod schemas!</p>
</div>
</div>
</div>
{/* Tab Navigation */}
<div className="border-b border-gray-200">
<nav className="flex">
<button
onClick={() => setActiveTab('generate')}
className={`px-6 py-3 font-medium text-sm border-b-2 transition-colors duration-200 ${
activeTab === 'generate'
? 'border-purple-500 text-purple-600'
: 'border-transparent text-gray-500 hover:text-gray-700'
}`}
>
<Database className="w-4 h-4 inline mr-2" />
Generate Data
</button>
<button
onClick={() => setActiveTab('validate')}
className={`px-6 py-3 font-medium text-sm border-b-2 transition-colors duration-200 ${
activeTab === 'validate'
? 'border-purple-500 text-purple-600'
: 'border-transparent text-gray-500 hover:text-gray-700'
}`}
>
<Check className="w-4 h-4 inline mr-2" />
Validate Data
</button>
</nav>
</div>
<div className="flex flex-1">
{/* Schema Sidebar */}
<div className="w-1/3 border-r border-gray-200 p-6">
<h3 className="font-semibold text-gray-900 mb-4 flex items-center">
<Code className="w-5 h-5 mr-2 text-purple-600" />
Available Schemas ({schemas.length})
</h3>
{/* Schema Selection */}
<div className="mb-4">
<label className="block text-sm font-medium text-gray-700 mb-2">
Select Schema
</label>
<select
value={selectedSchema}
onChange={(e) => setSelectedSchema(e.target.value)}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-purple-500"
>
<option value="">Choose a schema...</option>
{schemas.map((schema) => (
<option key={schema.name} value={schema.name}>
{schema.name} - {schema.description}
</option>
))}
</select>
</div>
{/* Schema Details */}
{selectedSchema && (
<div className="mb-4 p-4 bg-gray-50 rounded-lg">
{schemas.find(s => s.name === selectedSchema) && (
<div>
<h4 className="font-medium text-gray-900 mb-2">
{selectedSchema.charAt(0).toUpperCase() + selectedSchema.slice(1)} Schema
</h4>
<p className="text-sm text-gray-600 mb-3">
{schemas.find(s => s.name === selectedSchema).description}
</p>
<div>
<p className="text-xs font-medium text-gray-700 mb-1">Fields:</p>
<div className="flex flex-wrap gap-1">
{schemas.find(s => s.name === selectedSchema).example_fields.map((field) => (
<span
key={field}
className="px-2 py-1 bg-purple-100 text-purple-700 rounded text-xs"
>
{field}
</span>
))}
</div>
</div>
</div>
)}
</div>
)}
{/* Generation Settings */}
{activeTab === 'generate' && (
<div className="space-y-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Max Retries: {maxRetries}
</label>
<input
type="range"
min="1"
max="5"
value={maxRetries}
onChange={(e) => setMaxRetries(parseInt(e.target.value))}
className="w-full"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Temperature: {temperature}
</label>
<input
type="range"
min="0"
max="1"
step="0.1"
value={temperature}
onChange={(e) => setTemperature(parseFloat(e.target.value))}
className="w-full"
/>
<div className="flex justify-between text-xs text-gray-500 mt-1">
<span>Strict</span>
<span>Creative</span>
</div>
</div>
</div>
)}
<button
onClick={loadSchemas}
className="w-full mt-4 px-4 py-2 bg-gray-100 text-gray-700 rounded-lg hover:bg-gray-200 transition-colors duration-200 text-sm"
>
<RefreshCw className="w-4 h-4 inline mr-2" />
Refresh Schemas
</button>
</div>
{/* Main Content */}
<div className="flex-1 flex flex-col">
{/* Error Display */}
{error && (
<div className="p-4 bg-red-50 border-b border-red-200">
<p className="text-red-700 text-sm">
<strong>Error:</strong> {error}
</p>
</div>
)}
{/* Generate Tab */}
{activeTab === 'generate' && (
<div className="flex-1 p-6">
<div className="mb-4">
<div className="flex items-center justify-between mb-2">
<label className="block text-sm font-medium text-gray-700">
Generation Prompt
</label>
{selectedSchema && (
<button
onClick={() => setPrompt(getExamplePrompt(selectedSchema))}
className="px-3 py-1 bg-purple-100 text-purple-700 rounded-lg hover:bg-purple-200 transition-colors duration-200 text-sm"
>
Use Example
</button>
)}
</div>
<textarea
value={prompt}
onChange={(e) => setPrompt(e.target.value)}
onKeyDown={handleKeyPress}
placeholder="Describe what data you want to generate..."
disabled={isGenerating || !selectedSchema}
className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-purple-500 resize-none disabled:bg-gray-100"
rows="4"
/>
<p className="text-xs text-gray-500 mt-1">
Ctrl+Enter to generate β€’ Be specific about the data you want
</p>
</div>
<div className="flex items-center space-x-3 mb-6">
<button
onClick={generateStructuredData}
disabled={isGenerating || !prompt.trim() || !selectedSchema}
className="px-6 py-2 bg-purple-600 text-white rounded-lg hover:bg-purple-700 disabled:opacity-50 disabled:cursor-not-allowed transition-colors duration-200"
>
{isGenerating ? (
<>
<RefreshCw className="w-4 h-4 animate-spin inline mr-2" />
Generating...
</>
) : (
<>
<Database className="w-4 h-4 inline mr-2" />
Generate Data
</>
)}
</button>
{result && (
<button
onClick={clearResults}
className="px-4 py-2 bg-gray-100 text-gray-700 rounded-lg hover:bg-gray-200 transition-colors duration-200"
>
Clear
</button>
)}
</div>
{/* Generation Result */}
{result && (
<div className="space-y-4">
<div className="flex items-center justify-between">
<h3 className="font-semibold text-gray-900">Generated Data</h3>
<div className="flex items-center space-x-2">
<span className={`px-2 py-1 rounded text-xs ${
result.validation.passed
? 'bg-green-100 text-green-700'
: 'bg-red-100 text-red-700'
}`}>
{result.validation.passed ? 'Valid' : 'Invalid'}
</span>
<span className="text-xs text-gray-500">
{result.validation.attempts} attempt{result.validation.attempts !== 1 ? 's' : ''}
</span>
<button
onClick={() => downloadResult(result.data, `${selectedSchema}-${Date.now()}.json`)}
className="px-3 py-1 bg-purple-100 text-purple-700 rounded-lg hover:bg-purple-200 transition-colors duration-200 text-sm"
>
<Download className="w-4 h-4 inline mr-1" />
Download
</button>
</div>
</div>
<div className="bg-gray-50 rounded-lg p-4">
<pre className="text-sm text-gray-800 overflow-x-auto">
{JSON.stringify(result.data, null, 2)}
</pre>
</div>
{result.validation.errors.length > 0 && (
<div className="bg-red-50 rounded-lg p-4">
<h4 className="font-medium text-red-900 mb-2">Validation Errors</h4>
<div className="space-y-2">
{result.validation.errors.map((errorGroup, groupIndex) => (
<div key={groupIndex}>
<p className="text-sm font-medium text-red-800">
Attempt {errorGroup.attempt}:
</p>
<ul className="text-sm text-red-700 ml-4">
{errorGroup.errors.map((error, errorIndex) => (
<li key={errorIndex}>
<strong>{error.path}:</strong> {error.message}
</li>
))}
</ul>
</div>
))}
</div>
</div>
)}
</div>
)}
</div>
)}
{/* Validate Tab */}
{activeTab === 'validate' && (
<div className="flex-1 p-6">
<div className="mb-4">
<label className="block text-sm font-medium text-gray-700 mb-2">
JSON Data to Validate
</label>
<textarea
value={validationData}
onChange={(e) => setValidationData(e.target.value)}
onKeyDown={handleKeyPress}
placeholder="Paste your JSON data here..."
disabled={isValidating || !selectedSchema}
className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-purple-500 resize-none disabled:bg-gray-100 font-mono text-sm"
rows="8"
/>
<p className="text-xs text-gray-500 mt-1">
Ctrl+Enter to validate β€’ Must be valid JSON format
</p>
</div>
<div className="flex items-center space-x-3 mb-6">
<button
onClick={validateData}
disabled={isValidating || !validationData.trim() || !selectedSchema}
className="px-6 py-2 bg-purple-600 text-white rounded-lg hover:bg-purple-700 disabled:opacity-50 disabled:cursor-not-allowed transition-colors duration-200"
>
{isValidating ? (
<>
<RefreshCw className="w-4 h-4 animate-spin inline mr-2" />
Validating...
</>
) : (
<>
<Check className="w-4 h-4 inline mr-2" />
Validate Data
</>
)}
</button>
{validationResult && (
<button
onClick={clearResults}
className="px-4 py-2 bg-gray-100 text-gray-700 rounded-lg hover:bg-gray-200 transition-colors duration-200"
>
Clear
</button>
)}
</div>
{/* Validation Result */}
{validationResult && (
<div className="space-y-4">
<div className="flex items-center justify-between">
<h3 className="font-semibold text-gray-900">Validation Result</h3>
<span className={`px-3 py-1 rounded-lg text-sm font-medium ${
validationResult.valid
? 'bg-green-100 text-green-700'
: 'bg-red-100 text-red-700'
}`}>
{validationResult.valid ? (
<>
<Check className="w-4 h-4 inline mr-1" />
Valid
</>
) : (
<>
<X className="w-4 h-4 inline mr-1" />
Invalid
</>
)}
</span>
</div>
{validationResult.valid ? (
<div className="bg-green-50 rounded-lg p-4">
<p className="text-green-700 mb-3">
βœ… Data successfully validates against the {validationResult.schema_used} schema!
</p>
<div className="bg-white rounded p-3">
<pre className="text-sm text-gray-800 overflow-x-auto">
{JSON.stringify(validationResult.data, null, 2)}
</pre>
</div>
</div>
) : (
<div className="bg-red-50 rounded-lg p-4">
<p className="text-red-700 mb-3">
❌ Data does not match the {validationResult.schema_used} schema.
</p>
<div className="space-y-2">
{validationResult.errors.map((error, index) => (
<div key={index} className="bg-white rounded p-3">
<div className="flex items-start space-x-2">
<X className="w-4 h-4 text-red-500 mt-0.5" />
<div>
<p className="font-medium text-red-800">
{error.path || 'Root'}
</p>
<p className="text-sm text-red-700">{error.message}</p>
{error.received && (
<p className="text-xs text-red-600 mt-1">
Received: {JSON.stringify(error.received)}
</p>
)}
</div>
</div>
</div>
))}
</div>
</div>
)}
</div>
)}
</div>
)}
</div>
</div>
</div>
</div>
);
}
export default StructuredOutput;

Let’s test your structured output feature step by step to make sure everything works correctly.

First, verify your backend routes work by testing them individually:

Test schema listing:

Terminal window
# Test the schemas endpoint
curl http://localhost:8000/api/structured/schemas

Test data generation:

Terminal window
# Test generating structured data
curl -X POST http://localhost:8000/api/structured/generate \
-H "Content-Type: application/json" \
-d '{"prompt": "Create a person named John Doe", "schema_name": "person"}'

Start both servers:

Backend (in your backend folder):

Terminal window
npm run dev

Frontend (in your frontend folder):

Terminal window
npm run dev

Test the complete flow:

  1. Navigate to Structured β†’ Click the β€œStructured” tab in navigation
  2. Select schema β†’ Choose from available Zod schemas
  3. Generate data β†’ Write prompts and generate structured JSON
  4. Validate data β†’ Test existing JSON against schemas
  5. Download results β†’ Save generated data as JSON files
  6. Test different schemas β†’ Try person, event, product, task, and analysis schemas
  7. Adjust settings β†’ Change retry counts and temperature for different results

Test error scenarios:

❌ Invalid prompt: Try empty or invalid generation prompts
❌ Malformed JSON: Test validation with invalid JSON data
❌ Schema mismatch: Validate data against wrong schema
❌ Generation failure: Test with very complex or impossible prompts

Expected behavior:

  • Clear error messages with validation details
  • Retry logic for generation failures
  • Detailed schema validation error reporting
  • User can fix issues and retry

Congratulations! You’ve extended your existing application with complete structured output:

  • βœ… Extended your backend with Zod schema validation and structured output generation
  • βœ… Added React structured output component following the same patterns as your other features
  • βœ… Implemented guaranteed data formats with schema validation and retry logic
  • βœ… Created comprehensive validation with detailed error reporting and type safety
  • βœ… Added multiple schemas covering common use cases like persons, events, products, tasks, and analysis
  • βœ… Maintained consistent design with your existing application

Your application now has:

  • Text chat with streaming responses
  • Image generation with DALL-E 3
  • Audio transcription with Whisper voice recognition
  • File analysis with intelligent document processing
  • Text-to-speech with natural voice synthesis
  • Vision analysis with GPT-4o visual intelligence
  • Voice interaction with GPT-4o Audio natural conversations
  • Function calling with real-world tool integration
  • Web search with real-time internet access
  • Structured output with Zod validation and guaranteed data formats
  • Unified navigation between all features
  • Professional UI with consistent styling

Next up: You’ll learn about MCP Integration, where your AI can connect to external data sources and services using Model Context Protocol, expanding beyond built-in capabilities.

Your OpenAI mastery application now guarantees reliable data formats! πŸ“‹