""" Debug Analyzer - Uses Claude to identify gaps between source image and generated prompt. Only runs when DEBUG_ENABLED=true and ANTHROPIC_API_KEY is set in .env file. If not configured, gracefully skips without errors. """ import os import base64 from pathlib import Path from typing import Optional from PIL import Image import io def _load_env(): """Load .env file from toolkit root directory.""" env_path = Path(__file__).parent.parent / ".env" if not env_path.exists(): return {} env_vars = {} try: with open(env_path, "r", encoding="utf-8") as f: for line in f: line = line.strip() if not line or line.startswith("#"): continue if "=" in line: key, value = line.split("=", 1) env_vars[key.strip()] = value.strip() except Exception: pass return env_vars def _is_debug_enabled() -> bool: """Check if debug mode is enabled.""" env = _load_env() debug_enabled = env.get("DEBUG_ENABLED", "").lower() api_key = env.get("ANTHROPIC_API_KEY", "") # Both must be present and debug must be "true" return debug_enabled == "true" and api_key and api_key.startswith("sk-ant-") def _get_api_key() -> Optional[str]: """Get Anthropic API key from .env.""" env = _load_env() return env.get("ANTHROPIC_API_KEY") def _image_to_base64(image: Image.Image, max_size: int = 1024) -> str: """Convert PIL Image to base64 string, resizing if needed.""" # Resize if too large if image.width > max_size or image.height > max_size: ratio = min(max_size / image.width, max_size / image.height) new_size = (int(image.width * ratio), int(image.height * ratio)) image = image.resize(new_size, Image.Resampling.LANCZOS) # Convert to RGB if needed if image.mode != "RGB": image = image.convert("RGB") # Save to bytes buffer = io.BytesIO() image.save(buffer, format="JPEG", quality=85) buffer.seek(0) return base64.standard_b64encode(buffer.read()).decode("utf-8") def analyze_prompt_gaps( source_image: Image.Image, generated_prompt: str, metadata: Optional[dict] = None, verbose: bool = False, ) -> Optional[dict]: """ Use Claude to analyze gaps between source image and generated prompt. Args: source_image: The original image that was analyzed generated_prompt: The prompt generated by the synthesis node metadata: Optional metadata from image analysis stage verbose: Print debug info Returns: Dict with analysis results, or None if debug not enabled/configured """ if not _is_debug_enabled(): if verbose: print("[Debug] Debug mode not enabled or API key not configured") return None api_key = _get_api_key() if not api_key: return None try: import anthropic except ImportError: if verbose: print("[Debug] anthropic package not installed. Run: pip install anthropic") return None if verbose: print("[Debug] Running Claude analysis...") # Convert image to base64 image_b64 = _image_to_base64(source_image) # Build analysis prompt analysis_prompt = f"""Analyze this image and compare it to the generated prompt below. Identify any gaps, inaccuracies, or missing elements. GENERATED PROMPT: {generated_prompt} Please analyze and provide: 1. **INACCURACIES** - Things in the prompt that are WRONG (e.g., wrong colors, wrong garment types, wrong positions) 2. **MISSING ELEMENTS** - Important visible elements NOT mentioned in the prompt (e.g., exposed skin areas, accessories, headwear layers) 3. **HALLUCINATIONS** - Things mentioned in the prompt that are NOT visible in the image (e.g., "hands in pockets" when hands are visible elsewhere) 4. **COLOR ERRORS** - Any color descriptions that don't match (e.g., "white" when it's actually "gray") 5. **SUGGESTED CORRECTIONS** - Brief corrected descriptions for the main issues Be specific and concise. Focus on the most important gaps that would affect image regeneration accuracy.""" try: client = anthropic.Anthropic(api_key=api_key) message = client.messages.create( model="claude-sonnet-4-20250514", max_tokens=1500, messages=[ { "role": "user", "content": [ { "type": "image", "source": { "type": "base64", "media_type": "image/jpeg", "data": image_b64, }, }, { "type": "text", "text": analysis_prompt, }, ], } ], ) analysis_text = message.content[0].text if verbose: print(f"[Debug] Claude analysis complete ({len(analysis_text)} chars)") return { "analysis": analysis_text, "model": "claude-sonnet-4-20250514", "prompt_length": len(generated_prompt), } except anthropic.APIError as e: if verbose: print(f"[Debug] Anthropic API error: {e}") return {"error": str(e)} except Exception as e: if verbose: print(f"[Debug] Analysis error: {e}") return {"error": str(e)} def print_analysis(analysis: Optional[dict]) -> None: """Pretty print the analysis results.""" if not analysis: return if "error" in analysis: print(f"\n[Debug Analysis Error] {analysis['error']}") return print("\n" + "=" * 60) print("DEBUG ANALYSIS - Gaps in Generated Prompt") print("=" * 60) print(analysis.get("analysis", "No analysis available")) print("=" * 60 + "\n")