Aniruddha7 commited on
Commit
a2dedd0
·
1 Parent(s): 8ed74e6

Deploy QueryLens (Gradio-SDK launcher: worker :8700 / MCP :8701 / web :7860)

Browse files
Files changed (2) hide show
  1. Agent/agentic_workflow.py +77 -6
  2. Agent/worker.py +21 -8
Agent/agentic_workflow.py CHANGED
@@ -834,6 +834,7 @@ PLACEHOLDER_REGEXES = [
834
  # Global readiness flag so we can re-attempt handshake before first real generation
835
  LLM_HANDSHAKE_COMPLETE = False
836
  LAST_GENERATED_SQL = None # updated each time a final SQL statement is produced
 
837
  # Cached real department names from DB — populated during initialize_database
838
  # Fallback: known employee-DB departments (used if DB query fails)
839
  _cached_dept_names: set = {
@@ -848,7 +849,11 @@ def build_cloud_llm():
848
  This is the same path used on Hugging Face Spaces. It is also used as an
849
  automatic fallback inside initialize_llm() when the configured local
850
  Ollama model is not installed.
851
- """
 
 
 
 
852
  hf_token = os.environ.get("HF_API_TOKEN")
853
  hf_model = os.environ.get("HF_LLM_MODEL", "Qwen/Qwen2.5-Coder-7B-Instruct")
854
  if not hf_token:
@@ -874,11 +879,23 @@ def build_cloud_llm():
874
  # Talk directly to HF serverless pool using the Chat Completion API
875
  # This satisfies providers that require the "conversational" task
876
  hf_client = InferenceClient(model=self.model_name, token=hf_token)
877
- resp = hf_client.chat_completion(
878
- messages=[{"role": "user", "content": prompt}],
879
- max_tokens=1024
880
- )
881
- return CompletionResponse(text=resp.choices[0].message.content)
 
 
 
 
 
 
 
 
 
 
 
 
882
 
883
  def stream_complete(self, prompt: str, **kwargs):
884
  # Not needed for our agent, but required by base class
@@ -901,6 +918,44 @@ def build_cloud_llm():
901
  return llm
902
 
903
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
904
  def _ollama_model_available(model_name: str, base_url: str) -> bool:
905
  """Return True if the given model tag exists on the Ollama server.
906
 
@@ -2555,6 +2610,11 @@ def get_mcp_hint_if_available(question: str):
2555
  if isinstance(tool_res, dict) and 'sql' in tool_res:
2556
  sql = tool_res['sql']
2557
  _fast_log('TOOL_SQL', ms=int(dur*1000), sql_preview=sql[:160])
 
 
 
 
 
2558
  return sql # Return as hint
2559
  else:
2560
  _fast_log('NO_SQL', raw=tool_res)
@@ -3951,6 +4011,17 @@ def query_craft(question: str) -> str:
3951
 
3952
  # All 3 levels failed
3953
  print("[3-LEVEL] ✗ All 3 levels failed to generate valid SQL")
 
 
 
 
 
 
 
 
 
 
 
3954
  raise ValueError("Unable to generate SQL query after 3-level attempts; try rephrasing.")
3955
  # salvage path (unreachable due to raise above, kept for reference)
3956
 
 
834
  # Global readiness flag so we can re-attempt handshake before first real generation
835
  LLM_HANDSHAKE_COMPLETE = False
836
  LAST_GENERATED_SQL = None # updated each time a final SQL statement is produced
837
+ LAST_MCP_HINT_SQL = None # last validated fast-SQL hint (deterministic fallback path)
838
  # Cached real department names from DB — populated during initialize_database
839
  # Fallback: known employee-DB departments (used if DB query fails)
840
  _cached_dept_names: set = {
 
849
  This is the same path used on Hugging Face Spaces. It is also used as an
850
  automatic fallback inside initialize_llm() when the configured local
851
  Ollama model is not installed.
852
+
853
+ Credits-resilience: on HF Inference 402/401/429 (credits exhausted, expired
854
+ token, rate limit) the same prompt is re-issued to Groq's OpenAI-compatible
855
+ endpoint (GROQ_API_KEY is already a Space secret). Model is configurable via
856
+ GROQ_LLM_MODEL (default: llama-3.3-70b-versatile)."""
857
  hf_token = os.environ.get("HF_API_TOKEN")
858
  hf_model = os.environ.get("HF_LLM_MODEL", "Qwen/Qwen2.5-Coder-7B-Instruct")
859
  if not hf_token:
 
879
  # Talk directly to HF serverless pool using the Chat Completion API
880
  # This satisfies providers that require the "conversational" task
881
  hf_client = InferenceClient(model=self.model_name, token=hf_token)
882
+ try:
883
+ resp = hf_client.chat_completion(
884
+ messages=[{"role": "user", "content": prompt}],
885
+ max_tokens=1024
886
+ )
887
+ return CompletionResponse(text=resp.choices[0].message.content)
888
+ except Exception as hf_exc:
889
+ if _is_provider_quota_error(hf_exc):
890
+ print(f"[LLM-FALLBACK] HF Inference unavailable "
891
+ f"({hf_exc.__class__.__name__}); falling back to Groq "
892
+ f"text model '{GROQ_TEXT_MODEL}'", flush=True)
893
+ try:
894
+ groq_text = _groq_text_completion(prompt)
895
+ return CompletionResponse(text=groq_text)
896
+ except Exception as groq_exc:
897
+ print(f"[LLM-FALLBACK] Groq fallback also failed: {groq_exc}", flush=True)
898
+ raise
899
 
900
  def stream_complete(self, prompt: str, **kwargs):
901
  # Not needed for our agent, but required by base class
 
918
  return llm
919
 
920
 
921
+ def _is_provider_quota_error(exc: Exception) -> bool:
922
+ """Detect HF Inference failures that a Groq fallback can plausibly fix:
923
+ out of credits (402), expired/missing token (401) and rate limits (429)."""
924
+ status = getattr(exc, "status_code", None)
925
+ if status is None:
926
+ response = getattr(exc, "response", None)
927
+ status = getattr(response, "status_code", None)
928
+ if status in (401, 402, 429):
929
+ return True
930
+ msg = str(exc)
931
+ return any(sig in msg for sig in (
932
+ "402", "Payment Required", "no remaining credits",
933
+ "401", "Unauthorized", "429", "rate limit",
934
+ ))
935
+
936
+
937
+ GROQ_TEXT_MODEL = os.environ.get("GROQ_LLM_MODEL", "llama-3.3-70b-versatile")
938
+
939
+
940
+ def _groq_text_completion(prompt: str, max_tokens: int = 1024, timeout: float = 90.0) -> str:
941
+ """Plain chat completion against Groq's OpenAI-compatible endpoint."""
942
+ groq_key = os.environ.get("GROQ_API_KEY")
943
+ if not groq_key:
944
+ raise RuntimeError("GROQ_API_KEY is not set; Groq text-LLM fallback unavailable")
945
+ r = requests.post(
946
+ "https://api.groq.com/openai/v1/chat/completions",
947
+ headers={"Authorization": f"Bearer {groq_key}"},
948
+ json={
949
+ "model": GROQ_TEXT_MODEL,
950
+ "messages": [{"role": "user", "content": prompt}],
951
+ "max_tokens": max_tokens,
952
+ },
953
+ timeout=timeout,
954
+ )
955
+ r.raise_for_status()
956
+ return r.json()["choices"][0]["message"]["content"]
957
+
958
+
959
  def _ollama_model_available(model_name: str, base_url: str) -> bool:
960
  """Return True if the given model tag exists on the Ollama server.
961
 
 
2610
  if isinstance(tool_res, dict) and 'sql' in tool_res:
2611
  sql = tool_res['sql']
2612
  _fast_log('TOOL_SQL', ms=int(dur*1000), sql_preview=sql[:160])
2613
+ # Remember for the deterministic post-3-level fallback (see the
2614
+ # "All 3 levels failed" block): if every LLM call fails due to
2615
+ # provider outage/credits, this schema-true SQL still answers.
2616
+ global LAST_MCP_HINT_SQL
2617
+ LAST_MCP_HINT_SQL = sql
2618
  return sql # Return as hint
2619
  else:
2620
  _fast_log('NO_SQL', raw=tool_res)
 
4011
 
4012
  # All 3 levels failed
4013
  print("[3-LEVEL] ✗ All 3 levels failed to generate valid SQL")
4014
+ # Deterministic last resort: the MCP fast-SQL hint produced during Level 3
4015
+ # is schema-true and question-matched. If all LLM calls failed because of
4016
+ # provider outage / exhausted credits, return it instead of erroring out.
4017
+ if LAST_MCP_HINT_SQL:
4018
+ try:
4019
+ if is_sql_valid(LAST_MCP_HINT_SQL, question):
4020
+ print("[3-LEVEL] ✓ FALLBACK: using deterministic MCP fast-SQL hint as final answer")
4021
+ print(f"[GEN] Final SQL: {LAST_MCP_HINT_SQL[:200]}")
4022
+ return LAST_MCP_HINT_SQL
4023
+ except Exception as fb_err:
4024
+ print(f"[3-LEVEL] MCP fast-SQL fallback skipped: {fb_err}")
4025
  raise ValueError("Unable to generate SQL query after 3-level attempts; try rephrasing.")
4026
  # salvage path (unreachable due to raise above, kept for reference)
4027
 
Agent/worker.py CHANGED
@@ -124,27 +124,40 @@ async def startup_event():
124
  try:
125
  await agent.initialize_database(uri=target_uri)
126
  # Keep the registry's active entry in sync with what we connected to.
 
 
 
 
127
  try:
128
- if profile_registry.active_id is None:
 
 
 
 
 
 
129
  prof = getattr(agent, "ACTIVE_SCHEMA_PROFILE", None)
130
- _kind = "demo" if (prof and prof.is_employee_db) else "user"
 
 
 
 
131
  pid = f"boot_{uuid.uuid4().hex[:8]}"
132
  profile_registry.profiles[pid] = {
133
- "name": (prof.name if prof else "startup_db") or "startup_db",
134
  "db_url": target_uri,
135
  "kind": _kind,
136
  "created_at": __import__("time").time(),
137
  }
138
  profile_registry.active_id = pid
139
  profile_registry.save()
140
- print(f"[WORKER] Registered active schema profile '{pid}' for {target_uri}")
141
  else:
142
- # point the active id at the profile that owns target_uri
143
- entries = profile_registry.list()
144
- match = next((p for p in entries if (p.get("db_url") or "").replace("\\", "/") == target_uri.replace("\\", "/")), None)
145
- if match and profile_registry.active_id != match["id"]:
146
  profile_registry.switch(match["id"])
147
  print(f"[WORKER] Active profile switched to '{match['name']}'")
 
 
148
  except Exception as reg_err:
149
  print(f"[WORKER] profile bootstrap skipped: {reg_err}")
150
  except Exception as e:
 
124
  try:
125
  await agent.initialize_database(uri=target_uri)
126
  # Keep the registry's active entry in sync with what we connected to.
127
+ # IMPORTANT: force the booted database to OWN the active slot — the
128
+ # demo registration above grabs "active" on a fresh store (ProfileRegistry
129
+ # .add() activates when no active exists), which left Space deployments
130
+ # showing the demo DB even though the env DB_CONNECTION_URL booted.
131
  try:
132
+ entries = profile_registry.list()
133
+ match = next(
134
+ (p for p in entries
135
+ if ((p.get("db_url") or "").replace("\\", "/") == target_uri.replace("\\", "/"))),
136
+ None,
137
+ )
138
+ if match is None:
139
  prof = getattr(agent, "ACTIVE_SCHEMA_PROFILE", None)
140
+ _from_env = bool(db_url and target_uri == db_url)
141
+ _kind = "user" if _from_env else (
142
+ "demo" if (prof and getattr(prof, "is_employee_db", False)) else "user")
143
+ _name = (getattr(prof, "name", None) if prof else None) or (
144
+ "Employee_DB" if _from_env else "demo_employee")
145
  pid = f"boot_{uuid.uuid4().hex[:8]}"
146
  profile_registry.profiles[pid] = {
147
+ "name": _name,
148
  "db_url": target_uri,
149
  "kind": _kind,
150
  "created_at": __import__("time").time(),
151
  }
152
  profile_registry.active_id = pid
153
  profile_registry.save()
154
+ print(f"[WORKER] Registered ACTIVE schema profile '{_name}' (kind={_kind}) for env DB")
155
  else:
156
+ if profile_registry.active_id != match["id"]:
 
 
 
157
  profile_registry.switch(match["id"])
158
  print(f"[WORKER] Active profile switched to '{match['name']}'")
159
+ else:
160
+ print(f"[WORKER] Active profile already '{match['name']}'")
161
  except Exception as reg_err:
162
  print(f"[WORKER] profile bootstrap skipped: {reg_err}")
163
  except Exception as e: