Vivek0912 commited on
Commit
69e9d44
·
1 Parent(s): 5f5ea54

initial commit for huggingface

Browse files
.gitignore ADDED
@@ -0,0 +1,168 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ env/
12
+ build/
13
+ develop-eggs/
14
+ dist/
15
+ downloads/
16
+ eggs/
17
+ .eggs/
18
+ lib/
19
+ lib64/
20
+ parts/
21
+ sdist/
22
+ var/
23
+ *.egg-info/
24
+ .installed.cfg
25
+ *.egg
26
+
27
+ # PyInstaller
28
+ # Usually these files are written by a python script from a template
29
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
30
+ *.manifest
31
+ *.spec
32
+
33
+ # Installer logs
34
+ debug.log
35
+ pip-log.txt
36
+ pip-delete-this-directory.txt
37
+
38
+ # Unit test / coverage reports
39
+ htmlcov/
40
+ .tox/
41
+ .nox/
42
+ .coverage
43
+ .coverage.*
44
+ .cache
45
+ nosetests.xml
46
+ coverage.xml
47
+ *.cover
48
+ .hypothesis/
49
+ .pytest_cache/
50
+
51
+ # Translations
52
+ *.mo
53
+ *.pot
54
+
55
+ # Django stuff:
56
+ *.log
57
+ local_settings.py
58
+ db.sqlite3
59
+
60
+ # Flask stuff:
61
+ instance/
62
+ .webassets-cache
63
+
64
+ # Scrapy stuff:
65
+ .scrapy
66
+
67
+ # Sphinx documentation
68
+ docs/_build/
69
+
70
+ # PyBuilder
71
+ .target/
72
+
73
+ # Jupyter Notebook
74
+ .ipynb_checkpoints
75
+
76
+ # IPython
77
+ profile_default/
78
+ ipython_config.py
79
+
80
+ # pyenv
81
+ .python-version
82
+
83
+ # pipenv
84
+ Pipfile.lock
85
+
86
+ # poetry
87
+ poetry.lock
88
+
89
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow
90
+ __pypackages__/
91
+
92
+ # Celery stuff
93
+ celerybeat-schedule
94
+ celerybeat.pid
95
+
96
+ # SageMath parsed files
97
+ *.sage.py
98
+
99
+ # Environments
100
+ .env
101
+ .venv
102
+ env/
103
+ venv/
104
+ ENV/
105
+ env.bak/
106
+ venv.bak/
107
+
108
+ # Spyder project settings
109
+ .spyderproject
110
+ .spyderproject.*
111
+
112
+ # Rope project settings
113
+ .ropeproject
114
+
115
+ # mkdocs documentation
116
+ /site
117
+
118
+ # mypy
119
+ .mypy_cache/
120
+ .dmypy.json
121
+
122
+ # Pyre type checker
123
+ .pyre/
124
+
125
+ # pytype static type analyzer
126
+ .pytype/
127
+
128
+ # Cython debug symbols
129
+ cython_debug/
130
+
131
+ # VS Code
132
+ .vscode/
133
+
134
+ # Windows
135
+ Thumbs.db
136
+ Desktop.ini
137
+
138
+ # macOS
139
+ .DS_Store
140
+ .AppleDouble
141
+ .LSOverride
142
+
143
+ # Linux
144
+ *~
145
+
146
+ # Test artifacts
147
+ test/
148
+ artifacts/
149
+
150
+ # Database
151
+ *.db
152
+ *.sqlite
153
+
154
+ # Logs
155
+ *.log
156
+
157
+ # Excel
158
+ *.xlsx
159
+ *.xls
160
+
161
+ # Misc
162
+ *.bak
163
+ *.tmp
164
+ *.swp
165
+ *.swo
166
+
167
+ # Python virtual environment
168
+ backend_env/
backend/.dockerfile ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Use the official Python 3.11.5 image
2
+ FROM python:3.11.5
3
+
4
+ # Set the working directory
5
+ WORKDIR /app
6
+
7
+ # Copy the backend folder into the container
8
+ COPY backend /app
9
+
10
+ # Install dependencies
11
+ RUN pip install --no-cache-dir -r requirements.txt
12
+
13
+ # Expose the port FastAPI runs on
14
+ EXPOSE 7860
15
+
16
+ # Run FastAPI app
17
+ CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "7860"]
backend/app/api/endpoints.py ADDED
@@ -0,0 +1,82 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from langchain_openai import ChatOpenAI
2
+ from config.settings import settings
3
+
4
+ # --- LLM-based classifier for user input ---
5
+
6
+ import pandas as pd
7
+ # Use LLMClassifier from services.utility
8
+ from app.services.utility import UtilityClass
9
+ from fastapi import APIRouter
10
+ from pydantic import BaseModel
11
+ from app.services.sql_generator import generate_sql_query
12
+ from app.services.query_executor import execute_sql_query
13
+ from app.services.result_formatter import df_to_chart
14
+ from app.services.query_executor import execute_sql_query
15
+ from app.db.schema_reader import get_schema
16
+ from app.services.query_executor import run_and_handle_sql_query
17
+
18
+ router = APIRouter()
19
+
20
+ class QueryRequest(BaseModel):
21
+ question: str
22
+
23
+ from fastapi import HTTPException
24
+
25
+ @router.post("/ask")
26
+ def ask_query(req: QueryRequest):
27
+ try:
28
+ sql = generate_sql_query(req.question)
29
+
30
+ # If generate_sql_query returns a chat_message, treat as normal chat or empty SQL result
31
+ if isinstance(sql, dict) and "chat_message" in sql:
32
+ return {"message": sql["chat_message"]}
33
+
34
+
35
+ # Use utility method to check if the string is a valid SQL query
36
+ formattedSqlQuery = UtilityClass.is_valid_sql_query(sql)
37
+ if not formattedSqlQuery:
38
+ return {"message": str(sql) if sql else "No SQL query could be generated for your question."}
39
+
40
+
41
+ df = run_and_handle_sql_query(formattedSqlQuery, req.question)
42
+
43
+ # If run_and_handle_sql_query returns a chat_message (for empty SQL results), return it
44
+ if isinstance(df, dict) and "chat_message" in df:
45
+ return {"message": df["chat_message"]}
46
+
47
+ chart = None
48
+ # if len(df) > 0 and isinstance(df, list) and len(df[0]) > 0 and len(df[0].keys()) >= 2:
49
+ # chart = df_to_chart(pd.DataFrame(df))
50
+
51
+ # Prepare heading and records JSON using LLM
52
+ result_json = UtilityClass.prepare_llm_heading_and_records(req.question, df)
53
+
54
+ return {
55
+ "sql": sql,
56
+ "rows": result_json["records"],
57
+ "heading": result_json["heading"],
58
+ "chart": chart
59
+ }
60
+ except Exception as e:
61
+ raise HTTPException(status_code=400, detail=f"An error occurred: {str(e)}")
62
+
63
+ @router.get("/schema")
64
+ def read_schema():
65
+ return get_schema()
66
+
67
+ @router.get("/query")
68
+ def run_query(question: str):
69
+ try:
70
+ sql_query = generate_sql_query(question)
71
+ results = execute_sql_query(sql_query)
72
+ return {
73
+ "question": question,
74
+ "sql_query": sql_query,
75
+ "results": results
76
+ }
77
+ except Exception as e:
78
+ raise HTTPException(status_code=400, detail=f"An error occurred: {str(e)}")
79
+
80
+ @router.get("/")
81
+ def root():
82
+ return {"message": "Welcome to AI SQL Query Generator!"}
backend/app/db/db_connector.py ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from sqlalchemy import create_engine
2
+ from sqlalchemy.engine import Engine
3
+ from sqlalchemy.orm import sessionmaker
4
+ import sqlite3
5
+ from pathlib import Path
6
+ import os
7
+ from dotenv import load_dotenv
8
+ load_dotenv()
9
+
10
+ # Path to SQLite DB file, configurable via environment variable or .env
11
+ print("DB_PATH from env:", os.getenv("DB_PATH"))
12
+ DB_PATH = os.getenv("DB_PATH")
13
+ if not DB_PATH:
14
+ BASE_DIR = os.path.dirname(os.path.abspath(__file__))
15
+ DB_PATH = os.path.join(BASE_DIR, "BankingDb.db")
16
+
17
+ DATABASE_URL = f"sqlite:///{DB_PATH}"
18
+
19
+ def get_connection():
20
+ db_path = DB_PATH
21
+ # Only allow connection if DB file exists
22
+ if not os.path.isfile(db_path):
23
+ raise FileNotFoundError(f"Database file not found at {db_path}. Set DB_PATH env variable or .env to the correct location.")
24
+ try:
25
+ conn = sqlite3.connect(db_path, timeout=30, check_same_thread=False)
26
+ conn.row_factory = sqlite3.Row # access columns by name
27
+ return conn
28
+ except sqlite3.OperationalError as e:
29
+ raise Exception(f"Error connecting to DB at {db_path}: {e}")
30
+
31
+ # Create engine
32
+ engine = create_engine(
33
+ DATABASE_URL,
34
+ connect_args={"check_same_thread": False} # Needed for SQLite threading
35
+ )
36
+
37
+ # Session factory
38
+ SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
39
+
40
+ def get_db_engine():
41
+ """Return SQLAlchemy engine (used by LangChain SQLDatabase)."""
42
+ return engine
43
+
44
+
45
+ def get_db():
46
+ """Provide DB session for queries."""
47
+ db = SessionLocal()
48
+ try:
49
+ yield db
50
+ finally:
51
+ db.close()
backend/app/db/schema_reader.py ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from sqlalchemy import inspect
2
+ from .db_connector import get_db_engine
3
+ from .db_connector import get_connection
4
+
5
+ def get_schema():
6
+ """Fetch database schema information (tables & columns)."""
7
+ schema = {}
8
+ conn = get_connection()
9
+ try:
10
+ cursor = conn.cursor()
11
+
12
+ # Get all tables
13
+ cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%';")
14
+ tables = cursor.fetchall()
15
+
16
+ for t in tables:
17
+ table_name = t[0]
18
+ cursor.execute(f"PRAGMA table_info({table_name});")
19
+ columns = cursor.fetchall()
20
+ schema[table_name] = [col[1] for col in columns]
21
+
22
+ return schema
23
+ finally:
24
+ cursor.close()
25
+ conn.close()
26
+
27
+ def get_schema2():
28
+ """Fetch database schema information (tables & columns)."""
29
+ engine = get_db_engine()
30
+ inspector = inspect(engine)
31
+
32
+ schema = {}
33
+ for table_name in inspector.get_table_names():
34
+ columns = [
35
+ {"name": col["name"], "type": str(col["type"])}
36
+ for col in inspector.get_columns(table_name)
37
+ ]
38
+ schema[table_name] = columns
39
+
40
+ return schema
41
+
42
+ # def get_schema():
43
+ # engine = get_db_engine()
44
+ # inspector = inspect(engine)
45
+
46
+ # schema_info = {}
47
+ # for table in inspector.get_table_names():
48
+ # columns = [col["name"] for col in inspector.get_columns(table)]
49
+ # schema_info[table] = columns
50
+ # return schema_info
backend/app/services/query_executor.py ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pandas as pd
2
+ from sqlalchemy import text
3
+ from app.db.db_connector import get_db_engine
4
+
5
+
6
+
7
+ from langchain_openai import ChatOpenAI
8
+ from config.settings import settings
9
+
10
+ def execute_sql_query(sql_query: str):
11
+ """Run a SQL query and return results as list of dicts."""
12
+ engine = get_db_engine()
13
+ with engine.connect() as conn:
14
+ result = conn.execute(text(sql_query))
15
+ rows = [dict(row) for row in result.mappings()]
16
+ return rows
17
+
18
+ def run_and_handle_sql_query(sql_query: str, user_question: str):
19
+ """
20
+ Executes the SQL query, and if no results, uses LLM to generate a friendly message.
21
+ Returns either a list of dicts (rows) or {"chat_message": ...}.
22
+ """
23
+ try:
24
+ rows = execute_sql_query(sql_query)
25
+ if not rows:
26
+ llm = ChatOpenAI(model="gpt-4o-mini", temperature=0, api_key=settings.OPENAI_API_KEY)
27
+ no_result_prompt = (
28
+ f"The following SQL query was generated for the user's question, but it returned no results. "
29
+ f"User question: {user_question}\nSQL query: {sql_query}\n"
30
+ "Please explain to the user in a friendly way that no matching records were found for their request."
31
+ )
32
+ no_result_response = llm.invoke([{"role": "user", "content": no_result_prompt}]).content.strip()
33
+ return {"chat_message": no_result_response}
34
+ return rows
35
+ except Exception as e:
36
+ return {"error": str(e), "sql_query": sql_query}
37
+
38
+ # def run_query(sql: str):
39
+ # engine = get_db_engine()
40
+ # with engine.connect() as conn:
41
+ # result = conn.execute(text(sql))
42
+ # df = pd.DataFrame(result.fetchall(), columns=result.keys())
43
+ # return df
backend/app/services/result_formatter.py ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import matplotlib.pyplot as plt
2
+ import io
3
+ import base64
4
+
5
+ def df_to_chart(df, chart_type="bar"):
6
+ import pandas as pd
7
+ plt.clf()
8
+ y_col = df.columns[1] if len(df.columns) > 1 else None
9
+ if y_col is not None and pd.api.types.is_numeric_dtype(df[y_col]):
10
+ # Numeric y-column: plot as usual
11
+ ax = df.plot(kind=chart_type, x=df.columns[0], y=y_col, legend=False)
12
+ else:
13
+ # Non-numeric: plot value counts of the first column
14
+ value_counts = df[df.columns[0]].value_counts()
15
+ ax = value_counts.plot(kind="bar")
16
+ ax.set_xlabel(df.columns[0])
17
+ ax.set_ylabel("Count")
18
+ buf = io.BytesIO()
19
+ plt.savefig(buf, format="png")
20
+ buf.seek(0)
21
+ return base64.b64encode(buf.read()).decode("utf-8")
backend/app/services/sql_generator.py ADDED
@@ -0,0 +1,60 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from langchain_community.utilities import SQLDatabase
2
+ from langchain.chains import create_sql_query_chain
3
+ from langchain_openai import ChatOpenAI # or your LLaMA adapter
4
+ from config.settings import settings
5
+ from langchain.prompts import PromptTemplate, ChatPromptTemplate
6
+ from app.db.schema_reader import get_schema
7
+ from app.db.db_connector import get_db_engine
8
+ from app.db.db_connector import get_connection
9
+
10
+ # llm = ChatOpenAI(model="gpt-4", temperature=0, api_key=settings.OPENAI_API_KEY)
11
+ llm = ChatOpenAI(model="gpt-4o-mini", temperature=0, api_key=settings.OPENAI_API_KEY)
12
+
13
+
14
+ def generate_sql_query(user_question: str):
15
+ schema = get_schema()
16
+ schema_str = "\n".join(
17
+ [f"{table}: {', '.join(cols)}" for table, cols in schema.items()]
18
+ )
19
+
20
+ prompt = ChatPromptTemplate.from_template("""
21
+ You are an expert SQLite query generator.
22
+ Use ONLY the following tables and columns:
23
+
24
+ {schema}
25
+
26
+ Generate a correct SQLite query (no explanations, only SQL).
27
+ Do not add single quotes or any quotes around table names or column names.
28
+ If the user asks for data, generate a SELECT query and that outcome should have only
29
+ sqlite compatible, don't include any other text.
30
+ If you cannot answer, say: "I cannot generate this query."
31
+
32
+ User question: {question}
33
+ """)
34
+
35
+ messages = prompt.format_messages(schema=schema_str, question=user_question)
36
+ sql_query = llm.invoke(messages).content.strip()
37
+
38
+ # If the LLM says it cannot generate a query, treat as normal chat
39
+ if sql_query.lower().startswith("i cannot generate this query"):
40
+ # Use the LLM as a chatbot for normal conversation
41
+ chat_prompt = f"You are a helpful assistant. Respond conversationally to: {user_question}"
42
+ chat_response = llm.invoke([{"role": "user", "content": chat_prompt}]).content.strip()
43
+ return {"chat_message": chat_response}
44
+
45
+ return sql_query
46
+
47
+ def generate_sql_query2(natural_language_query: str) -> str:
48
+ """Generate SQL query from natural language using LangChain."""
49
+ engine = get_db_engine()
50
+ db = SQLDatabase(engine)
51
+
52
+ # You can swap ChatOpenAI with LLaMA wrapper
53
+ llm = ChatOpenAI(model="gpt-4o-mini", temperature=0, api_key=settings.OPENAI_API_KEY)
54
+
55
+ sql_chain = create_sql_query_chain(llm, db)
56
+
57
+ sql_query = sql_chain.invoke({"question": natural_language_query})
58
+ return sql_query
59
+
60
+
backend/app/services/utility.py ADDED
@@ -0,0 +1,105 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import openai
2
+ from config.settings import settings
3
+ from langchain_openai import ChatOpenAI
4
+ import sqlparse
5
+ import re
6
+ class UtilityClass:
7
+ """
8
+ Classifies user input as 'sql' (SQL/data context) or 'chat' (normal conversation) using an LLM.
9
+ """
10
+ @staticmethod
11
+ def classify_user_input(text: str) -> str:
12
+ prompt = (
13
+ "Classify the following message as 'sql' if it is a database/data/SQL question, "
14
+ "or 'chat' if it is normal conversation.\n"
15
+ f"Message: {text}\n"
16
+ "Respond with only 'sql' or 'chat'."
17
+ )
18
+ response = openai.ChatCompletion.create(
19
+ model="gpt-3.5-turbo",
20
+ messages=[{"role": "user", "content": prompt}],
21
+ api_key=settings.OPENAI_API_KEY
22
+ )
23
+ label = response.choices[0].message.content.strip().lower()
24
+ return label if label in ("sql", "chat") else "chat"
25
+
26
+ @staticmethod
27
+ def chat_response(text: str) -> str:
28
+ chat_prompt = f"You are a helpful assistant. Respond conversationally to: {text}"
29
+ response = openai.ChatCompletion.create(
30
+ model="gpt-3.5-turbo",
31
+ messages=[{"role": "user", "content": chat_prompt}],
32
+ api_key=settings.OPENAI_API_KEY
33
+ )
34
+ return response.choices[0].message.content.strip()
35
+
36
+ # Helper to prepare JSON with heading from LLM and records
37
+ @staticmethod
38
+ def prepare_llm_heading_and_records(user_question, rows):
39
+ llm = ChatOpenAI(model="gpt-4o-mini", temperature=0, api_key=settings.OPENAI_API_KEY)
40
+ record_count = len(rows)
41
+ column_names = list(rows[0].keys()) if rows and isinstance(rows[0], dict) else []
42
+ heading_prompt = (
43
+ "Given the user's question and the following column names and record count, generate:\n"
44
+ "1. A short, clear, and specific heading for the results (do NOT use generic phrases like 'Here are the top X results I found').\n"
45
+ "2. A 1-2 sentence summary or description of what the results represent, using the question and columns for context.\n"
46
+ f"User question: {user_question}\nColumns: {column_names}\nRecord count: {record_count}\n"
47
+ "Return your answer as JSON with keys 'heading' and 'summary'. Do not mention the actual data."
48
+ )
49
+ heading = llm.invoke([{"role": "user", "content": heading_prompt}]).content.strip()
50
+ return {"heading": heading, "records": rows}
51
+
52
+ @staticmethod
53
+ def is_valid_sql_query33(sql: str) -> bool:
54
+ """
55
+ Returns True if the string is a valid SQL statement (not just a keyword in text).
56
+ Uses sqlparse to check for a valid statement structure.
57
+ Accepts queries starting with 'sql ' followed by a valid SQL statement.
58
+ """
59
+ if not sql or not isinstance(sql, str):
60
+ return False
61
+ sql_strip = sql.strip()
62
+ # Remove leading 'sql' if present
63
+ if sql_strip.lower().startswith('sql'):
64
+ sql_strip = sql_strip[4:].lstrip()
65
+ parsed = sqlparse.parse(sql_strip)
66
+ if not parsed or not parsed[0].tokens:
67
+ return False
68
+ stmt = parsed[0]
69
+ first_token = stmt.token_first(skip_cm=True, skip_ws=True)
70
+ if first_token is None:
71
+ return False
72
+ # Accept only if the first token is a SQL keyword and there is more than one token
73
+ return first_token.ttype in sqlparse.tokens.Keyword.DML and len(stmt.tokens) > 1
74
+
75
+ @staticmethod
76
+ def is_valid_sql_query(text: str):
77
+ if not text or not text.strip():
78
+ return False
79
+
80
+ # Remove markdown formatting like ```sql ... ```
81
+ text = re.sub(r"```sql|```", "", text, flags=re.IGNORECASE).strip()
82
+
83
+ # Collapse multiple spaces & line breaks into a single space
84
+ normalized = re.sub(r"\s+", " ", text).strip().upper()
85
+
86
+ # Common SQL query structure patterns (heuristics)
87
+ sql_patterns = [
88
+ r"^SELECT\s+.+\s+FROM\s+.+", # SELECT ... FROM ...
89
+ r"^INSERT\s+INTO\s+.+\s+VALUES\s*\(", # INSERT INTO ... VALUES (...)
90
+ r"^UPDATE\s+.+\s+SET\s+.+", # UPDATE ... SET ...
91
+ r"^DELETE\s+FROM\s+.+", # DELETE FROM ...
92
+ r"^CREATE\s+(TABLE|DATABASE)\s+.+", # CREATE TABLE/DATABASE ...
93
+ r"^DROP\s+(TABLE|DATABASE)\s+.+", # DROP TABLE/DATABASE ...
94
+ r"^ALTER\s+TABLE\s+.+", # ALTER TABLE ...
95
+ r"^WITH\s+.+\s+AS\s*\(.+\)" # WITH ... AS (...)
96
+ ]
97
+
98
+ for pattern in sql_patterns:
99
+ # Format the SQL for readability
100
+ formatted_sql = sqlparse.format(text, reindent=True, keyword_case='upper')
101
+ return formatted_sql
102
+
103
+ return False
104
+
105
+
backend/app/services/utility_sql.py ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import sqlparse
2
+
3
+ class UtilityClass:
4
+ @staticmethod
5
+ def is_valid_sql_query(sql: str) -> bool:
6
+ """
7
+ Returns True if the string is a valid SQL statement (not just a keyword in text).
8
+ Uses sqlparse to check for a valid statement structure.
9
+ """
10
+ if not sql or not isinstance(sql, str):
11
+ return False
12
+ parsed = sqlparse.parse(sql)
13
+ if not parsed or not parsed[0].tokens:
14
+ return False
15
+ # Check if the first token is a DML/DDL keyword and the statement is not just a keyword
16
+ stmt = parsed[0]
17
+ first_token = stmt.token_first(skip_cm=True, skip_ws=True)
18
+ if first_token is None:
19
+ return False
20
+ # Accept only if the first token is a SQL keyword and there is more than one token
21
+ return first_token.ttype in sqlparse.tokens.Keyword.DML and len(stmt.tokens) > 1
backend/config/settings.py ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from dotenv import load_dotenv
3
+
4
+ load_dotenv()
5
+
6
+ class Settings:
7
+ ENV = os.getenv("ENV") # Default to "local" if ENV is not set
8
+ # LLM API Token
9
+ OPENAI_API_KEY = os.getenv("OPENAI_API_KEY") or os.environ.get("OPENAI_API_KEY")
10
+
11
+ # GEMENI_API_KEY_TOKEN = os.getenv("OPENAI_API_KEY") or os.environ.get("OPENAI_API_KEY")
12
+
13
+ settings = Settings()
backend/main.py ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI
2
+ from app.api.endpoints import router
3
+ from fastapi.middleware.cors import CORSMiddleware
4
+
5
+ app = FastAPI(title="AI Query Generator")
6
+ # app.include_router(router)
7
+
8
+ # Allowed origins (Update this for production)
9
+
10
+ #router = APIRouter()
11
+ app.add_middleware(
12
+ CORSMiddleware,
13
+ allow_origins=["*"], # Replace "*" with your frontend domain in production
14
+ allow_credentials=True,
15
+ allow_methods=["*"],
16
+ allow_headers=["*"],
17
+ )
18
+
19
+ # Include API routes from endpoints.py
20
+ app.include_router(router, prefix="/api") # You can remove prefix if not needed
21
+
22
+ @app.get("/")
23
+ async def root():
24
+ return {"message": "Hello from FastAPI"}
25
+
26
+ #app.include_router(router)
backend/requirements.txt ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ sqlparse
2
+ fastapi
3
+ uvicorn
4
+ streamlit
5
+ requests
6
+ langchain
7
+ langchain-openai
8
+ langchain-community
9
+ sqlalchemy
10
+ pydantic
11
+ faiss-cpu # optional for embeddings
12
+ pandas
13
+ matplotlib
14
+ python-dotenv
15
+