Download src/t2s/serving/service.py from Ashoksainibadu/text2sql-assistant: direct link, hf CLI and curl.
- Browser
- Download file 9.6 kB
-
https://huggingface.co/spaces/Ashoksainibadu/text2sql-assistant/resolve/main/src/t2s/serving/service.py
- Command line
-
hf download hf://spaces/Ashoksainibadu/text2sql-assistant/src/t2s/serving/service.py
-
curl -L -o service.py https://huggingface.co/spaces/Ashoksainibadu/text2sql-assistant/resolve/main/src/t2s/serving/service.py
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", | |
| } | |