SaitejaMothukuri's picture
Deploy text-to-sql-guardrails
981d8b1 verified
Raw
History Blame Contribute Delete
2.15 kB
"""OpenRouter NL->SQL: schema-aware prompt + robust SQL extraction."""
from __future__ import annotations
import os
import re
from ._openrouter import get_api_key, make_client
DEFAULT_MODEL = "nvidia/nemotron-3-nano-30b-a3b:free"
SYSTEM_PROMPT = (
"You are an expert data analyst who writes DuckDB SQL. Given a database schema "
"and a question, return ONE read-only SELECT query that answers it.\n"
"Rules:\n"
"1. SELECT only — never INSERT/UPDATE/DELETE/DROP/ALTER or any DDL.\n"
"2. Use ONLY the tables and columns listed in the schema.\n"
"3. Return ONLY the SQL — no prose, no explanation, no markdown fences."
)
_FENCE = re.compile(r"```(?:sql)?\s*(.*?)```", re.S | re.I)
def get_model() -> str:
return os.environ.get("OPENROUTER_MODEL", DEFAULT_MODEL).strip()
def build_messages(question: str, schema_text: str) -> list[dict]:
user = f"Schema:\n{schema_text}\n\nQuestion: {question}\n\nSQL:"
return [
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": user},
]
def extract_sql(text: str) -> str:
"""Pull a SQL statement out of a model response (handles ```sql fences and prose)."""
if not text:
return ""
m = _FENCE.search(text)
sql = (m.group(1) if m else text).strip()
# Trim leading prose: jump to the first WITH/SELECT keyword.
low = sql.lower()
idx = min(
(i for i in (low.find("with "), low.find("select ")) if i != -1),
default=-1,
)
if idx > 0:
sql = sql[idx:]
return sql.strip().rstrip(";").strip()
def generate_sql(
question: str,
schema_text: str,
api_key: str | None = None,
model: str | None = None,
temperature: float = 0.0,
) -> str:
api_key = api_key or get_api_key()
if not api_key:
raise RuntimeError("OPENROUTER_API_KEY is not set. See the README.")
client = make_client(api_key)
resp = client.chat.completions.create(
model=model or get_model(),
messages=build_messages(question, schema_text),
temperature=temperature,
)
return extract_sql(resp.choices[0].message.content or "")