بناء وكلاء تحليلات بيانات موثوقين: دروس من KDD Cup

تُظهر الدروس المستفادة من كأس KDD أن وكلاء تحليلات البيانات الموثوقين يعتمدون على تقييم صارم، وتنسيق قوي للأدوات، وتعامل دقيق مع البيانات الواقعية الغامضة. تكشف المعايير على غرار المسابقات عن أنماط الفشل مبكرًا، مما يشجع على التحسين التكراري، والتفكير الشفاف، وحلقات التحقق التي تحوّل النماذج الأولية إلى مساعدين موثوقين في التحليلات.

القراءة الصوتية غير متاحة في هذا المتصفح
بناء وكلاء تحليلات بيانات موثوقين: دروس من KDD Cup

الوسوم

ملخص سريع

تُظهر الدروس المستفادة من كأس KDD أن وكلاء تحليلات البيانات الموثوقين يعتمدون على تقييم صارم، وتنسيق قوي للأدوات، وتعامل دقيق مع البيانات الواقعية الغامضة. تكشف المعايير على غرار المسابقات عن أنماط الفشل مبكرًا، مما يشجع على التحسين التكراري، والتفكير الشفاف، وحلقات التحقق التي تحوّل النماذج الأولية إلى مساعدين موثوقين في التحليلات.

بناء وكلاء تحليلات بيانات موثوقين: دروس من KDD Cup

يقع وكلاء تحليلات البيانات في موضع غير مريح ضمن الموجة الحالية من أنظمة الذكاء الاصطناعي. روبوت محادثة ينتج فقرة معقولة يكون قد نجح إذا كانت الفقرة مفيدة. أما وكيل تحليلات ينتج رقمًا معقولًا فقد نجح فقط إذا كان الرقم صحيحًا — وصحة العمل في البيانات ليست مسألة أسلوب. إنها مسألة عمليات ربط، وتصفية، وأنواع، وقابلية للقيم الفارغة، ومناطق زمنية.

يُعدّ KDD Cup، سلسلة مسابقات التنقيب عن البيانات طويلة الأمد المرتبطة بمجتمع SIGKDD، إطارًا مفيدًا لهذه المشكلة. مهام المسابقة تنافسية بطريقة محددة جدًا: فهي تمنحك مجموعة بيانات لم تصممها، وسؤالًا لم تكتبه، ودالة تقييم لا يمكنك التفاوض معها. يستخدم منشور مدونة NVIDIA للذكاء الاصطناعي، "Building Reliable Data Analytics Agents: Lessons from the KDD Cup"، هذا السياق لفحص ما يلزم كي يُؤتمن وكيل على عمل تحليلي حقيقي بدلًا من أن يبدو فقط أنه يقوم به.

يتبنّى هذا المقال الموضوع نفسه — الموثوقية كمشكلة هندسية جوهرية — ويحوّله إلى دليل بناء: ما الذي يجب تثبيته، وكيفية هيكلة خط الأنابيب، وكيفية اختباره. توضيح واحد في البداية: المصدر هو مقال هندسي من مورّد، وأنماط التنفيذ أدناه هي ممارسة عامة قابلة لإعادة الإنتاج ومدفوعة بذلك الموضوع. وهي ليست ادعاءات حول التفاصيل الداخلية لأي نظام محدد موصوف هناك.

لماذا يفشل وكلاء التحليلات بشكل مختلف

معظم حالات فشل الوكلاء في السياقات الحوارية مزعجة. ومعظم حالات فشل الوكلاء في السياقات التحليلية صامتة. الوكيل الذي يهلوس اسم عمود عادةً ما يحصل على خطأ. أما الوكيل الذي يربط جدولين على مفتاح خاطئ فغالبًا لا يفعل. إنه يعيد رقمًا، ويبدو الرقم معقولًا، والرقم خاطئ.

تهيمن ثلاثة أنماط فشل:

انحراف المخطط والهلوسة. يفترض النموذج وجود عمود باسم user_id بينما يحتوي الجدول على uid. وبدون فحص داخلي، يملأ النموذج الفجوات بأسماء معقولة.

استعلامات خاطئة دلاليًا لكنها صحيحة نحويًا. الربط الداخلي يُسقط بصمت صفوفًا كان الربط الأيسر سيبقيها. شرط WHERE على عمود قابل للقيم الفارغة يزيل سجلات كان المحلل يتوقع رؤيتها.

عدم قابلية إعادة الإنتاج. السؤال نفسه إذا طُرح مرتين ينتج استعلامين مختلفين وإجابتين مختلفتين. ولا يوجد أثر يمكن تدقيقه.

كل ممارسة أدناه موجودة لسدّ إحدى هذه الفجوات. والفكرة الموحّدة بسيطة: عامل الوكيل كخط أنابيب بمراحل قابلة للتحقق، لا كأمر نصي يُصدر إجابات.

المتطلبات

لمتابعة ما يلي، تحتاج إلى:

  • Python 3.11 أو أحدث.
  • صدفة شبيهة بـ Unix (macOS أو Linux أو WSL). تفترض الأوامر وجود bash أو zsh.
  • الوصول إلى أي LLM يتبع التعليمات عبر HTTP API أو خادم محلي. الأمثلة أدناه محايدة تجاه المزوّد وتشير إلى معرّف نموذج توفره أنت.
  • ذاكرة RAM كافية لاحتواء مجموعة البيانات التي تعمل عليها، أو محرك قاعدة بيانات يمكنه التدفق إلى القرص. لا تُشترط GPU للسقالات نفسها.

المجموعة التقنية مملة عمدًا: Pandas للبيانات في الذاكرة، وDuckDB لتنفيذ SQL على الملفات دون خادم، وPydantic لعقود أدوات محددة الأنواع، وTenacity لمنطق إعادة المحاولة، وpytest لاختبارات الانحدار.

التثبيت خطوة بخطوة

ابدأ بإنشاء بيئة معزولة حتى لا تتعارض تبعيات الوكيل مع Python النظام لديك.

python3 -m venv .venv

فعّلها. على Windows، استخدم .venv\Scripts\activate بدلًا من ذلك.

source .venv/bin/activate

قم بترقية pip قبل تثبيت أي شيء، لأن الإصدارات الأقدم تحل التبعيات بشكل أقل قابلية للتنبؤ.

python -m pip install --upgrade pip

ثبّت التبعيات الأساسية. يشغّل DuckDB استعلامات SQL التحليلية مباشرةً على ملفات CSV وParquet، ما يزيل فئة كاملة من أخطاء "وجّه الوكيل إلى قاعدة البيانات الخطأ" أثناء التطوير.

pip install "pandas>=2.0" duckdb "pydantic>=2" pyyaml tenacity pytest openai

أنشئ هيكل المشروع. إبقاء الأدوات وفحوصات التحقق والتقييمات في مجلدات منفصلة يجعل من الواضح أين تنتمي أي قطعة منطق جديدة.

mkdir -p analytics_agent/tools analytics_agent/checks analytics_agent/evals data artifacts
touch analytics_agent/__init__.py

اكتب ملف إعدادات بدلًا من ترميز أسماء النماذج والمسارات بشكل صلب. هذا هو التغيير الوحيد الذي يحسّن قابلية إعادة الإنتاج أكثر من غيره، لأن كل تشغيل يوصف بواسطة أثر يمكنك إيداعه.

cat > config.yaml <<'YAML'
model:
  id: "<your-model-id>"
  temperature: 0
  max_repair_attempts: 3
data:
  source: "data/orders.csv"
  sample_rows: 20
limits:
  max_rows_returned: 5000
  statement_timeout_seconds: 30
YAML

صدّر بيانات اعتماد API الخاصة بك كمتغيرات بيئة حتى لا تدخل المستودع أبدًا. لا تُودع المفاتيح أبدًا في ملف إعدادات.

export ANALYTICS_AGENT_API_KEY="<your-key>"

اضبط temperature على 0 في الإعدادات. لا يضمن الحتمية عبر المزوّدين، لكنه يزيل أحد مصادر التباين ويجعل إسناد الانحدارات أسهل.

إرساء الوكيل في المخطط الفعلي

القاعدة الأولى لوكيل تحليلات موثوق: يجب ألا يخمّن أبدًا شكل البيانات. قبل الإجابة عن أي سؤال، قم بتنميط المصدر وضع هذا التنميط في سياق المطالبة.

import duckdb

con = duckdb.connect()
con.execute("CREATE VIEW t AS SELECT * FROM read_csv_auto('data/orders.csv')")

print(con.execute("DESCRIBE t").df())
print(con.execute("SUMMARIZE t").df())

يمنح DESCRIBE أسماء الأعمدة وأنواعها؛ ويضيف SUMMARIZE أعداد القيم الفارغة، وأعداد القيم المميزة، والتوزيعات الأساسية. تمرير كليهما إلى النموذج يكلّف بضع مئات من الرموز ويزيل معظم حالات فشل هلوسة الأعمدة.

خذ عيّنة من صفوف حقيقية أيضًا، لكن بطريقة حتمية — بذرة ثابتة أو ORDER BY مع LIMIT — حتى ينتج السؤال نفسه السياق نفسه في كل تشغيل.

تقييد سطح الأدوات

لا يكون الوكيل موثوقًا إلا بقدر موثوقية الإجراءات التي يمكنه اتخاذها. عرّف مجموعة صغيرة ومحددة الأنواع من الأدوات وارفض كل ما عداها.

from pydantic import BaseModel, Field

class DescribeTable(BaseModel):
    table: str = Field(..., description="Name of a registered view.")

class SampleRows(BaseModel):
    table: str
    limit: int = Field(20, ge=1, le=100)

class RunSQL(BaseModel):
    sql: str = Field(..., description="A read-only SELECT or WITH statement.")
    rationale: str = Field(..., description="Why this query answers the sub-question.")

أربع أدوات — describe، وsample، وrun SQL، وأداة إجابة نهائية — تغطي قدرًا مفاجئًا من العمل التحليلي. وكل أداة إضافية تضاعف مساحة الطرق التي يمكن أن يخطئ بها الوكيل.

جعل التنفيذ آمنًا وحتميًا

يجب أن تكون SQL المولّدة للقراءة فقط ومحدودة. تصفية الكلمات المفتاحية بوابة أولى مفيدة، لكن كن صريحًا بشأن حدودها: فهي ليست حدًا أمنيًا. اقرنها باتصال قاعدة بيانات للقراءة فقط.

FORBIDDEN = ("insert", "update", "delete", "drop", "alter", "create", "attach", "copy")

def safe_sql(sql: str) -> str:
    stripped = sql.strip().rstrip(";")
    lowered = stripped.lower()
    if not lowered.startswith(("select", "with")):
        raise ValueError("Only SELECT and WITH statements are permitted.")
    if any(kw in lowered for kw in FORBIDDEN):
        raise ValueError("Statement contains a forbidden keyword.")
    return stripped

حتى SELECT قد تكون مكلفة، لذا اضبط مهلة للاستعلام وحدًا صارمًا للصفوف. الوكيل الذي يعيد 500,000 صف لم يجب عن السؤال؛ بل نقل المشكلة إلى المرحلة التالية.

التحقق من كل نتيجة قبل وصولها إلى المستخدم

هذه هي الخطوة التي تتخطاها معظم الفرق، وهي الأكثر أهمية. بعد تنفيذ كل استعلام، شغّل تحققات برمجية على النتيجة. ينبغي أن تؤدي حالات الفشل إلى حلقة إصلاح، لا إلى إجابة واثقة.

def assert_sane(df, min_rows=1, max_null_fraction=0.2, unique_key=None):
    assert len(df) >= min_rows, f"Expected >= {min_rows} rows, got {len(df)}"
    if unique_key:
        assert df[unique_key].is_unique, f"{unique_key} is not unique after the join"
    for col in df.columns:
        frac = df[col].isna().mean()
        assert frac <= max_null_fraction, f"{col} is {frac:.0%} null"

فحص unique_key قيّم بشكل خاص. فهو يلتقط الربط الكلاسيكي متعدد إلى متعدد الذي يضخّم أعداد الصفوف ويضاعف بهدوء كل مجموع في تجميع لاحق.

غلّف تسلسل التوليد-التحقق-الإصلاح في حلقة إعادة محاولة محدودة. أعطِ النموذج خطأ التحقق الدقيق ودعه يعدّل؛ وضع حدًا للمحاولات حتى لا يظل وكيل مرتبك في حلقة لا نهائية.

from tenacity import retry, stop_after_attempt, wait_exponential, retry_if_exception_type

@retry(
    stop=stop_after_attempt(3),
    wait=wait_exponential(multiplier=1, min=1, max=8),
    retry=retry_if_exception_type(ValueError),
)
def generate_and_execute(question, schema_context):
    sql = safe_sql(propose_sql(question, schema_context))
    df = con.execute(sql).df()
    assert_sane(df)
    return df

التتبع وإعادة التشغيل

في كل تشغيل، ألحق سجلًا منظمًا بملف JSONL: السؤال، وتجزئة سياق المخطط، وSQL المولّدة، ونتيجة التحقق، والإجابة النهائية، والتوقيت.

import json, time, hashlib

def log_event(path, **fields):
    fields["ts"] = time.time()
    with open(path, "a") as f:
        f.write(json.dumps(fields) + "\n")

log_event("artifacts/trace.jsonl", question=q, sql=sql, rows=len(df))

هذا أمر غير لامع، وهو الفرق بين التنقيح بالاستدلال والتنقيح بالتخمين. عندما يُطعَن في إجابة بعد ثلاثة أسابيع، يكون التتبع هو الأثر الذي يحسم النزاع.

أمثلة الاستخدام

نقطة دخول دنيا من سطر الأوامر تجعل الوكيل قابلًا للاختبار في CI وفي الطرفية. أبقِ الواجهة ضيقة.

python -m analytics_agent.run \
  --config config.yaml \
  --question "What was the monthly revenue trend for the top three regions in the last year?"

شغّله على ملف Parquet محلي بدلًا من CSV بتغيير data.source في الإعدادات — فلا تتغير طبقتا التنميط والأدوات.

python -m analytics_agent.run --config config.local.yaml --question "Which customers have not ordered in 90 days?"

للاستكشاف التفاعلي، أبقِ خطوة التنميط جاهزة وأعد استخدام اتصال DuckDB حتى لا تعيد الأسئلة المتكررة قراءة الملف.

تقييم الموثوقية بدلًا من الانطباعات

الموثوقية خاصية قابلة للقياس، وليست شعورًا. ابنِ مجموعة ذهبية من عشرين إلى خمسين سؤالًا بإجابات تحققت منها يدويًا أو باستعلام مستقل. قيّم كل تشغيل على ثلاثة محاور: هل أنتج الإجابة الصحيحة، وهل أنتجها باستعلام صالح، وهل أنتجها ضمن ميزانية التكلفة.

pytest analytics_agent/evals -q --tb=short

ثمة اختبار مفيد يؤكد الحتمية صراحةً — شغّل السؤال نفسه مرتين وقارن إطاري البيانات الناتجين.

def test_repeatable(agent, question):
    a = agent.ask(question)
    b = agent.ask(question)
    assert a.equals(b), "Agent output is not reproducible at temperature 0"

تتبّع معدل النجاح بمرور الوقت. الانخفاض بعد تغيير في المطالبة أو النموذج إشارة؛ وبدون مجموعة التقييم، يكون انحدارًا خفيًا يظهر كرقم خاطئ في لوحة معلومات بعد أشهر.

حدود مفتوحة تستحق التسمية

ثلاثة قيود تقاوم الإصلاحات السهلة. أولًا، يمكن أن تكون SQL المولّدة بواسطة LLM صحيحة على عيّنتك وخاطئة في حالات حدية لم تحتوِها العينة، لذا فإن تغطية مجموعة التقييم تهم أكثر من حجمها. ثانيًا، تصفية SQL القائمة على الكلمات المفتاحية دفاع في العمق، وليست دفاعًا؛ فالعزل ينتمي إلى مستوى قاعدة البيانات. ثالثًا، "موثوق" نسبي حسب المجال — معدل نجاح 95% ممتاز للتحليل الاستكشافي وغير مقبول للتقارير التنظيمية. حدّد العتبة قبل أن تبني، لا بعد ذلك.

الخاتمة

الدرس الذي يجعله سياق KDD Cup ملموسًا هو أن وكلاء التحليلات يُقيَّمون مثل مشاركات علم البيانات، لا مثل ردود المحادثة: مقابل أسئلة محجوزة، وبلا درجات جزئية عن الثقة. تأتي الموثوقية من السقالات المحيطة بالنموذج — إرساء المخطط قبل التوليد، وسطح أدوات ضيق ومحدد الأنواع، وتنفيذ محدود للقراءة فقط، وتحقق برمجي من كل نتيجة، وحلقات إصلاح محدودة، ومجموعة انحدار تلتقط الانحراف. لا شيء من هذه المكونات غريب، ولا شيء منها اختياري إذا كان الناتج رقمًا سيتصرف أحدهم بناءً عليه. ابدأ بخطوة التنميط وتأكيدات التحقق؛ فهي تمنح أكبر مكسب في الموثوقية بأقل جهد، وكل ما عداها يُبنى فوقها.

المصدر: "Building Reliable Data Analytics Agents: Lessons from the KDD Cup," مدونة NVIDIA للذكاء الاصطناعي — https://developer.nvidia.com/blog/building-reliable-data-analytics-agents-lessons-from-the-kdd-cup

المصادر