LIANJie-Jason Claude Opus 4.6 commited on
Commit
f97b9fe
Β·
1 Parent(s): 1b50825

fix: strip trailing semicolons from LLM-generated SQL and add SQL-route fallback

Browse files

Two bugs prevented SQL retrieval from working:

1. LLMs append trailing semicolons to SQL (~90%% of the time).
_validate_sql() rejected these as potential injection attacks.
Fix: rstrip(';') before validation (internal semicolons still blocked).

2. When route=sql and the LLM SQL query failed, the keyword-based
SQL fallback never triggered (condition excluded route=sql).
Fix: expanded fallback to build fresh keyword query for route=sql.

Verified end-to-end: China PTS scores now returns 49 rows (was 0).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

Files changed (2) hide show
  1. src/retriever.py +23 -6
  2. src/sql_retriever.py +5 -0
src/retriever.py CHANGED
@@ -259,12 +259,29 @@ def retrieve(
259
  if route in ("vector", "both") or (route == "sql" and not sql_rows):
260
  db_results = retrieve_from_vectordb(query, cfg)
261
 
262
- # ── Fallback: vector found nothing, try SQL ──────────────────────
263
- if sql_enabled and not db_results and not sql_rows and route in ("vector", "both"):
264
- if not sql_query:
265
- sql_query = _build_fallback_sql_query(query, cfg)
266
- if sql_query:
267
- sql_rows, sql_context = _run_sql_retrieval(sql_query, cfg)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
268
 
269
  # ── Web search ───────────────────────────────────────────────────
270
  web_enabled = cfg.get("web_search", {}).get("enabled", False)
 
259
  if route in ("vector", "both") or (route == "sql" and not sql_rows):
260
  db_results = retrieve_from_vectordb(query, cfg)
261
 
262
+ # ── Fallback: try keyword-based SQL when primary path returned nothing ─
263
+ # - route "vector"/"both": vector found nothing β†’ try SQL keyword search
264
+ # - route "sql": LLM SQL query failed β†’ try SQL keyword search as last resort
265
+ sql_fallback_needed = (
266
+ sql_enabled
267
+ and not sql_rows
268
+ and (
269
+ (route in ("vector", "both") and not db_results)
270
+ or route == "sql"
271
+ )
272
+ )
273
+ if sql_fallback_needed:
274
+ if route == "sql":
275
+ # LLM-generated sql_query already failed; build a fresh keyword query
276
+ fallback_query = _build_fallback_sql_query(query, cfg)
277
+ if fallback_query:
278
+ sql_rows, sql_context = _run_sql_retrieval(fallback_query, cfg)
279
+ else:
280
+ # route is "vector" or "both" β€” existing fallback logic
281
+ if not sql_query:
282
+ sql_query = _build_fallback_sql_query(query, cfg)
283
+ if sql_query:
284
+ sql_rows, sql_context = _run_sql_retrieval(sql_query, cfg)
285
 
286
  # ── Web search ───────────────────────────────────────────────────
287
  web_enabled = cfg.get("web_search", {}).get("enabled", False)
src/sql_retriever.py CHANGED
@@ -73,6 +73,11 @@ def execute_sql_query(sql_query: str, cfg: dict) -> list[dict]:
73
  # Strip comments before validation AND execution
74
  sql_query = _strip_sql_comments(sql_query).strip()
75
 
 
 
 
 
 
76
  if not _validate_sql(sql_query):
77
  return []
78
 
 
73
  # Strip comments before validation AND execution
74
  sql_query = _strip_sql_comments(sql_query).strip()
75
 
76
+ # Strip trailing semicolons β€” LLMs add them ~90% of the time.
77
+ # Must happen AFTER comment removal but BEFORE validation, so that
78
+ # _validate_sql() still rejects internal semicolons (statement chaining).
79
+ sql_query = sql_query.rstrip(";").rstrip()
80
+
81
  if not _validate_sql(sql_query):
82
  return []
83