π 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!
π― From Unpredictable Text to Guaranteed Data
Section titled βπ― From Unpredictable Text to Guaranteed Dataβ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!
π The Data Reliability Transformation
Section titled βπ The Data Reliability Transformationβ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!
π Why Structured Output Changes Everything
Section titled βπ Why Structured Output Changes Everythingβ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"}π§ 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 transforms your AI applications.
What Structured Output Actually Means
Section titled βWhat Structured Output Actually Meansβ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).
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)
π 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.
Zod Schema Validation
Section titled βZod Schema Validationβ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.
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 levelsKey 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 zodWhat 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 web search 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
// 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, 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 schemaapp.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:
- Schema definitions - Predefined Zod schemas for common data structures
- Schema validation - Ensure generated data meets exact requirements
- Data generation - Create structured data matching exact schemas
- Validation pipeline - Multiple attempts with detailed error reporting
- 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 4: Testing Your Structured Output
Section titled βπ§ͺ Step 4: Testing Your Structured OutputβLetβs test your structured output feature step by step to make sure everything works correctly.
Step 4A: Backend Route Test
Section titled βStep 4A: 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/schemasTest 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 4B: Full Application Test
Section titled βStep 4B: Full Application TestβStart both servers:
Backend (in your backend folder):
npm run devFrontend (in your frontend folder):
npm run devTest 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 4C: Error Handling Test
Section titled βStep 4C: 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 promptsExpected 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 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! π