📋 Structured Output Made Simple
Right now, you have chat, images, audio, files, speech, vision, voice interaction, and MCP integration working in your application. But what if your AI could return perfectly structured, validated data every time?
Structured output eliminates unpredictable AI responses. Instead of parsing free-form text, your AI returns JSON data that matches exact schemas, making it perfect for APIs, databases, and reliable data processing.
You’re about to learn exactly how to add structured output with Zod validation to your existing application.
🧠 Step 1: Understanding Structured Output
Section titled “🧠 Step 1: Understanding Structured Output”Before we write any code, let’s understand what structured output actually means and why it’s different from what you’ve built before.
What Structured Output Actually Means
Section titled “What Structured Output Actually Means”Structured output is like having your AI speak in perfect data formats. Instead of returning natural language that you need to parse, your AI returns validated JSON objects that match exact schemas you define.
Real-world analogy: It’s like the difference between asking someone to “tell me about this person” (free-form response) versus asking them to “fill out this exact form with name, age, email, and address fields” (structured response).
Why Structured Output vs. Your Existing Features
Section titled “Why Structured Output vs. Your Existing Features”You already have powerful AI capabilities, but structured output is different:
💬 Regular Chat - AI returns natural language text (unpredictable format)
🎤 Voice/Audio - AI processes and generates audio (specific format but not data)
📋 Structured Output - AI returns validated JSON data (guaranteed format)
The key difference: Structured output guarantees your AI responses match exact data schemas, making them perfect for programmatic use, database storage, and API integration.
Real-World Use Cases
Section titled “Real-World Use Cases”Think about all the times you need reliable data formats:
- Form processing - Extract structured data from documents
- API responses - Return consistent JSON for frontend applications
- Database operations - Generate records that match database schemas
- Data analysis - Create standardized reports and analytics
- Workflow automation - Trigger actions based on structured data
Without structured output, you must:
- Parse unpredictable AI text responses (error-prone)
- Handle various response formats (inconsistent)
- Validate data manually (time-consuming)
- Deal with parsing failures (unreliable)
With structured output, your AI guarantees valid, structured data that matches your exact requirements.
Zod Schema Validation
Section titled “Zod Schema Validation”Your structured output will use Zod for schema validation:
📋 Zod Schema Validation - The Data Validator
- Best for: Defining and validating TypeScript-first schemas
- Strengths: Type safety, runtime validation, error handling
- Use cases: API responses, form validation, data processing
- Think of it as: Your data format enforcer
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.
Step 2A: Understanding Structured Output State
Section titled “Step 2A: Understanding Structured Output State”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
Step 2B: Installing Required Dependencies
Section titled “Step 2B: Installing Required Dependencies”First, add the structured output dependencies to your backend. In your backend folder, run:
npm install zod
What this package does:
- zod: TypeScript-first schema validation with static type inference
Step 2C: Adding the Structured Output Routes
Section titled “Step 2C: Adding the Structured Output Routes”Add these new endpoints to your existing index.js
file, right after your MCP integration routes:
import { z } from 'zod';
// 📋 SCHEMA DEFINITIONS: Predefined Zod schemas for common use cases
// Personal information schemaconst 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 schemaconst 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 schemaconst 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 schemaconst 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 schemaconst 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
// Convert Zod schema to OpenAI function formatconst zodToOpenAIFunction = (schema, name, description) => { const schemaObject = schema._def;
// Convert Zod schema to JSON Schema format const convertZodToJsonSchema = (zodSchema) => { const def = zodSchema._def;
switch (def.typeName) { case 'ZodString': return { type: 'string' }; case 'ZodNumber': return { type: 'number' }; case 'ZodBoolean': return { type: 'boolean' }; case 'ZodArray': return { type: 'array', items: convertZodToJsonSchema(def.type) }; case 'ZodObject': const properties = {}; const required = [];
for (const [key, value] of Object.entries(def.shape())) { properties[key] = convertZodToJsonSchema(value); if (!value.isOptional()) { required.push(key); } }
return { type: 'object', properties, required: required.length > 0 ? required : undefined }; case 'ZodEnum': return { type: 'string', enum: def.values }; default: return { type: 'string' }; // Fallback } };
return { type: "function", function: { name, description, parameters: convertZodToJsonSchema(schema) } };};
// Get available schemasconst 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 schemasapp.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 outputapp.post("/api/structured/generate", async (req, res) => { try { const { prompt, schema_name, custom_schema = null, 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 let targetSchema; let schemaDescription;
if (custom_schema) { try { // Parse custom schema (simplified - you might want more robust parsing) targetSchema = z.object(custom_schema); schemaDescription = "Custom schema"; } catch (error) { return res.status(400).json({ error: "Invalid custom schema", details: error.message, success: false }); } } else if (schema_name) { 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 }); } targetSchema = schemas[schema_name].schema; schemaDescription = schemas[schema_name].description; } else { return res.status(400).json({ error: "Either schema_name or custom_schema is required", success: false }); }
// Convert schema to OpenAI function format const functionSchema = zodToOpenAIFunction( targetSchema, `generate_${schema_name || 'custom'}`, `Generate structured data: ${schemaDescription}` );
let attempt = 0; let validationErrors = [];
while (attempt < max_retries) { try { // 🎯 STRUCTURED GENERATION: Create OpenAI chat with function calling const response = await openai.chat.completions.create({ model: "gpt-4", 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` }, { role: "user", content: prompt } ], tools: [functionSchema], tool_choice: { type: "function", function: { name: functionSchema.function.name } }, temperature });
// Extract function call result const toolCall = response.choices[0].message.tool_calls?.[0]; if (!toolCall) { throw new Error("No function call generated"); }
const generatedData = JSON.parse(toolCall.function.arguments);
// 🛡️ 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 || 'custom', validation: { passed: true, attempts: attempt + 1, errors: [] }, metadata: { model: "gpt-4", 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 schemaapp.post("/api/structured/validate", (req, res) => { try { const { data, schema_name, custom_schema = null } = req.body;
if (!data) { return res.status(400).json({ error: "Data is required", success: false }); }
// Get schema let targetSchema;
if (custom_schema) { try { targetSchema = z.object(custom_schema); } catch (error) { return res.status(400).json({ error: "Invalid custom schema", details: error.message, success: false }); } } else if (schema_name) { 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 }); } targetSchema = schemas[schema_name].schema; } else { return res.status(400).json({ error: "Either schema_name or custom_schema is required", success: false }); }
// Validate data try { const validatedData = targetSchema.parse(data);
res.json({ success: true, valid: true, data: validatedData, schema_used: schema_name || 'custom', 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 || 'custom' }); } else { throw error; } }
} catch (error) { console.error("Validation error:", error); res.status(500).json({ error: "Failed to validate data", details: error.message, success: false }); }});
// Transform data between schemasapp.post("/api/structured/transform", async (req, res) => { try { const { data, source_schema, target_schema, transformation_prompt = "Transform the data to match the target schema while preserving as much information as possible." } = req.body;
if (!data || !source_schema || !target_schema) { return res.status(400).json({ error: "Data, source_schema, and target_schema are required", success: false }); }
console.log(`📋 Transforming data from ${source_schema} to ${target_schema}`);
const schemas = getAvailableSchemas();
// Validate source schema if (!schemas[source_schema]) { return res.status(400).json({ error: `Source schema '${source_schema}' not found`, success: false }); }
// Validate target schema if (!schemas[target_schema]) { return res.status(400).json({ error: `Target schema '${target_schema}' not found`, success: false }); }
// Validate input data against source schema try { schemas[source_schema].schema.parse(data); } catch (error) { return res.status(422).json({ error: "Input data does not match source schema", validation_errors: error.errors, success: false }); }
// Transform using structured output const transformResponse = await fetch(`http://localhost:${port}/api/structured/generate`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ prompt: `${transformation_prompt}\n\nSource data (${source_schema}): ${JSON.stringify(data, null, 2)}`, schema_name: target_schema, temperature: 0.1 }) });
const transformData = await transformResponse.json();
if (!transformResponse.ok) { throw new Error(transformData.error || 'Transformation failed'); }
res.json({ success: true, original_data: data, transformed_data: transformData.data, source_schema, target_schema, validation: transformData.validation, timestamp: new Date().toISOString() });
} catch (error) { console.error("Transformation error:", error); res.status(500).json({ error: "Failed to transform data", details: error.message, success: false }); }});
Function breakdown:
- Schema definitions - Predefined Zod schemas for common data structures
- Schema conversion - Transform Zod schemas to OpenAI function format
- Data generation - Create structured data matching exact schemas
- Validation pipeline - Ensure generated data meets schema requirements
- 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.
Step 3A: Creating the Structured Output Component
Section titled “Step 3A: Creating the Structured Output Component”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;
Step 3B: Adding Structured Output to Navigation
Section titled “Step 3B: Adding Structured Output to Navigation”Update your src/App.jsx
to include the new structured output component:
import { useState } from "react";import StreamingChat from "./StreamingChat";import ImageGenerator from "./ImageGenerator";import AudioTranscription from "./AudioTranscription";import FileAnalysis from "./FileAnalysis";import TextToSpeech from "./TextToSpeech";import VisionAnalysis from "./VisionAnalysis";import VoiceInteraction from "./VoiceInteraction";import MCPIntegration from "./MCPIntegration";import StructuredOutput from "./StructuredOutput";import { MessageSquare, Image, Mic, Folder, Volume2, Eye, Phone, Link, FileText } from "lucide-react";
function App() { // 🧠 STATE: Navigation management const [currentView, setCurrentView] = useState("chat"); // 'chat', 'images', 'audio', 'files', 'speech', 'vision', 'voice', 'mcp', or 'structured'
// 🎨 UI: Main app with navigation return ( <div className="min-h-screen bg-gray-100"> {/* Navigation Header */} <nav className="bg-white shadow-sm border-b border-gray-200"> <div className="max-w-7xl mx-auto px-4"> <div className="flex items-center justify-between h-16"> {/* Logo */} <div className="flex items-center space-x-3"> <div className="w-8 h-8 bg-gradient-to-r from-blue-500 to-purple-600 rounded-lg flex items-center justify-center"> <span className="text-white font-bold text-sm">AI</span> </div> <h1 className="text-xl font-bold text-gray-900">OpenAI Mastery</h1> </div>
{/* Navigation Buttons */} <div className="flex space-x-1 overflow-x-auto"> <button onClick={() => setCurrentView("chat")} className={`px-3 py-2 rounded-lg flex items-center space-x-2 transition-all duration-200 whitespace-nowrap ${ currentView === "chat" ? "bg-blue-100 text-blue-700 shadow-sm" : "text-gray-600 hover:text-gray-900 hover:bg-gray-100" }`} > <MessageSquare className="w-4 h-4" /> <span>Chat</span> </button>
<button onClick={() => setCurrentView("images")} className={`px-3 py-2 rounded-lg flex items-center space-x-2 transition-all duration-200 whitespace-nowrap ${ currentView === "images" ? "bg-purple-100 text-purple-700 shadow-sm" : "text-gray-600 hover:text-gray-900 hover:bg-gray-100" }`} > <Image className="w-4 h-4" /> <span>Images</span> </button>
<button onClick={() => setCurrentView("audio")} className={`px-3 py-2 rounded-lg flex items-center space-x-2 transition-all duration-200 whitespace-nowrap ${ currentView === "audio" ? "bg-blue-100 text-blue-700 shadow-sm" : "text-gray-600 hover:text-gray-900 hover:bg-gray-100" }`} > <Mic className="w-4 h-4" /> <span>Audio</span> </button>
<button onClick={() => setCurrentView("files")} className={`px-3 py-2 rounded-lg flex items-center space-x-2 transition-all duration-200 whitespace-nowrap ${ currentView === "files" ? "bg-green-100 text-green-700 shadow-sm" : "text-gray-600 hover:text-gray-900 hover:bg-gray-100" }`} > <Folder className="w-4 h-4" /> <span>Files</span> </button>
<button onClick={() => setCurrentView("speech")} className={`px-3 py-2 rounded-lg flex items-center space-x-2 transition-all duration-200 whitespace-nowrap ${ currentView === "speech" ? "bg-orange-100 text-orange-700 shadow-sm" : "text-gray-600 hover:text-gray-900 hover:bg-gray-100" }`} > <Volume2 className="w-4 h-4" /> <span>Speech</span> </button>
<button onClick={() => setCurrentView("vision")} className={`px-3 py-2 rounded-lg flex items-center space-x-2 transition-all duration-200 whitespace-nowrap ${ currentView === "vision" ? "bg-indigo-100 text-indigo-700 shadow-sm" : "text-gray-600 hover:text-gray-900 hover:bg-gray-100" }`} > <Eye className="w-4 h-4" /> <span>Vision</span> </button>
<button onClick={() => setCurrentView("voice")} className={`px-3 py-2 rounded-lg flex items-center space-x-2 transition-all duration-200 whitespace-nowrap ${ currentView === "voice" ? "bg-blue-100 text-blue-700 shadow-sm" : "text-gray-600 hover:text-gray-900 hover:bg-gray-100" }`} > <Phone className="w-4 h-4" /> <span>Voice</span> </button>
<button onClick={() => setCurrentView("mcp")} className={`px-3 py-2 rounded-lg flex items-center space-x-2 transition-all duration-200 whitespace-nowrap ${ currentView === "mcp" ? "bg-green-100 text-green-700 shadow-sm" : "text-gray-600 hover:text-gray-900 hover:bg-gray-100" }`} > <Link className="w-4 h-4" /> <span>MCP</span> </button>
<button onClick={() => setCurrentView("structured")} className={`px-3 py-2 rounded-lg flex items-center space-x-2 transition-all duration-200 whitespace-nowrap ${ currentView === "structured" ? "bg-purple-100 text-purple-700 shadow-sm" : "text-gray-600 hover:text-gray-900 hover:bg-gray-100" }`} > <FileText className="w-4 h-4" /> <span>Structured</span> </button> </div> </div> </div> </nav>
{/* Main Content */} <main className="h-[calc(100vh-4rem)]"> {currentView === "chat" && <StreamingChat />} {currentView === "images" && <ImageGenerator />} {currentView === "audio" && <AudioTranscription />} {currentView === "files" && <FileAnalysis />} {currentView === "speech" && <TextToSpeech />} {currentView === "vision" && <VisionAnalysis />} {currentView === "voice" && <VoiceInteraction />} {currentView === "mcp" && <MCPIntegration />} {currentView === "structured" && <StructuredOutput />} </main> </div> );}
export default App;
🧪 Testing Your Structured Output
Section titled “🧪 Testing Your Structured Output”Let’s test your structured output feature step by step to make sure everything works correctly.
Step 1: Backend Route Test
Section titled “Step 1: Backend Route Test”First, verify your backend routes work by testing them individually:
Test schema listing:
# Test the schemas endpointcurl http://localhost:8000/api/structured/schemas
Test data generation:
# Test generating structured datacurl -X POST http://localhost:8000/api/structured/generate \ -H "Content-Type: application/json" \ -d '{"prompt": "Create a person named John Doe", "schema_name": "person"}'
Step 2: Full Application Test
Section titled “Step 2: Full Application Test”Start both servers:
Backend (in your backend folder):
npm run dev
Frontend (in your frontend folder):
npm run dev
Test the complete flow:
- Navigate to Structured → Click the “Structured” tab in navigation
- Select schema → Choose from available Zod schemas
- Generate data → Write prompts and generate structured JSON
- Validate data → Test existing JSON against schemas
- Download results → Save generated data as JSON files
- Test different schemas → Try person, event, product, task, and analysis schemas
- Adjust settings → Change retry counts and temperature for different results
Step 3: Error Handling Test
Section titled “Step 3: Error Handling Test”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
✅ What You Built
Section titled “✅ What You Built”Congratulations! You’ve completed Module 2 with all advanced OpenAI features:
- ✅ 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 complete Module 2 application now has:
- Text chat with streaming responses
- Image generation with DALL-E 3 and GPT-Image-1
- 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 external tools and APIs
- Web search with real-time internet access
- MCP integration with external data sources and services
- Structured output with Zod validation and guaranteed data formats
- Unified navigation between all features
- Professional UI with consistent TailwindCSS styling
Ready for Module 3: You now have a comprehensive OpenAI application with all major features. Module 3 will focus on production readiness, safety, content moderation, performance optimization, and deployment strategies.
Your OpenAI mastery application now guarantees reliable data formats! 📋