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.
- {contsql-0.2.0 → contsql-0.2.2}/PKG-INFO +1 -1
- {contsql-0.2.0 → contsql-0.2.2}/contsql.egg-info/PKG-INFO +1 -1
- {contsql-0.2.0 → contsql-0.2.2}/contsql.py +73 -7
- {contsql-0.2.0 → contsql-0.2.2}/pyproject.toml +1 -1
- {contsql-0.2.0 → contsql-0.2.2}/README.md +0 -0
- {contsql-0.2.0 → contsql-0.2.2}/contsql.egg-info/SOURCES.txt +0 -0
- {contsql-0.2.0 → contsql-0.2.2}/contsql.egg-info/dependency_links.txt +0 -0
- {contsql-0.2.0 → contsql-0.2.2}/contsql.egg-info/entry_points.txt +0 -0
- {contsql-0.2.0 → contsql-0.2.2}/contsql.egg-info/requires.txt +0 -0
- {contsql-0.2.0 → contsql-0.2.2}/contsql.egg-info/top_level.txt +0 -0
- {contsql-0.2.0 → contsql-0.2.2}/setup.cfg +0 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
|
-
# v0.2 | 2026-04-13 |
|
|
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
|
-
"
|
|
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)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|