MVP / Backend /tools /query_tool.py
github-actions[bot]
Deploy application
1f0fa9b
Raw History Blame Contribute Delete
3.61 kB
import os
import sys
from pathlib import Path
# Ensure Backend directory is in sys.path and root .env is loaded
BACKEND_DIR = Path(__file__).resolve().parent.parent
if str(BACKEND_DIR) not in sys.path:
sys.path.insert(0, str(BACKEND_DIR))
import config
from pydantic import BaseModel
from litellm import completion
from database.database import execute_query
# Simplified schema reference for the LLM context
DB_SCHEMA = """
CREATE TABLE customers (id INTEGER PRIMARY KEY, first_name TEXT, last_name TEXT, email TEXT, created_at TIMESTAMP);
CREATE TABLE orders (id INTEGER PRIMARY KEY, customer_id INTEGER, product_name TEXT, amount REAL, status TEXT, order_date TIMESTAMP, FOREIGN KEY (customer_id) REFERENCES customers(id));
"""
class QueryToolParams(BaseModel):
natural_language_query: str
def run_query_tool(params: dict) -> str:
"""Translates NL to SQL, validates it, and executes it against the database."""
try:
tool_config = QueryToolParams(**params)
except Exception as e:
return f"Error parsing parameters: {e}"
prompt = f"""You are an expert SQL assistant. Convert the following natural language query into a raw SQL SELECT statement.
Schema:
{DB_SCHEMA}
Query: {tool_config.natural_language_query}
Return ONLY the SQL string, without markdown formatting or code blocks.
"""
try:
model_name = config.get_llm_model()
api_key = config.get_api_key()
completion_kwargs = {
"model": model_name,
"messages": [{"role": "user", "content": prompt}],
"temperature": 0
}
if api_key:
completion_kwargs["api_key"] = api_key
response = completion(**completion_kwargs)
message = response.choices[0].message
sql_query = (message.content or getattr(message, "reasoning_content", "") or "").strip()
except Exception as e:
return f"Query Tool Error: {str(e)}"
try:
import re
# If output contains markdown code block, extract it
code_block_match = re.search(r"```(?:sql)?\s*([\s\S]*?)\s*```", sql_query, re.IGNORECASE)
if code_block_match:
sql_query = code_block_match.group(1).strip()
elif not sql_query.upper().startswith("SELECT"):
# If wrapped in reasoning text, extract the SELECT statement
select_match = re.search(r"(SELECT\s+[\s\S]+?)(?:;|\n\n|$)", sql_query, re.IGNORECASE)
if select_match:
sql_query = select_match.group(1).strip()
if sql_query.startswith("```sql"):
sql_query = sql_query.replace("```sql", "", 1)
if sql_query.endswith("```"):
sql_query = sql_query[::-1].replace("```", "", 1)[::-1]
elif sql_query.startswith("```"):
sql_query = sql_query.replace("```", "", 1)
if sql_query.endswith("```"):
sql_query = sql_query[::-1].replace("```", "", 1)[::-1]
sql_query = sql_query.strip()
# Simple read-only check since safety module is removed
if not sql_query.upper().startswith("SELECT"):
return "Safety Violation: Only SELECT queries are allowed."
if "LIMIT" not in sql_query.upper():
sql_query = sql_query.rstrip(";") + " LIMIT 50;"
rows = execute_query(sql_query)
if not rows:
return f"Executed `{sql_query}`: No results found."
return f"Executed `{sql_query}`:\n" + str(rows)
except Exception as e:
return f"Query Tool Error: {str(e)}"