A multi-agent AI system that converts natural language questions into SQL queries, executes them against a PostgreSQL database, and generates interactive visualizations with AI-powered insights.
Dynamic Query Visualizer uses an agentic architecture with LangGraph to orchestrate multiple specialized agents:
- SQL Agent: Converts natural language to PostgreSQL queries with error correction
- Visualization Agent: Generates Python code for interactive charts using Plotly
- Report Agent: Creates AI-driven summaries and insights
The system leverages GPT-4o for intelligent query generation and correction, ensuring robust SQL execution even when initial queries fail.
Dynamic-Query-Visualization/
├── agent/
│ ├── sql_react_agent.py # SQL generation & correction agent
│ ├── initiate_llm.py # LLM initialization (GPT-4o)
│ └── final_supervisor_agent_report.py # Orchestrator agent
├── app.py # Flask web application
├── static/
│ ├── images/ # Generated visualizations
│ └── css/
│ ├── chat_style.css
│ └── dashboard.css
├── templates/
│ ├── chat.html # Chat interface
│ ├── dashboard.html # Results dashboard
│ └── reports.html # Reports view
├── requirements.txt
└── README.md
- LangGraph: Agentic workflow orchestration with state management
- LangChain: LLM integration and database tooling
- GPT-4o: Natural language understanding & SQL/Python code generation
- PostgreSQL: Database backend
- Flask: Web framework
- Plotly: Interactive visualizations
- Pydantic: Structured output schemas
The system uses a multi-agent orchestrator pattern:
- Each agent has a specific responsibility (SQL, visualization, reporting)
- Agents communicate via shared state (messages, query results, dataframes)
- The supervisor agent routes tasks and aggregates results
- AgentState: Central state object containing message history and execution context
- add_messages: Reducer that accumulates messages (prevents token loss)
- Command-based Routing: Nodes conditionally route to other nodes or END based on execution results
- Uses Pydantic models (
SQLOutput) with.with_structured_output() - Ensures LLM responses conform to expected schema (no parsing errors)
- Enables type-safe downstream processing
- SQL Node: Generates initial query; if execution fails, passes error to correction node
- Correction Loop: LLM analyzes error message and database schema, produces corrected SQL
- Graceful Degradation: Reports execution errors to user if all retries fail
- All agent outputs wrapped in
ToolMessagewith JSON content - Maintains full audit trail of generation → execution → correction → visualization
- Enables debugging and result tracking
- Python 3.10+
- A PostgreSQL database (Supabase / Neon supported out of the box)
- OpenAI API key
-
Clone the repository
cd /Users/mehulmathur/AI\ Projects/Dynamic\ Query\ Visualizer/Dynamic-Query-Visualization
-
Create virtual environment
python3 -m venv venv source venv/bin/activate -
Install dependencies
pip install -r requirements.txt
-
Configure environment variables (optional) Create a
.envfile in the project root if you want defaults:# Used to sign the Flask session cookie FLASK_SECRET_KEY=dev-secret-change-in-production # Optional: restrict which hosts users can connect to via db_url # ALLOWED_DB_HOST_SUFFIXES=supabase.co,neon.tech
python app.pyVisit http://localhost:5000 in your browser. Enter natural language questions and view:
- Generated SQL query
- Query results as interactive table
- AI-generated visualization
- Insights summary
On first load, the UI will prompt for:
db_url: your PostgreSQL connection string (e.g. Supabase/Neon)db_password(optional): if your URL has no passwordschema_name(optional): defaults to the database default schema- OpenAI API key
The app accepts standard PostgreSQL connection URLs that SQLAlchemy can use.
Supabase (direct connection)
postgresql://postgres@db.<project-ref>.supabase.co:5432/postgres?sslmode=require
Neon (direct connection)
postgresql://<user>@<endpoint>.neon.tech/<db>?sslmode=require
Password handling (important)
- Recommended: leave the password out of
db_urland put it into the UI’sdb_passwordfield. - If you embed the password in the URL, it must be URL-encoded (e.g.
#must be%23).
Schema handling
- If your tables live in a non-
publicschema (e.g.analytical_schema), setschema_name=analytical_schemain the UI.
python3 -c "
from agent.sql_react_agent import graph
from langchain_core.messages import HumanMessage
state = {'messages': [HumanMessage(content='What is the average salary by department?')]}
result = graph.invoke(state)
for msg in result['messages']:
msg.pretty_print()
"gen_sql_node
↓ (generates SQLOutput)
exec_sql_node
↓ (executes query, returns result)
check_node
├→ if error: corrects SQL, loops back to exec_sql
└→ if success: returns END
Key Features:
- Structured LLM output with
.with_structured_output(SQLOutput) - Dynamic schema awareness (reads all table DDL)
- Error-aware correction using GPT-4o
- No prompt templates (values pre-filled for clarity)
SQLOutput Schema:
class SQLOutput(BaseModel):
sql_query: str = Field(description="The raw SQL query that answers the user's question")Consumes SQL results and generates Plotly code:
- Automatically selects chart type (bar, line, scatter, etc.)
- Handles numeric/categorical/time-series data
- Outputs
figobject for rendering
Aggregates results and generates insights:
- Summarizes query intent and results
- Identifies trends and outliers
- Provides business context
Question: "What is the average salary of employees each year for the past 5 years?"
Flow:
- SQL Agent generates:
SELECT EXTRACT(YEAR FROM hire_date) AS year, AVG(salary) FROM employees WHERE hire_date > CURRENT_DATE - INTERVAL '5 years' GROUP BY year ORDER BY year
- Executes against PostgreSQL
- Visualization Agent generates Plotly line chart code
- Report Agent writes summary: "Salary trends show X% growth over 5 years..."
- Flask renders results in web UI
Edit agent/initiate_llm.py:
gpt_llm = ChatOpenAI(model="gpt-4o") # or "gpt-4-turbo", "gpt-3.5-turbo"The system auto-detects tables. To limit to specific schema:
db = SQLDatabase(engine, schema="analytical_schema")Modify retry logic in check_node() to adjust error correction attempts.
| Issue | Solution |
|---|---|
| "Database host is not allowed" | Use a Supabase/Neon host (or set ALLOWED_DB_HOST_SUFFIXES in .env / container env). |
| "Could not connect to the provided database URL" | Ensure the URL/user/password are correct; add ?sslmode=require; for special chars in passwords prefer db_password or URL-encode. |
| "Invalid API Key" | Ensure OPENAI_API_KEY is set in .env |
| SQL execution fails on first attempt | Check agent logs; correction node will retry automatically |
| Visualization not generating | Charts render in the browser via Plotly.js; check the browser console and confirm the response includes plotly_figure. |
- First query: ~3-5 seconds (LLM inference + SQL execution)
- Cached schema info reduces subsequent queries to ~2-3 seconds
- Large result sets (>10k rows) may slow visualization generation
- Multi-turn conversational context
- Query optimization suggestions
- Data privacy redaction filters
- Support for Snowflake, BigQuery backends
- Batch query scheduling
- Query result caching
MIT
Mehul Mathur
