contsql 0.2.0__tar.gz → 0.2.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: contsql
3
- Version: 0.2.0
3
+ Version: 0.2.2
4
4
  Requires-Python: >=3.10
5
5
  Requires-Dist: duckdb
6
6
  Requires-Dist: requests
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: contsql
3
- Version: 0.2.0
3
+ Version: 0.2.2
4
4
  Requires-Python: >=3.10
5
5
  Requires-Dist: duckdb
6
6
  Requires-Dist: requests
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env python3
2
- # v0.2 | 2026-04-13 | case insensitive ILIKE guardrail + önceki sorgu entity context
2
+ # v0.2.2 | 2026-04-13 | asal sayı fixture + SUM checksum + ambiguous column retry
3
3
  """contsql — Minimal DuckDB SQL agent. Soru sor, SQL üret, çalıştır, göster."""
4
4
 
5
5
  import argparse
@@ -58,9 +58,25 @@ def read_domain_notes(db_path):
58
58
  return ""
59
59
 
60
60
 
61
+ # ── Referans algılama ──
62
+
63
+ REFERANS_TRIGGERS = [
64
+ "bu firma", "bunlar", "bunların", "yukarıdaki", "yukarıdakiler",
65
+ "aynı firma", "o firma", "önceki", "listedeki", "sonuçtaki",
66
+ "bu müşteri", "bu muta", "onların",
67
+ ]
68
+
69
+
70
+ def has_reference_trigger(question):
71
+ """Kullanıcı sorusu önceki sorgu sonucuna referans veriyor mu?"""
72
+ q_lower = question.lower()
73
+ return any(trigger in q_lower for trigger in REFERANS_TRIGGERS)
74
+
75
+
61
76
  # ── System prompt ──
62
77
 
63
- def build_system_prompt(schema_text, domain_text="", last_result_entities=None):
78
+ def build_system_prompt(schema_text, domain_text="", last_result_entities=None,
79
+ question=None):
64
80
  prompt = f"""Sen bir SQL asistanısın. Kullanıcının sorusuna uygun SQL yaz.
65
81
 
66
82
  Kurallar:
@@ -69,6 +85,7 @@ Kurallar:
69
85
  - SQL öncesi veya sonrası açıklama ekleme.
70
86
  - Emin değilsen "Bu soruyu mevcut tablolarla cevaplayamıyorum" de.
71
87
  - Veri uydurma. Sorgu sonucu olmadan liste verme.
88
+ - HER sorguda entity_id ve unvan kolonlarını dahil et. Firmalar bu iki alanla tanımlanır, istisnası yok. COUNT/SUM gibi aggregation sorgularında bile GROUP BY entity_id, unvan kullan veya alt sorgu yaz.
72
89
  - String karşılaştırmalarında LIKE yerine her zaman ILIKE kullan. Türkçe karakter eşleştirmesi (İ↔i, I↔ı, Ş↔ş, Ü↔ü, Ö↔ö, Ç↔ç, Ğ↔ğ) için ILIKE şart.
73
90
 
74
91
  Veritabanı şeması:
@@ -76,11 +93,10 @@ Veritabanı şeması:
76
93
  """
77
94
  if domain_text:
78
95
  prompt += f"\nDomain bilgisi:\n{domain_text}\n"
79
- if last_result_entities:
96
+ if last_result_entities and question and has_reference_trigger(question):
80
97
  prompt += (
81
98
  f"\nÖNCEKİ SORGU SONUCUNDAKI FİRMALAR (entity_id): {last_result_entities}\n"
82
- "Kullanıcı 'bu firmalar', 'bunların', 'aynıları', 'yukarıdakiler' gibi "
83
- "referans verirse bu entity_id listesini WHERE koşulunda kullan.\n"
99
+ "Bu entity_id listesini WHERE koşulunda kullan.\n"
84
100
  )
85
101
  return prompt
86
102
 
@@ -154,6 +170,33 @@ def ask_model(system_prompt, question):
154
170
  return f"LLM HATA: {e}", time.time() - t0, 0
155
171
 
156
172
 
173
+ def generate_sql(conn, question, last_result_entities=None, domain_text=""):
174
+ """Soru → SQL string. Test runner için callable. Başarısızsa None."""
175
+ schema_text = read_schema(conn)
176
+ system_prompt = build_system_prompt(schema_text, domain_text, last_result_entities,
177
+ question=question)
178
+ response, _, _ = ask_model(system_prompt, question)
179
+ sql = extract_sql(response)
180
+ if not sql or check_sql_safety(sql):
181
+ return None
182
+ sql = _like_to_ilike(sql)
183
+
184
+ # Ambiguous column retry: EXPLAIN ile ön kontrol
185
+ try:
186
+ conn.execute(f"EXPLAIN {sql}")
187
+ except Exception as e:
188
+ if "ambiguous" not in str(e).lower():
189
+ return sql
190
+ retry_q = f"{question}\n\nÖNCEKİ SQL HATA: {e}\nJOIN'de tablo alias kullan."
191
+ resp2, _, _ = ask_model(system_prompt, retry_q)
192
+ sql2 = extract_sql(resp2)
193
+ if sql2 and not check_sql_safety(sql2):
194
+ return _like_to_ilike(sql2)
195
+ return None
196
+
197
+ return sql
198
+
199
+
157
200
  # ── Result formatting ──
158
201
 
159
202
  def _fmt_value(v):
@@ -269,6 +312,27 @@ def run_query(conn, system_prompt, question):
269
312
  print(" ⚠ Önceki sorgu çok geniş — firma referansı için soruyu daraltın.")
270
313
  return entities
271
314
  except duckdb.Error as e:
315
+ # Ambiguous column retry
316
+ if "ambiguous" in str(e).lower():
317
+ print(f"🔄 Ambiguous column, retry...")
318
+ retry_q = f"{question}\n\nSQL HATA: {e}\nJOIN'de tablo alias kullan."
319
+ resp2, _, _ = ask_model(system_prompt, retry_q)
320
+ sql2 = extract_sql(resp2)
321
+ if sql2 and not check_sql_safety(sql2):
322
+ sql2 = _like_to_ilike(sql2)
323
+ print(f"🔍 Retry SQL: {sql2}")
324
+ try:
325
+ result = conn.execute(sql2)
326
+ columns = [desc[0] for desc in result.description]
327
+ rows = result.fetchall()
328
+ query_ms = (time.time() - t0) * 1000
329
+ print(f"\n📊 SONUÇ ({len(rows)} satır, {query_ms:.0f}ms)")
330
+ print(format_table(columns, rows))
331
+ entities = _extract_entity_ids(columns, rows)
332
+ return entities
333
+ except duckdb.Error as e2:
334
+ print(f"\n❌ Retry hatası: {e2}")
335
+ return None
272
336
  print(f"\n❌ SQL hatası: {e}")
273
337
  print(f"🔍 SQL: {sql}")
274
338
  return None
@@ -296,7 +360,8 @@ def interactive_loop(conn, schema_text, domain_text):
296
360
  print(f"\n{read_schema(conn)}\n")
297
361
  continue
298
362
 
299
- system_prompt = build_system_prompt(schema_text, domain_text, last_result_entities)
363
+ system_prompt = build_system_prompt(schema_text, domain_text, last_result_entities,
364
+ question=question)
300
365
  entities = run_query(conn, system_prompt, question)
301
366
  if entities is not None:
302
367
  last_result_entities = entities
@@ -331,7 +396,8 @@ def main():
331
396
 
332
397
  # Tek soru veya interaktif
333
398
  if args.question:
334
- system_prompt = build_system_prompt(schema_text, domain_text)
399
+ system_prompt = build_system_prompt(schema_text, domain_text,
400
+ question=args.question)
335
401
  run_query(conn, system_prompt, args.question)
336
402
  else:
337
403
  interactive_loop(conn, schema_text, domain_text)
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "contsql"
7
- version = "0.2.0"
7
+ version = "0.2.2"
8
8
  requires-python = ">=3.10"
9
9
  dependencies = ["duckdb", "requests"]
10
10
 
File without changes
File without changes