Ashoksainibadu's picture
Upload folder using huggingface_hub
388137e verified
Raw History Blame Contribute Delete
9.6 kB
"""Service orchestration for text-to-SQL query processing with Consensus Voting and Self-Correction."""
import time
import uuid
from typing import Any, Dict, List
from src.t2s.data.prompts import build_prompt_messages
from src.t2s.gen.backends import get_backend
from src.t2s.safety.executor import SafeSQLExecutor
from src.t2s.safety.policy import SafetyPolicy
from src.t2s.safety.validator import SQLValidator
from src.t2s.service.self_correct import SelfCorrectionRunner
from src.t2s.service.voting import ConsensusVoter
from src.t2s.serving.dbs import DatabaseRegistry
class Text2SQLService:
def __init__(
self,
backend_name: str = "mock",
model_path: str = "models/qlora_adapter",
db_base_dir: str = "data/raw/spider/database",
) -> None:
self.db_registry = DatabaseRegistry(db_base_dir=db_base_dir)
self.policy = SafetyPolicy.load()
self.validator = SQLValidator(self.policy)
self.executor = SafeSQLExecutor(
timeout_seconds=self.policy.timeout_seconds,
max_rows=self.policy.max_row_limit,
)
self.self_corrector = SelfCorrectionRunner(
validator=self.validator,
executor=self.executor,
max_retries=2,
)
self.consensus_voter = ConsensusVoter(
validator=self.validator,
executor=self.executor,
per_candidate_timeout_seconds=self.policy.timeout_seconds,
total_budget_seconds=15.0,
max_concurrent=4,
)
try:
self.backend = get_backend(backend_name, model_name=model_path, model_path=model_path)
except Exception:
self.backend = get_backend("mock")
def process_query(
self,
db_id: str,
question: str,
include_sample_rows: bool = False,
enable_consensus: bool = False,
n_samples: int = 5,
) -> Dict[str, Any]:
if enable_consensus and n_samples > 1:
return self.process_query_consensus(
db_id=db_id,
question=question,
include_sample_rows=include_sample_rows,
n_samples=n_samples,
)
request_id = str(uuid.uuid4())
start_time = time.perf_counter()
timings: Dict[str, float] = {}
# 1. Fetch schema DDL
t0 = time.perf_counter()
schema_ddl = self.db_registry.get_schema_ddl(db_id, include_sample_rows=include_sample_rows)
timings["schema_fetch_ms"] = round((time.perf_counter() - t0) * 1000.0, 2)
# 2. Build prompt
prompt_msgs = build_prompt_messages(schema_ddl, question)
# 3. Model inference
t1 = time.perf_counter()
gen_output = self.backend.generate([prompt_msgs])[0]
timings["inference_ms"] = round((time.perf_counter() - t1) * 1000.0, 2)
raw_sql = gen_output.get("text", "") if isinstance(gen_output, dict) else str(gen_output)
clean_sql = raw_sql.strip()
if ";" in clean_sql:
clean_sql = clean_sql.split(";")[0] + ";"
db_path = str(self.db_registry.get_db_path(db_id))
def generate_repair(msgs: List[Dict[str, str]]) -> str:
res = self.backend.generate([msgs])[0]
return res.get("text", "") if isinstance(res, dict) else str(res)
t_corr = time.perf_counter()
corr_res = self.self_corrector.run(
db_path=db_path,
initial_sql=clean_sql,
generate_fn=generate_repair,
base_prompt_messages=prompt_msgs,
)
timings["self_correction_ms"] = round((time.perf_counter() - t_corr) * 1000.0, 2)
timings["total_ms"] = round((time.perf_counter() - start_time) * 1000.0, 2)
client_attempts = [
{"attempt_index": att.attempt_index, "sql": att.sql, "status": att.status}
for att in corr_res.attempts
]
if not corr_res.allowed:
return {
"request_id": request_id,
"db_id": db_id,
"question": question,
"sql": corr_res.final_sql,
"allowed": False,
"reasons": corr_res.verdict.reasons,
"attempts": client_attempts,
"columns": [],
"rows": [],
"row_count": 0,
"truncated": False,
"retried": corr_res.retried,
"consensus_score": "1/1",
"timings_ms": timings,
"model_version": "Qwen2.5-Coder-3B-QLoRA",
}
exec_res = corr_res.exec_res
columns = exec_res.columns if exec_res else []
rows = [list(r) for r in exec_res.rows] if exec_res else []
row_count = exec_res.row_count if exec_res else 0
truncated = exec_res.truncated if exec_res else False
return {
"request_id": request_id,
"db_id": db_id,
"question": question,
"sql": corr_res.final_sql,
"allowed": True,
"reasons": corr_res.verdict.reasons,
"attempts": client_attempts,
"columns": columns,
"rows": rows,
"row_count": row_count,
"truncated": truncated,
"retried": corr_res.retried,
"consensus_score": "1/1",
"timings_ms": timings,
"model_version": "Qwen2.5-Coder-3B-QLoRA",
}
def process_query_consensus(
self,
db_id: str,
question: str,
include_sample_rows: bool = False,
n_samples: int = 5,
) -> Dict[str, Any]:
"""Execution-Guided Self-Correction with Majority Voting (Consensus Decoding)."""
request_id = str(uuid.uuid4())
start_time = time.perf_counter()
timings: Dict[str, float] = {}
t0 = time.perf_counter()
schema_ddl = self.db_registry.get_schema_ddl(db_id, include_sample_rows=include_sample_rows)
timings["schema_fetch_ms"] = round((time.perf_counter() - t0) * 1000.0, 2)
prompt_msgs = build_prompt_messages(schema_ddl, question)
prompt_list = [prompt_msgs] * n_samples
t1 = time.perf_counter()
gen_outputs = self.backend.generate(prompt_list)
timings["inference_ms"] = round((time.perf_counter() - t1) * 1000.0, 2)
db_path = str(self.db_registry.get_db_path(db_id))
candidate_sqls = []
for out in gen_outputs:
raw_sql = out.get("text", "") if isinstance(out, dict) else str(out)
candidate_sqls.append(raw_sql)
t_vote = time.perf_counter()
vote_res = self.consensus_voter.select_best_candidate(
db_path=db_path,
candidate_sqls=candidate_sqls,
)
timings["voting_execution_ms"] = round((time.perf_counter() - t_vote) * 1000.0, 2)
timings["total_ms"] = round((time.perf_counter() - start_time) * 1000.0, 2)
client_attempts = []
for c in vote_res.candidates:
err = c.exec_res.error if c.exec_res and not c.exec_res.success else None
status = (
"blocked_by_policy"
if not c.verdict.allowed
else (
"ok"
if c.exec_res and c.exec_res.success
else f"exec_error:{c.exec_res.error[:30] if c.exec_res else 'failed'}"
)
)
from src.t2s.service.self_correct import sanitize_attempt_status
status = sanitize_attempt_status(
c.verdict.allowed, bool(c.exec_res and c.exec_res.success), err
)
client_attempts.append({"attempt_index": c.index, "sql": c.sql, "status": status})
if not vote_res.winner_verdict.allowed:
return {
"request_id": request_id,
"db_id": db_id,
"question": question,
"sql": vote_res.winner_sql,
"allowed": False,
"reasons": vote_res.winner_verdict.reasons,
"attempts": client_attempts,
"columns": [],
"rows": [],
"row_count": 0,
"truncated": False,
"retried": False,
"consensus_score": vote_res.consensus_score,
"timings_ms": timings,
"model_version": "Qwen2.5-Coder-3B-QLoRA",
}
exec_res = vote_res.winner_exec_res
columns = exec_res.columns if exec_res else []
rows = [list(r) for r in exec_res.rows] if exec_res else []
row_count = exec_res.row_count if exec_res else 0
truncated = exec_res.truncated if exec_res else False
return {
"request_id": request_id,
"db_id": db_id,
"question": question,
"sql": vote_res.winner_sql,
"allowed": True,
"reasons": vote_res.winner_verdict.reasons,
"attempts": client_attempts,
"columns": columns,
"rows": rows,
"row_count": row_count,
"truncated": truncated,
"retried": False,
"consensus_score": vote_res.consensus_score,
"timings_ms": timings,
"model_version": "Qwen2.5-Coder-3B-QLoRA",
}