A SQL Database Agent built using the ReAct (Reasoning + Acting) pattern with a clean Flask backend + Streamlit frontend architecture. Query your database using natural language powered by LLMs.
Clean Separation Design:
- Backend (Flask API): Handles agent logic, LLM calls, and database operations
- Frontend (Streamlit): Interactive UI for queries and results visualization
- No Threading Issues: Proper SQLite connection management per request
- Full Transparency: Complete ReAct loop (THOUGHT β ACTION β OBSERVATION) displayed
pip install flask flask-cors requests streamlit groq python-dotenvCreate a .env file in the project root:
GROQ_API_KEY=your-groq-api-key-herepython api.pyBackend runs on http://localhost:5000
python -m streamlit run app_frontend.pyFrontend opens at http://localhost:8501
- Auto-Initialization: The agent initializes automatically when you open the app.
- Enter Query: Type your question in natural language (e.g., "How many employees?").
- Run: Click "Run Query" to execute.
- View Reasoning: Toggle "Show Reasoning" to see the full ReAct loop (Thought β Action β Observation).
The agent connects to a company database with the following structure:
departments
idINTEGER PRIMARY KEYnameTEXT UNIQUElocationTEXT
employees
idINTEGER PRIMARY KEYnameTEXTemailTEXT UNIQUEdepartment_idINTEGER (β departments.id)salaryREALhire_dateTEXT (YYYY-MM-DD)
Sample Data: 3 departments, 8 employees
sql_react_agent/
βββ api.py # Flask backend API
βββ app_frontend.py # Streamlit frontend UI
βββ database/
β βββ schema.sql # Database schema definition
β βββ data.py # Sample data population
β βββ company.db # SQLite database file
βββ src/
β βββ tools.py # Database tools + SQL validation
β βββ prompts.py # System prompts + few-shot examples
β βββ agent.py # ReAct agent core implementation
βββ tests/
β βββ test_tools_manual.py # Database tool tests
β βββ test_prompts.py # Prompt and parser tests
βββ docs/
β βββ REACT_LOOP_INTERNALS.md
β βββ LANGCHAIN_INTERNALS.md
β βββ API_SAFETY.md
βββ logs/ # Test execution logs
- Manual ReAct loop without using agent frameworks
- LLM integration with Groq and Google Gemini support
- Conversation history management
- Tool execution with injected observations
- Hallucination prevention via stop sequences
- Auto-Initialization: Seamless startup experience
- Reasoning Toggle: Option to view full internal reasoning steps or just the final answer
- Instant Execution: Optimized request handling for fast responses
- History Tracking: Session-based query history
- list_tables() - List all available tables
- describe_table(table_name) - Get table schema
- query_database(query) - Execute SQL SELECT queries
- extract_full_schema() - Get complete database schema
- Keyword checking - Only SELECT statements allowed
- Blacklist filtering - Prevents DROP, DELETE, UPDATE, etc.
- Syntax validation - SQLite EXPLAIN check before execution
- Auto-LIMIT - Automatic LIMIT 100 to prevent data overload
- SQL injection prevention - Parameterized queries
- Role-based system prompt
- Auto-generated tool descriptions
- Few-shot examples demonstrating proper ReAct format
- Clear output format instructions
- Turn-by-turn interaction patterns
- Rate limiting (20 seconds between queries)
- Request timeouts with error recovery
- Exponential backoff on failures
- Connection error detection and handling
Simple:
"How many employees are in the database?"
"How many departments do we have?"
Medium Complexity:
"What's the average salary by department?"
"List all employees in the Engineering department."
Complex:
"Who is the highest paid employee and which department are they in?"
"Which department has the highest average salary?"
"Compare total salary expenses across all departments."
GET /health
Response: {"status": "healthy"}POST /init
Request: {"db_path": "database/company.db"}
Response: {"status": "initialized", "db_path": "...", "schema": "..."}POST /query
Request: {"query": "How many employees?"}
Response: {
"final_answer": "There are 8 employees in the database.",
"steps": [
{
"iteration": 1,
"type": "REACT_CYCLE",
"thought": "I need to count the rows in employees table...",
"action": "query_database",
"action_params": {"query": "SELECT COUNT(*) FROM employees"},
"observation": "Query returned 1 row(s): count = 8",
"raw_llm_response": "THOUGHT: I need to count...\nACTION: ..."
}
],
"iterations": 1,
"status": "success"
}# Test prompt generation and parsers (no API calls)
python tests/test_prompts.py
# Test database tools manually
python tests/test_tools_manual.py- β Prompt template building
- β ACTION parsing with single-brace JSON
- β Complex SQL query parsing
- β FINAL ANSWER detection
- β THOUGHT vs FINAL ANSWER distinction
- β Database tool functionality
- 20-second delay between queries
- Configurable rate limits
- Prevents API quota exhaustion
- Whitelist-based query validation
- Syntax pre-validation
- Read-only enforcement
- Auto-limiting result sets
- Connection timeout management
- Graceful API error recovery
- Detailed error logging
Detailed Guides:
docs/REACT_LOOP_INTERNALS.md- How the ReAct pattern worksdocs/LANGCHAIN_INTERNALS.md- LangChain framework comparisondocs/API_SAFETY.md- Rate limiting strategies
- Backend: Flask, SQLite
- Frontend: Streamlit
- LLM Providers: Groq, Google Gemini
- Language: Python 3.8+
- User asks a question in natural language
- LLM generates THOUGHT (reasoning) and ACTION (tool call)
- System executes the tool (e.g., SQL query)
- System injects OBSERVATION back to LLM
- LLM continues reasoning or provides FINAL ANSWER
- Repeat until answer is found
User: "How many employees are in Engineering?"
Agent (LLM):
THOUGHT: Need to find department_id for Engineering first
ACTION: query_database{"query": "SELECT id FROM departments WHERE name='Engineering'"}
System:
OBSERVATION: Query returned 1 row: id=1
Agent (LLM):
THOUGHT: Now count employees with department_id=1
ACTION: query_database{"query": "SELECT COUNT(*) FROM employees WHERE department_id=1"}
System:
OBSERVATION: Query returned 1 row: count=3
Agent (LLM):
FINAL ANSWER: There are 3 employees in the Engineering department.
Backend (api.py, src/*.py):
- Flask auto-reloads in debug mode
- Changes take effect immediately
Frontend (app_frontend.py):
- Streamlit auto-reloads on file save
- Refresh browser to see changes
Database (database/):
- Modify
schema.sqlordata.pyand regenerate
Contributions welcome! Areas for improvement:
- Additional database tools
- Advanced SQL query support
- Multiple database backend support
- Enhanced UI features
- Additional LLM provider integrations
Built as part of the Vizuara assignment.
Tech Stack: Flask β’ Streamlit β’ Groq β’ SQLite