Spaces:
Running
Running
File size: 7,181 Bytes
09801ca | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 | """
PURE LLM-DRIVEN VISUALIZATION - ZERO HARDCODING
=================================================
This is the ULTIMATE autonomous chart system.
The LLM generates the COMPLETE Plotly configuration.
NO predefined chart types.
NO hardcoded generators.
LLM decides EVERYTHING based on:
1. User's query
2. User's actual data
3. What makes sense for the visualization
Just like ChatGPT - pure intelligence, pure autonomy.
"""
import json
import logging
from typing import Optional
import pandas as pd
from core.llm import chat
logger = logging.getLogger(__name__)
def generate_visualization_pure_llm(query: str, df: pd.DataFrame) -> Optional[str]:
"""
100% LLM-DRIVEN VISUALIZATION.
The LLM decides:
- IF a chart should be generated
- WHAT type of chart
- WHICH columns to use
- HOW to structure the data
- ALL styling and configuration
Returns complete Plotly JSON - no hardcoding!
"""
if df is None or df.empty:
return None
try:
# Prepare data context for LLM
columns = list(df.columns)
dtypes = {col: str(df[col].dtype) for col in columns}
sample_data = df.head(10).to_dict(orient='records')
row_count = len(df)
# For numeric columns, get basic stats
numeric_summary = {}
for col in df.select_dtypes(include=['int64', 'float64']).columns:
numeric_summary[col] = {
"min": float(df[col].min()),
"max": float(df[col].max()),
"sum": float(df[col].sum()),
"mean": float(df[col].mean())
}
# For categorical columns, get value counts
categorical_summary = {}
for col in df.select_dtypes(include=['object']).columns[:5]:
categorical_summary[col] = df[col].value_counts().head(10).to_dict()
# THE MASTER PROMPT - LLM does EVERYTHING
prompt = f"""You are an expert data visualization AI. Generate a Plotly chart configuration.
USER REQUEST: "{query}"
DATA AVAILABLE:
- Columns: {columns}
- Data Types: {dtypes}
- Total Rows: {row_count}
- Sample Data: {json.dumps(sample_data[:5], default=str)[:1000]}
NUMERIC DATA SUMMARY:
{json.dumps(numeric_summary, indent=2)[:800]}
CATEGORICAL DATA SUMMARY:
{json.dumps(categorical_summary, default=str, indent=2)[:800]}
YOUR TASK:
1. Analyze what the user is asking for
2. Decide the BEST visualization (or no chart if not needed)
3. Generate a COMPLETE Plotly chart configuration using the ACTUAL data values above
IMPORTANT RULES:
- Use ONLY the actual data values shown above
- Choose the visualization that best answers the user's question
- Include proper titles, labels, and styling
- Use dark theme colors (paper_bgcolor: "rgba(0,0,0,0)", font color: "#9ca3af")
- Accent colors: teal (#14b8a6), indigo (#6366f1), amber (#f59e0b)
- For pie/donut charts, use "labels" and "values" (NEVER "x" and "y") in the data array.
Return your response in this EXACT format:
{{
"should_generate": true or false,
"visualization_type": "what you chose and why",
"plotly_chart": {{COMPLETE PLOTLY JSON HERE}}
}}
If no chart is needed, return:
{{"should_generate": false, "reason": "why no chart needed"}}
Generate the visualization:"""
response = chat(prompt, temperature=0.2, max_tokens=2000)
# Parse the response
response = response.strip()
# Find JSON in response
if '```json' in response:
response = response.split('```json')[1].split('```')[0]
elif '```' in response:
response = response.split('```')[1].split('```')[0]
# Find the JSON object
start = response.find('{')
end = response.rfind('}') + 1
if start >= 0 and end > start:
response = response[start:end]
result = json.loads(response)
# Check if we should generate
if not result.get("should_generate", True):
logger.info(f"LLM decided not to generate chart: {result.get('reason', 'No reason')}")
return None
# Get the Plotly chart configuration
plotly_config = result.get("plotly_chart", {})
if not plotly_config or "data" not in plotly_config:
logger.warning("LLM returned invalid Plotly config")
return None
# Ensure dark theme styling
if "layout" not in plotly_config:
plotly_config["layout"] = {}
plotly_config["layout"]["paper_bgcolor"] = "rgba(0,0,0,0)"
plotly_config["layout"]["plot_bgcolor"] = "rgba(0,0,0,0)"
plotly_config["layout"]["font"] = {"color": "#9ca3af"}
if "title" in plotly_config["layout"]:
if isinstance(plotly_config["layout"]["title"], str):
plotly_config["layout"]["title"] = {
"text": plotly_config["layout"]["title"],
"font": {"color": "#fff"}
}
elif isinstance(plotly_config["layout"]["title"], dict):
plotly_config["layout"]["title"]["font"] = {"color": "#fff"}
logger.info(f"LLM generated {result.get('visualization_type', 'chart')}")
return f"\n\n```plotly_chart\n{json.dumps(plotly_config)}\n```"
except json.JSONDecodeError as e:
logger.error(f"Failed to parse LLM response as JSON: {e}")
return None
except Exception as e:
logger.error(f"Pure LLM visualization error: {e}")
return None
def should_visualize(query: str) -> bool:
"""
Quick check if query likely needs visualization.
This is just a hint - LLM makes final decision.
"""
viz_hints = [
'chart', 'graph', 'plot', 'visualize', 'show', 'display',
'pie', 'bar', 'line', 'scatter', 'distribution',
'top', 'bottom', 'compare', 'trend', 'breakdown',
'heatmap', 'histogram', 'funnel', 'gauge', 'mindmap',
'how many', 'what is the total', 'percentage',
'img', 'image', 'picture', 'dashboard', 'report'
]
query_lower = query.lower()
return any(hint in query_lower for hint in viz_hints)
def generate_smart_chart(query: str, df: pd.DataFrame) -> Optional[str]:
"""
Main entry point for smart chart generation.
This is 100% autonomous:
1. Checks if visualization might be needed
2. Calls LLM to generate complete chart
3. Returns Plotly JSON or None
"""
# Quick check for visualization hints
if not should_visualize(query):
# Still let LLM decide - it might choose to visualize anyway
pass
return generate_visualization_pure_llm(query, df)
# Backwards compatibility aliases
def smart_chart(query: str, df: pd.DataFrame) -> Optional[str]:
"""Alias for generate_smart_chart for backwards compatibility"""
return generate_smart_chart(query, df)
def get_color_palette_from_query(query: str) -> list:
"""Get color palette - returns default teal/indigo theme"""
return ["#14b8a6", "#6366f1", "#f59e0b", "#ef4444", "#8b5cf6", "#06b6d4", "#84cc16", "#f97316"]
|