پرتگاه text-to-SQL: چرا نتیجهٔ سنجه روی انبار دادهٔ شما تکرار نمیشود
نمرهٔ سنجههای text-to-SQL به انبار دادهٔ تولید منتقل نمیشود. این نوشته ارقام منتشرشدهٔ Spider 1.0، Spider 2.0 و BIRD را تا منبع اصلیشان دنبال میکند، برمیشمارد اسکیمای یک انبار داده چه چیزهایی دارد که اسکیمای دانشگاهی ندارد، و داربستی میدهد برای اندازهگیری دقت روی اسکیمای خودتان.
چرا این نوشته
ارقام Spider 1.0، Spider 2.0 و BIRD تا منابع اصلیشان با تاریخ دنبال شدهاند، بههمراه شرحی از اینکه جدول رتبهبندی ۲۰۲۶ استدلال را چطور عوض میکند و چطور نمیکند، برشماری ملموس ده ویژگی اسکیما که انبار دادهٔ واقعی را از پایگاهدادهٔ سنجه جدا میکند، و یک داربست ارزیابی اجراشدنی که تنها عدد مهم را میسازد — دقت روی اسکیمای خودتان، با شمارش جداگانهٔ پاسخهای نادرستِ محتمل از خطاها.
هر نمایش محصولی text-to-SQL خوب پیش میرود. یک پایگاهدادهٔ نمونهٔ پنججدولی، یک پرسش به فارسی یا انگلیسی، یک کوئری درست، یک عدد درست. جلسه با لبخند تمام میشود و کسی چیزی یاد نمیگیرد.
مسئله این نیست که آن نمایش تقلب است. مسئله این است که هیچ چیزی را دربارهٔ انبار دادهٔ شما پیشبینی نمیکند، و اندازهٔ این فاصله مستند است نه حکایتی. پروژهٔ Spider 2.0 گزارش کرد که پیشرفتهترین مدلهای زبانی موجود در آن زمان، از جمله GPT-4، ۶٫۰٪ از وظایف Spider 2.0 را حل کردند، در برابر ۸۶٫۶٪ روی Spider 1.0 و ۵۷٫۴٪ روی BIRD. همین یک جمله مفیدترین چیزی است که دربارهٔ این مسئله منتشر شده.
بقیهٔ این نوشته سه کار میکند: آن ارقام را با تاریخ تا منبعشان دنبال میکند، دقیقاً توضیح میدهد اسکیمای یک انبار داده چه چیزی دارد که اسکیمای سنجه ندارد، و داربستی به شما میدهد برای اندازهگیری تنها عددی که اهمیت دارد، یعنی عدد خودتان.
ارقام، و اینکه هر کدام چه چیزی را اندازه گرفتند
چهار عدد در این بحث نقل میشود و چهار چیز متفاوت را میسنجند. چسباندن تاریخ و شرایط به هر کدام، بیشترِ کار است.
| رقم | سنجه | چه چیزی را سنجید | منبع |
|---|---|---|---|
| 86.6% | Spider 1.0 | دقت اجرا، DAIL-SQL + GPT-4 با خودسازگاری، رتبهٔ نخست جدول در تاریخ 19 Sep 2023 | Gao et al., arXiv:2308.15363 |
| 91.2% | Spider 1.0 | رقم مرجع خودِ مقالهٔ Spider 2.0 برای کارایی روی سنجهٔ پیشین | Lei et al., arXiv:2411.07763 |
| 6.0% | Spider 2.0 | پرسش مستقیم از مدلهای پیشرفته از جمله GPT-4، طبق نخستین گزارش پروژه | xlang-ai/Spider2 |
| 17.0% → 21.3% | Spider 2.0 | o1-preview داخل یک چارچوب عامل کدنویس ساختهشده برای همین کار — نسخهٔ ۱ مقاله 17.0% و نسخهٔ بعدی 21.3% گزارش کرد | arXiv:2411.07763 |
| 92.96% | BIRD | دقت اجرای متخصص انسانی — سقف، نه نمرهٔ مدل | جدول رتبهبندی BIRD |
Spider 2.0 شامل ۶۳۲ مسئلهٔ گردشکاری واقعی text-to-SQL روی پایگاهدادههای سازمانی است که بهطور معمول از ۱٬۰۰۰ ستون فراتر میروند، و به سه بخش تقسیم شده: Spider 2.0-Snow (۵۴۷ نمونه روی Snowflake)، Spider 2.0-Lite (۵۴۷ نمونه روی BigQuery و Snowflake و SQLite) و Spider 2.0-DBT (۶۸ وظیفه روی DuckDB). وظیفه «یک SELECT بنویس» نیست. یک گردشکار است: در فراداده بگرد، چند کوئری به گویش درست بنویس، تبدیل کن، و نتیجه بساز.
جدول رتبهبندی ۲۰۲۶ خیلی جلو رفته، و کمتر از آنچه به نظر میرسد استدلال را عوض میکند
هر کسی که در سال ۲۰۲۶ رقم ۶٫۰٪ را بدون نگاهکردن به جدول رتبهبندی نقل کند، دارد عددی کهنه نقل میکند. پس نگاه کنید. تا زمان نگارش، جدول رتبهبندی عمومی Spider 2.0 ارسالهای برتری را نشان میدهد که بسیار بالاتر از خط پایههای اصلی هستند — در حدود ۹۶٪ روی Spider 2.0-Snow، ۷۶٪ روی Spider 2.0-Lite و ۶۶٪ روی Spider 2.0-DBT — و خود سایت یادآوری میکند که نمرهها ممکن است هنگام تأیید ارزیابی کمی تغییر کنند.
سه چیز همزمان دربارهٔ این درست است، و موضع صادقانه هر سه را با هم نگه میدارد.
نخست اینکه بهبود واقعی است. مدلهای مرزی بهعلاوهٔ داربست عامل، بخش بزرگی از فاصلهای را که در ۲۰۲۴ وجود داشت واقعاً بستهاند، و هر کسی که هنوز میگوید text-to-SQL «کار نمیکند» دارد وضعیت دوسالپیشِ فن را توصیف میکند.
دوم اینکه ورودیهای جدول رتبهبندی سامانههای عاملی هستند که مخصوص همین کار ساخته شدهاند و تیمها آنها را در برابر یک سنجهٔ ثابت، عمومی و بهشدت مطالعهشده بهینه کردهاند. ارسالی که روی ۵۴۷ وظیفهٔ Snowflake نمرهٔ ۹۶٪ میگیرد، در برابر همان ۵۴۷ وظیفه مهندسی شده — به شکلی که هیچ سامانهای در روز اولش در برابر انبار دادهٔ شما مهندسی نشده است.
سوم همان چیزی است که از نظر عملیاتی اهمیت دارد: پراکندگی درون جدول رتبهبندی، خودش یافته است. یک خانوادهٔ سنجهٔ واحد روی یک بخش ۹۶٪ میدهد و روی بخش دیگر ۶۶٪، و بخش سخت — Spider 2.0-DBT — همان است که به شیوهٔ واقعی ساخت تحلیل نزدیکتر است: با یک لایهٔ تبدیل و ساختار پروژه، نه با یک کوئری تنها. وقتی زیرمجموعههای خود یک سنجه سی واحد با هم اختلاف دارند، آن عدد به اسکیمایی که کسی ندیده تعمیم نمییابد.
انبار دادهٔ شما چه چیزی دارد که پایگاهدادهٔ سنجه ندارد
این پرتگاه از سختیِ SQL نمیآید. از ده ویژگی مشخص اسکیماهای تولیدی میآید که هیچکدام در پایگاهدادهٔ سنجهٔ دانشگاهی نیستند و تقریباً همهشان در هر انبار دادهای هستند.
| ویژگی | پایگاهدادهٔ سنجه | انبار دادهٔ شما | شکستی که میسازد |
|---|---|---|---|
| تعداد ستون | چند ده | بیش از 1,000، گاهی بیش از 3,000 | ستون مرتبط اصلاً وارد بافت نمیشود |
| نامگذاری | customer.name | DIM_CUST_ACCT.SRC_SYS_PTY_NM | ستون محتمل انتخاب میشود، ستون نادرست به کار میرود |
| تعریفهای کسبوکار | از خود پرسش برمیآید | «مشتری فعال» قاعدهای چهلخطی است که مالکش مالی است | کوئری درست که به پرسش نادرست پاسخ میدهد |
| ابعاد کندتغییر | ندارد | valid_from / valid_to روی هر بُعد | شمارش مضاعف خاموش روی سطرهای نسخهدار |
| حذف نرم | ندارد | is_deleted، status_cd = 'X'، سطرهای سنگقبر | رکوردهای حذفشده در هر جمع میآیند |
| جدولهای تکراری | ندارد | orders، orders_v2، orders_new، stg_orders | کوئری روی جدولی رهاشده اجرا میشود |
| معنای null | یکنواخت | null اینجا یعنی «نامعلوم» و آنجا یعنی «صفر» | تجمیعهایی که با گزارش رسمی فرق دارند |
| گویش | SQLite | BigQuery، Snowflake، SQL Server، Oracle، ClickHouse | SQL نامعتبر نحوی یا جابهجاشده از نظر معنایی |
| کار چندمرحلهای | یک کوئری | زنجیرهٔ CTE، جدول موقت، مدلهای dbt | قاب «یک کوئری» به وظیفه نمیخورد |
| دسترسیها | دسترسی کامل | امنیت سطح سطر، ستونهای پوشاندهشده، اسکیماهای ممنوع | مدل جدولی را میبیند که نمیتواند بخواند |
به وسط این جدول نگاه کنید، نه به بالای آن. مسئلهٔ تعداد ستون توجه میگیرد چون توصیفش آسان است، و واقعاً هم دلیل ضرورت بازیابی روی فرادادهٔ اسکیماست. اما شکستهای گران، شکستهای معنایی هستند — ابعاد کندتغییر، حذف نرم و تعریفهای کسبوکار — چون اینها کوئریای میسازند که اجرا میشود، عددی برمیگرداند، و نادرست است.
سنجه و تولید بر سر اینکه کدام شکست بدتر است اختلاف دارند
دقت اجرا، کوئریای که خطا میدهد و کوئریای که بیصدا عدد نادرست برمیگرداند را یکسان میشمارد: هر دو صرفاً درست نیستند. در تولید این دو متضاد یکدیگرند.
کوئریای که شکست میخورد خودش را گزارش میکند. کاربر خطا را میبیند، سامانه ثبتش میکند، هیچکس بر مبنایش تصمیم نمیگیرد، و شکست وارد فهرست کارهای شما میشود. کوئریای که اجرا میشود و عددی محتمل اما نادرست برمیگرداند، همان حالت شکستی است که پول میبَرد — چون تا هفتهها بعد که کسی آن را با یک گزارش تطبیق دهد، از پاسخ درست قابل تشخیص نیست، و تا آن موقع در یک جلسه نقل شده است.
این یک پیامد طراحی مستقیم دارد. رفتار درست در حالت عدم قطعیت، شکستِ آشکار است، و این باید مهندسی شود نه اینکه آرزویش را داشته باشیم. در دیتاکوپایلوت صفحهٔ داده یک قطعکنندهٔ مدار (circuit breaker) دارد: هشت شکست پیاپی یک خطای صادقانه برمیگرداند بهجای پاسخی که از باقیماندهٔ ذهن مدل سرِ هم شده باشد. سامانه طوری طراحی شده که «نتوانستم به این پاسخ بدهم» یک خروجی پشتیبانیشده است. هر سامانهٔ text-to-SQL بدون چنین حالتی، در برابر پرسشی که نمیتواند پاسخ دهد فقط یک واکنش در اختیار دارد — و همان را تولید میکند.
پیامد طراحی دوم دربارهٔ این است که اعداد از کجا میآیند. رقمی که از یک جریان توکن عبور کرده، از جزئی گذشته که میتواند تغییرش دهد. در دیتاکوپایلوت نتایج از راه یک پل اجرای پایتون به نمودار میرسند، نه از راه نثر تولیدشده — هیچ عددی که روی نمودار نشسته از جریان توکن عبور نکرده است. این ویژگی از چند واحد دقت سنجه ارزشمندتر است، چون یک ردهٔ کامل از خرابی خاموش را حذف میکند.
روی اسکیمای خودتان اندازه بگیرید — داربستش چهل خط است
تنها عددی که باید بر تصمیم خرید اثر بگذارد، دقت اجرا روی اسکیمای شما با پرسشهای شماست، و ساختنش حدود یک روز کار دارد. یک مجموعهٔ طلایی از ۵۰ پرسش با SQL درستِ دستنوشته بسازید که توزیع واقعی پرسشهای آدمها را نمونهبرداری کند، و بعد این را اجرا کنید:
# evalsql.py — SQLAlchemy 2.0, pandas 2.2, Python 3.11
# Produces the three counts that matter: correct, errored, wrong-but-ran.
import json
from dataclasses import dataclass
import pandas as pd
from sqlalchemy import create_engine, text
ENGINE = create_engine("postgresql+psycopg://readonly@warehouse/analytics")
@dataclass
class Case:
question: str
gold_sql: str
def run(sql: str) -> pd.DataFrame:
# Read-only, time-bounded. Never evaluate against a session that can write.
with ENGINE.connect() as conn:
conn.execute(text("SET LOCAL statement_timeout = '30s'"))
return pd.read_sql_query(text(sql), conn)
def equivalent(a: pd.DataFrame, b: pd.DataFrame, tol: float = 1e-6) -> bool:
"""Order-insensitive, column-name-insensitive result comparison."""
if a.shape != b.shape:
return False
a2 = a.copy(); b2 = b.copy()
a2.columns = range(a2.shape[1]); b2.columns = range(b2.shape[1])
key = list(a2.columns)
a2 = a2.sort_values(key).reset_index(drop=True)
b2 = b2.sort_values(key).reset_index(drop=True)
try:
pd.testing.assert_frame_equal(
a2, b2, check_dtype=False, rtol=tol, atol=tol
)
return True
except AssertionError:
return False
def evaluate(cases: list[Case], predict) -> dict:
correct = errored = wrong = 0
log = []
for c in cases:
gold = run(c.gold_sql)
pred_sql = predict(c.question)
try:
pred = run(pred_sql)
except Exception as exc: # noqa: BLE001
errored += 1
log.append({"q": c.question, "outcome": "error", "detail": str(exc)})
continue
if equivalent(gold, pred):
correct += 1
log.append({"q": c.question, "outcome": "correct"})
else:
wrong += 1 # the expensive category
log.append({"q": c.question, "outcome": "wrong", "sql": pred_sql})
n = len(cases)
return {
"n": n,
"execution_accuracy": correct / n,
"error_rate": errored / n,
"silent_wrong_rate": wrong / n, # report this separately
"log": log,
}
if __name__ == "__main__":
cases = [Case(**c) for c in json.load(open("gold.json"))]
result = evaluate(cases, predict=lambda q: your_system.to_sql(q))
print(json.dumps({k: v for k, v in result.items() if k != "log"}, indent=2))
دو تصمیم در این داربست همانهایی هستند که معمولاً اشتباه گرفته میشوند. مقایسه نسبت به ترتیب و نام ستون بیتفاوت است، چون کوئریای که سطرهای درست را با ترتیب دیگری برمیگرداند درست است و مقایسهٔ رشتهای سختگیرانه آن را نادرست علامت میزند. و silent_wrong_rate سطر مستقل خودش را دارد و در عدد دقت تا نمیشود، چون همان عددی است که بعد از نخستین حادثه از شما خواهند پرسید.
انتظار داشته باشید نتیجهٔ اولتان ناراحتکننده باشد. نکته دقیقاً همین است: این یک اندازهگیری واقعی از یک سامانهٔ واقعی روی یک اسکیمای واقعی است، و همان خط پایهای است که هر تغییر بعدی در برابرش سنجیده میشود.
چه چیزی واقعاً این عدد را جابهجا میکند
چهار چیز دقت text-to-SQL در تولید را جابهجا میکنند، تقریباً به همین ترتیب اثر.
معنای مستندشدهٔ اسکیما. بزرگترین سود واحد از این میآید که مستندات خود انبار داده در اختیار سامانه باشد: هر جدول چیست، هر ستون چه معنایی دارد، کدام جدولها رهاشدهاند، و سازمان اصطلاحهایش را چطور تعریف میکند. این یک تمرین مستندسازی و مدلسازی داده است، نه تمرین مدلسازی زبانی — و دلیل اینکه لایهٔ معنایی دیتاکوپایلوت حول مستندات اسکیما و یک جریان کمکی تدوین شناسنامهٔ داده ساخته شده، نه حول ترفندهای بازیابی. اگر انبار دادهٔ شما فرهنگ داده ندارد، ساختن آن از هر تغییری در مدل بازده بیشتری دارد.
تولید گویش درست. کوئریای که از نظر معنایی درست و از نظر نحوی برای موتور مقصد نامعتبر است، نمرهٔ صفر میگیرد. این مهندسیِ بیزرقوبرقی است — دیتاکوپایلوت ۳۰ آداپتور موتور پایگاهداده ثبتشده دارد و لایهٔ آداپتورش ۶٬۴۷۱ خط است، بزرگترین تکفایل بکاند — و یک پیششرط است، نه یک بهینهسازی.
حلقهٔ اجرای کراندار و قابل بازرسی. تلاش دوباره با پیام خطای موتور ضمیمهشده، سهم معناداری از شکستها را بازمیگرداند. همین حلقه اگر کران نداشته باشد تا ابد اجرا میشود، و به همین دلیل حلقهٔ عامل سقف پیشفرض ۱۲۸ گام دارد، قابل تنظیم بین ۱ تا ۵۱۲. بودجهٔ گام، شکست را خوانا میکند: عامل یا داخل بودجه تمام میکند یا گزارش میدهد که نتوانست.
اجرای فقطخواندنی و زماندار. هر کوئری داخل تراکنشی فقطخواندنی و زماندار اجرا میشود. این هیچ تأثیری بر دقت ندارد. هزینهٔ نادرستبودن را عوض میکند، و همین است که اصلاً پذیرفتنی میکند یک مدل زبانی را نزدیک پایگاهدادهٔ تولید ببرید.
چه چیزی این عدد را جابهجا نمیکند
صریحبودن دربارهٔ این، چند جلسهٔ تدارکات را نجات میدهد.
مدل بزرگتر کمتر از آنچه اختلاف سنجهها القا میکند کمک میکند، چون قید بازدارنده معمولاً دانشی دربارهٔ اسکیماست که مدل هیچ راهی برای داشتنش ندارد. مهندسی پرسش هم به همین دلیل روی اسکیماهای عریض زود به فلات میرسد. و نمرهٔ سنجه — نمرهٔ هر کسی، از جمله همانهایی که در ابتدای این نوشته آمد — نتیجهٔ شما را چنان ضعیف پیشبینی میکند که نباید بدون اندازهگیری خودتان در کنارش، در یک سند تصمیم ظاهر شود.
جایی که این تحلیل دیگر کار نمیکند
این تحلیل دربارهٔ پرسوجوی تحلیلی روی یک انبار دادهٔ رابطهای مستند، با اجرای فقطخواندنی، و انسانی که پاسخ را میخواند است. چهار مرز دارد.
اگر پرسشهای شما را یک گزارش تأییدشدهٔ موجود پاسخ میدهد، text-to-SQL ابزار نادرستی است. به همان گزارش مسیر بدهید. ارزش پرسوجوی زبان طبیعی در پرسشهایی است که کسی برایشان گزارش نساخته.
اگر انبار داده مستند نیست، صرفنظر از اینکه چه چیزی میخرید انتظار نتیجهٔ ته جدول را داشته باشید. هیچ سامانهای استنباط نمیکند که status_cd = 'X' یعنی حذفشده. آن دانشی است که نزد آدمهاست، و پیش از آنکه بتوان به کارش برد باید نوشته شود.
اگر پاسخها بهجای یک انسان به یک کنش خودکار خورانده میشوند، سطح دقت بهکلی سطح دیگری است، و معماری درست مجموعهای گزیده از کوئریهای پارامتری با یک رابط زبان طبیعی است — نه تولید آزاد.
و اگر دادهٔ شما اصلاً در انبار دادهٔ رابطهای نیست، ادبیات سنجه حرف چندانی برای گفتن ندارد. انبارهای سند، نمایههای جستوجو و پایگاهدادههای ستونعریض هر کدام معنای پرسوجوی خودشان، حالتهای شکست خودشان و هیچ سنجهٔ عمومی با بلوغ قابل مقایسهای ندارند. هر عددی در این نوشته، از جمله عنوانش، دربارهٔ SQL است.