Digisky

پرتگاه text-to-SQL: چرا نتیجهٔ سنجه روی انبار دادهٔ شما تکرار نمی‌شود

نمرهٔ سنجه‌های text-to-SQL به انبار دادهٔ تولید منتقل نمی‌شود. این نوشته ارقام منتشرشدهٔ Spider 1.0، Spider 2.0 و BIRD را تا منبع اصلی‌شان دنبال می‌کند، برمی‌شمارد اسکیمای یک انبار داده چه چیزهایی دارد که اسکیمای دانشگاهی ندارد، و داربستی می‌دهد برای اندازه‌گیری دقت روی اسکیمای خودتان.

· Digisky
text-to-SQLسنجه‌هاSpider 2.0BIRDلایهٔ معناییانبار داده

چرا این نوشته

ارقام 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 2023Gao 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.0o1-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.nameDIM_CUST_ACCT.SRC_SYS_PTY_NMستون محتمل انتخاب می‌شود، ستون نادرست به کار می‌رود
تعریف‌های کسب‌وکاراز خود پرسش برمی‌آید«مشتری فعال» قاعده‌ای چهل‌خطی است که مالکش مالی استکوئری درست که به پرسش نادرست پاسخ می‌دهد
ابعاد کندتغییرنداردvalid_from / valid_to روی هر بُعدشمارش مضاعف خاموش روی سطرهای نسخه‌دار
حذف نرمنداردis_deleted، status_cd = 'X'، سطرهای سنگ‌قبررکوردهای حذف‌شده در هر جمع می‌آیند
جدول‌های تکرارینداردorders، orders_v2، orders_new، stg_ordersکوئری روی جدولی رهاشده اجرا می‌شود
معنای nullیکنواختnull اینجا یعنی «نامعلوم» و آنجا یعنی «صفر»تجمیع‌هایی که با گزارش رسمی فرق دارند
گویشSQLiteBigQuery، Snowflake، SQL Server، Oracle، ClickHouseSQL نامعتبر نحوی یا جابه‌جاشده از نظر معنایی
کار چندمرحله‌اییک کوئریزنجیرهٔ 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 است.