Image Generation Troubleshooting
This guide covers common issues and solutions for Bike4Mind's image generation system.
Common Issues
1. Generation Failures
Issue: Images fail to generate with timeout errors
Symptoms:
- Generation status stuck at "Now painting..."
- Eventually times out with error message
- No images returned in quest
Possible Causes:
- API Rate Limiting: Provider rate limits exceeded
- Network Issues: Connectivity problems with AI provider
- Content Moderation: Prompt flagged by safety filters
- Resource Exhaustion: High server load or memory issues
Solutions:
// Check API rate limits
const checkRateLimit = async (provider: string) => {
try {
const response = await axios.get(`/api/debug/rate-limit/${provider}`);
console.log(`${provider} rate limit status:`, response.data);
} catch (error) {
console.error(`Failed to check rate limit for ${provider}:`, error);
}
};
// Implement exponential backoff retry
const retryWithBackoff = async (fn: () => Promise<any>, maxRetries = 3) => {
for (let i = 0; i < maxRetries; i++) {
try {
return await fn();
} catch (error) {
if (i === maxRetries - 1) throw error;
const delay = Math.pow(2, i) * 1000; // 1s, 2s, 4s
await new Promise(resolve => setTimeout(resolve, delay));
}
}
};
Debug Steps:
- Check server logs for error messages
- Verify API keys are valid and have sufficient quota
- Test with a simple, safe prompt
- Monitor provider status pages
Issue: BFL generation fails with "Request Moderated" error
Symptoms:
- Generation immediately fails
- Error message: "Your image request was flagged by content moderation"
- No images generated
Possible Causes:
- Prompt Content: Contains restricted words or concepts
- Safety Tolerance: Set too low for the prompt
- Image Prompt: Source image contains inappropriate content
Solutions:
// Adjust safety tolerance for borderline content
const handleModerationError = (originalPrompt: string) => {
const { setLLM, safety_tolerance } = useLLM();
// Increase safety tolerance if currently low
if (safety_tolerance < 4) {
setLLM({ safety_tolerance: 4 });
toast.info('Increased safety tolerance - try generating again');
} else {
// Suggest prompt modification
toast.error('Prompt may contain restricted content. Try rephrasing your request.');
}
};
// Prompt sanitization utility
const sanitizePrompt = (prompt: string): string => {
const restrictedTerms = ['violence', 'explicit', 'harmful'];
let sanitized = prompt;
restrictedTerms.forEach(term => {
const regex = new RegExp(term, 'gi');
sanitized = sanitized.replace(regex, '[content]');
});
return sanitized;
};
Debug Steps:
- Review prompt for potentially problematic content
- Try increasing BFL safety tolerance to 5-6
- Test with a known-safe prompt
- Check if image prompts contain inappropriate content
2. Model Parameter Issues
Issue: Parameter validation errors
Symptoms:
- Frontend shows validation errors
- Generation requests rejected by API
- Console errors about invalid parameters
Common Parameter Issues:
// Size validation for different models
const validateImageSize = (model: ImageModelName, size: string) => {
const constraints = IMAGE_SIZE_CONSTRAINTS;
if (model === ImageModels.GPT_IMAGE_1) {
if (!constraints.GPT_IMAGE_1.sizes.includes(size as any)) {
throw new Error(`Invalid size ${size} for GPT-Image-1. Allowed: ${constraints.GPT_IMAGE_1.sizes.join(', ')}`);
}
} else if (model === ImageModels.FLUX_PRO_ULTRA) {
// Ultra models use aspect ratios, not fixed sizes
console.warn('FLUX PRO Ultra uses aspect_ratio, not size parameter');
}
};
// BFL safety tolerance validation
const validateBFLSafetyTolerance = (tolerance: number) => {
if (tolerance < BFL_SAFETY_TOLERANCE.MIN || tolerance > BFL_SAFETY_TOLERANCE.MAX) {
throw new Error(
`Safety tolerance must be between ${BFL_SAFETY_TOLERANCE.MIN} and ${BFL_SAFETY_TOLERANCE.MAX}`
);
}
};
// Aspect ratio validation for BFL Ultra
const validateAspectRatio = (aspectRatio: string) => {
const validRatios = ['1:1', '4:3', '3:4', '16:9', '9:16', '21:9', '9:21'];
if (!validRatios.includes(aspectRatio)) {
throw new Error(`Invalid aspect ratio. Allowed: ${validRatios.join(', ')}`);
}
};
Solutions:
- Check model-specific parameter constraints
- Ensure UI validation matches backend validation
- Provide clear error messages for invalid combinations
Issue: GPT-Image-1 parameter mapping errors
Symptoms:
- OpenAI API returns 400 errors
- Parameters not recognized by model
- Generation fails with parameter validation
Root Cause: GPT-Image-1 has specific parameter requirements:
// Correct parameter mapping for GPT-Image-1
const mapGPTImageParameters = (commonParams: any) => {
const { quality, style, ...otherParams } = commonParams;
if (commonParams.model === ImageModels.GPT_IMAGE_1) {
return {
...otherParams,
// Map quality values
quality: quality === 'standard' ? 'medium' :
quality === 'hd' ? 'high' : quality,
// Remove unsupported parameters
style: undefined,
response_format: undefined,
};
}
return commonParams;
};
3. Storage and URL Issues
Issue: Images not displaying (broken image URLs)
Symptoms:
- Generated images show as broken
- Console shows 403 or 404 errors for image URLs
- Images work initially but break after time
Possible Causes:
- Expired Signed URLs: URLs have exceeded expiration time
- CORS Issues: Cross-origin requests blocked
- S3 Permissions: Incorrect bucket permissions
- Network Issues: CDN or S3 connectivity problems
Solutions:
// Implement URL refresh mechanism
const useImageUrl = (originalUrl: string) => {
const [currentUrl, setCurrentUrl] = useState(originalUrl);
const [isRefreshing, setIsRefreshing] = useState(false);
const refreshUrl = useCallback(async () => {
if (isRefreshing) return;
setIsRefreshing(true);
try {
// Extract path from original URL
const path = extractPathFromUrl(originalUrl);
// Get fresh signed URL
const response = await api.post('/api/images/get-signed-url', {
path,
expiresIn: 3600, // 1 hour
});
setCurrentUrl(response.data.url);
} catch (error) {
console.error('Failed to refresh image URL:', error);
} finally {
setIsRefreshing(false);
}
}, [originalUrl, isRefreshing]);
// Auto-refresh on error
const handleImageError = useCallback(() => {
refreshUrl();
}, [refreshUrl]);
return { currentUrl, refreshUrl, handleImageError, isRefreshing };
};
// CORS-aware image loading
const CORSAwareImage = ({ src, alt, ...props }) => {
const { currentUrl, handleImageError } = useImageUrl(src);
return (
<img
src={currentUrl}
alt={alt}
crossOrigin="anonymous"
onError={handleImageError}
{...props}
/>
);
};
Debug Steps:
- Check browser network tab for HTTP status codes
- Verify S3 bucket CORS configuration
- Test image URLs directly in browser
- Check CloudWatch logs for S3 access errors
Issue: Slow image loading
Symptoms:
- Images take long time to load
- Poor user experience with large images
- Timeout errors on mobile networks
Solutions:
// Implement progressive image loading
const ProgressiveImage = ({ src, alt, placeholder }) => {
const [isLoading, setIsLoading] = useState(true);
const [currentSrc, setCurrentSrc] = useState(placeholder);
useEffect(() => {
const img = new Image();
img.onload = () => {
setCurrentSrc(src);
setIsLoading(false);
};
img.src = src;
}, [src]);
return (
<Box position="relative">
<img
src={currentSrc}
alt={alt}
style={{
filter: isLoading ? 'blur(5px)' : 'none',
transition: 'filter 0.3s ease',
}}
/>
{isLoading && (
<Box position="absolute" top="50%" left="50%" transform="translate(-50%, -50%)">
<CircularProgress size="sm" />
</Box>
)}
</Box>
);
};
// Image optimization for different screen sizes
const ResponsiveImage = ({ src, alt }) => {
const isMobile = useIsMobile();
// Generate different image URLs for different sizes
const imageSrc = useMemo(() => {
if (isMobile) {
// Request smaller image for mobile
return `${src}?w=800&q=75`;
}
return `${src}?w=1200&q=85`;
}, [src, isMobile]);
return <img src={imageSrc} alt={alt} loading="lazy" />;
};
4. WebSocket and Real-time Issues
Issue: Progress updates not working
Symptoms:
- Status messages not updating during generation
- UI appears frozen during generation
- No real-time feedback to users
Possible Causes:
- WebSocket Connection: Connection lost or failed
- User ID Mismatch: WebSocket subscribed to wrong user
- Quest ID Issues: Incorrect quest ID in updates
- Network Issues: Firewall blocking WebSocket connections
Debug Solutions:
// WebSocket connection debugging
const debugWebSocketConnection = () => {
const { connectionState, lastMessage, sendJsonMessage } = useWebSocket();
useEffect(() => {
console.log('WebSocket state:', {
connectionState,
lastMessage,
timestamp: new Date().toISOString(),
});
// Test connection with ping
if (connectionState === 'OPEN') {
sendJsonMessage({ type: 'ping', timestamp: Date.now() });
}
}, [connectionState, lastMessage]);
// Monitor for missing updates
useEffect(() => {
const timeout = setTimeout(() => {
if (connectionState !== 'OPEN') {
console.warn('WebSocket connection not established after 5 seconds');
// Attempt reconnection
}
}, 5000);
return () => clearTimeout(timeout);
}, [connectionState]);
};
// Fallback polling for progress updates
const useProgressWithFallback = (questId: string) => {
const [progress, setProgress] = useState(null);
const { lastMessage } = useWebSocket();
// Primary: WebSocket updates
useEffect(() => {
if (lastMessage?.quest?.id === questId) {
setProgress(lastMessage);
}
}, [lastMessage, questId]);
// Fallback: HTTP polling
useEffect(() => {
if (!lastMessage) {
const interval = setInterval(async () => {
try {
const response = await api.get(`/api/quests/${questId}`);
setProgress({ quest: response.data });
} catch (error) {
console.error('Failed to poll quest progress:', error);
}
}, 2000);
return () => clearInterval(interval);
}
}, [lastMessage, questId]);
return progress;
};
5. Credit and Billing Issues
Issue: Incorrect credit deduction
Symptoms:
- Credits deducted for failed generations
- Wrong amount deducted for generation
- Credits not deducted for successful generation
Debug and Fix:
// Credit calculation debugging
const debugCreditCalculation = (model: string, parameters: any) => {
const modelInfo = getModelInfo(model);
if (!modelInfo) {
console.error(`Model info not found for: ${model}`);
return;
}
const expectedCost = calculateImageCost(modelInfo, parameters);
console.log('Credit calculation:', {
model,
parameters,
modelInfo: {
pricing: modelInfo.pricing,
max_tokens: modelInfo.max_tokens,
},
expectedCost,
});
};
// Ensure credits are only deducted on success
const processImageGenerationWithCredits = async (quest: Quest, user: User) => {
const originalCredits = user.currentCredits;
try {
// Generate image
const result = await generateImage(quest);
// Only deduct credits on successful generation
if (result.success && quest.creditsUsed) {
user.currentCredits = originalCredits - quest.creditsUsed;
await updateUser(user);
// Log credit transaction
await logCreditTransaction({
userId: user.id,
amount: -quest.creditsUsed,
type: 'image_generation',
metadata: {
questId: quest.id,
model: quest.model,
success: true,
},
});
}
return result;
} catch (error) {
// Ensure no credits deducted on failure
console.log('Generation failed, no credits deducted');
throw error;
}
};
Debugging Tools
1. Debug API Endpoint
Create a debug endpoint for troubleshooting:
// packages/client/pages/api/debug/image-generation.ts
import { baseApi } from '@server/middlewares/baseApi';
const handler = baseApi().get(async (req, res) => {
const { questId, userId } = req.query;
try {
// Gather debug information
const debugInfo = {
quest: await db.quests.findById(questId),
user: await db.users.findById(userId),
connections: await db.connections.findByUserId(userId),
apiKeys: await getApiKeyStatus(userId),
systemStatus: await getSystemStatus(),
};
return res.json({ debug: debugInfo });
} catch (error) {
return res.status(500).json({ error: error.message });
}
});
export default handler;
2. Frontend Debug Component
// Debug component for development
const ImageGenerationDebugger = ({ questId }: { questId: string }) => {
const [debugInfo, setDebugInfo] = useState(null);
const { user } = useAuth();
const loadDebugInfo = async () => {
try {
const response = await api.get('/api/debug/image-generation', {
params: { questId, userId: user.id },
});
setDebugInfo(response.data.debug);
} catch (error) {
console.error('Failed to load debug info:', error);
}
};
if (process.env.NODE_ENV !== 'development') {
return null;
}
return (
<Box border="1px solid red" p={2} mt={2}>
<Typography level="h4" color="danger">Debug Info</Typography>
<Button onClick={loadDebugInfo} size="sm">Load Debug Info</Button>
{debugInfo && (
<pre style={{ fontSize: '12px', overflow: 'auto' }}>
{JSON.stringify(debugInfo, null, 2)}
</pre>
)}
</Box>
);
};
3. Logging Enhancement
// Enhanced logging for image generation
const enhancedLogger = {
logImageGeneration: (stage: string, data: any) => {
console.log(`[IMAGE_GEN:${stage}]`, {
timestamp: new Date().toISOString(),
stage,
...data,
});
},
logError: (error: Error, context: any) => {
console.error('[IMAGE_GEN:ERROR]', {
timestamp: new Date().toISOString(),
error: {
message: error.message,
stack: error.stack,
name: error.name,
},
context,
});
},
logPerformance: (operation: string, duration: number, metadata: any) => {
console.log(`[IMAGE_GEN:PERF]`, {
timestamp: new Date().toISOString(),
operation,
duration,
metadata,
});
},
};
Prevention Strategies
1. Input Validation
// Comprehensive input validation
const validateImageGenerationRequest = (body: any) => {
const errors: string[] = [];
// Model validation
if (!IMAGE_MODELS.includes(body.model)) {
errors.push(`Invalid model: ${body.model}`);
}
// Provider-specific validation
if (BFL_IMAGE_MODELS.includes(body.model)) {
if (body.safety_tolerance < 0 || body.safety_tolerance > 6) {
errors.push('BFL safety tolerance must be between 0 and 6');
}
}
// Size validation
if (body.size && !isValidSizeForModel(body.model, body.size)) {
errors.push(`Invalid size ${body.size} for model ${body.model}`);
}
// Prompt validation
if (!body.prompt || body.prompt.trim().length === 0) {
errors.push('Prompt is required');
}
if (body.prompt.length > 4000) {
errors.push('Prompt too long (max 4000 characters)');
}
if (errors.length > 0) {
throw new ValidationError(errors.join(', '));
}
};
2. Circuit Breaker Pattern
// Circuit breaker for provider APIs
class ProviderCircuitBreaker {
private failures = 0;
private lastFailureTime = 0;
private isOpen = false;
constructor(
private maxFailures = 5,
private timeout = 60000 // 1 minute
) {}
async execute<T>(fn: () => Promise<T>): Promise<T> {
if (this.isOpen) {
if (Date.now() - this.lastFailureTime > this.timeout) {
this.reset();
} else {
throw new Error('Circuit breaker is OPEN');
}
}
try {
const result = await fn();
this.reset();
return result;
} catch (error) {
this.recordFailure();
throw error;
}
}
private recordFailure() {
this.failures++;
this.lastFailureTime = Date.now();
if (this.failures >= this.maxFailures) {
this.isOpen = true;
}
}
private reset() {
this.failures = 0;
this.isOpen = false;
this.lastFailureTime = 0;
}
}
3. Health Checks
// Provider health check system
const checkProviderHealth = async () => {
const providers = ['openai', 'bfl', 'midjourney'];
const results = {};
for (const provider of providers) {
try {
const startTime = Date.now();
await testProviderConnection(provider);
const duration = Date.now() - startTime;
results[provider] = {
status: 'healthy',
responseTime: duration,
timestamp: new Date().toISOString(),
};
} catch (error) {
results[provider] = {
status: 'unhealthy',
error: error.message,
timestamp: new Date().toISOString(),
};
}
}
return results;
};
Monitoring and Alerts
Key Metrics to Monitor
- Generation Success Rate: Track by provider and model
- Average Generation Time: Monitor performance trends
- Error Rates: Track different error types
- Credit Usage: Monitor for unexpected spikes
- WebSocket Connection Health: Track connection stability
- Storage Usage: Monitor S3 usage and costs
Alert Conditions
- Generation success rate drops below 95%
- Average generation time exceeds 60 seconds
- Error rate exceeds 5%
- WebSocket disconnection rate exceeds 1%
- Credit usage anomalies detected
This troubleshooting guide should help resolve most common issues with the image generation system. For complex issues, check the server logs and use the debugging tools provided.