0Pricing
AI Agents · درس

كيف تعمل وكلاء NL-to-SQL؟

حقن المخطط، وإنشاء الاستعلامات، وتنفيذها، وتنسيق النتائج

كيف تعمل وكلاء NL-to-SQL؟ درس مجاني في AI Agents على CoddyKit. هذا هو الدرس 1 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7. هذا الدرس جزء من مسار التعلم في AI Agents، وتقدمك يتزامن عبر الويب وتطبيق CoddyKit. تتضمن دورة AI Agents 4 دروس في المجموع.

ما وكيل NL-to-SQL؟

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

بدلًا من كتابة SELECT COUNT(*) FROM orders WHERE status='pending'، يمكن للمستخدمين ببساطة أن يسألوا: «كم عدد الطلبات المعلّقة لدينا؟»

البنية الأساسية

يتبع كل وكيل NL-to-SQL مسار المعالجة نفسه:

  1. حقن المخطط — إدخال بنية قاعدة البيانات في التلقين
  2. يولّد LLM استعلام SQL — يُنتج النموذج استعلامًا
  3. التنفيذ — تشغيل الاستعلام على قاعدة البيانات
  4. تنسيق النتائج — تحويل الصفوف إلى نص قابل للقراءة
  5. إعادة الإجابة — الرد على المستخدم
# High-level pipeline
def nl_to_sql_agent(user_question, db_connection):
    schema = get_schema(db_connection)
    sql = llm_generate_sql(user_question, schema)
    rows = execute_query(db_connection, sql)
    answer = format_results(rows, user_question)
    return answer

شرح حقن المخطط

لا يعرف LLM شيئًا عن بنية قاعدة بياناتك. يجب أن تحقن المخطط في كل تلقين حتى يعرف النموذج الجداول والأعمدة الموجودة.

يخبر وصف موجز للمخطط النموذج بما يلي: «يحتوي جدول orders على الأعمدة: id وuser_id وstatus وtotal وcreated_at.»

def build_schema_prompt(schema_info):
    lines = []
    for table in schema_info:
        cols = ', '.join(
            f"{c['name']} ({c['type']})"
            for c in table['columns']
        )
        lines.append(f"Table {table['name']}: {cols}")
    return '\n'.join(lines)

# Output:
# Table users: id (INT), email (VARCHAR), created_at (TIMESTAMP)
# Table orders: id (INT), user_id (INT), status (VARCHAR), total (FLOAT)

if __name__ == '__main__':
    demo_schema = [
        {'name': 'users', 'columns': [{'name': 'id', 'type': 'INT'}, {'name': 'email', 'type': 'VARCHAR'}]},
        {'name': 'orders', 'columns': [{'name': 'id', 'type': 'INT'}, {'name': 'user_id', 'type': 'INT'}]},
    ]
    print(build_schema_prompt(demo_schema))

تلقين توليد SQL بواسطة LLM

يجب أن يزوّد التلقين LLM بثلاثة أشياء: المخطط، والسؤال، وتعليمات صريحة لإعادة SQL صالح فقط.

من المهم جدًا تحديد أن الاستعلامات SELECT فقط وتحديد لهجة SQL المستهدفة (PostgreSQL أو MySQL أو SQLite) لأسباب تتعلق بالأمان والصحة.

SYSTEM_PROMPT = '''You are a SQL expert. Given a database schema and a question,
generate a valid {dialect} SELECT query. Return ONLY the SQL query, no explanation.
Do not use INSERT, UPDATE, DELETE, or DROP.

Schema:
{schema}
'''

def llm_generate_sql(question, schema, dialect='PostgreSQL'):
    prompt = SYSTEM_PROMPT.format(schema=schema, dialect=dialect)
    response = client.chat.completions.create(
        model='gpt-4o',
        messages=[
            {'role': 'system', 'content': prompt},
            {'role': 'user', 'content': question}
        ]
    )
    return response.choices[0].message.content.strip()

تنفيذ SQL المُولّد

بعد أن يعيد LLM جملة SQL، نفّذها على قاعدة البيانات الفعلية. استخدم الاستعلامات ذات المعاملات حيثما أمكن، وتعامل دائمًا مع الاستثناءات — إذ يمكن لـ LLM إنتاج SQL غير صالح.

يتيح تغليف التنفيذ داخل try/except إعادة المحاولة مع إرسال تلميح عن الخطأ إلى LLM.

import psycopg2

def execute_query(conn, sql):
    try:
        with conn.cursor() as cur:
            cur.execute(sql)
            columns = [desc[0] for desc in cur.description]
            rows = cur.fetchmany(100)  # limit rows
            return {'columns': columns, 'rows': rows}
    except psycopg2.Error as e:
        return {'error': str(e), 'sql': sql}

تنسيق النتائج للمستخدم

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

بالنسبة إلى مجموعات النتائج الصغيرة، مرّر الصفوف إلى LLM لتفسيرها. أما في المجموعات الكبيرة، فاحسب الإحصاءات الموجزة أولًا.

def format_results(result, original_question):
    if 'error' in result:
        return f'Query failed: {result["error"]}'

    rows = result['rows']
    columns = result['columns']

    if not rows:
        return 'No results found.'

    # For simple counts/aggregates — just return the value
    if len(columns) == 1 and len(rows) == 1:
        return f'Result: {rows[0][0]}'

    # For multi-row results — summarize
    summary = f'Found {len(rows)} rows.\n'
    for row in rows[:5]:  # show first 5
        summary += ', '.join(f'{columns[i]}: {row[i]}' for i in range(len(columns))) + '\n'
    return summary

if __name__ == '__main__':
    demo_result = {'rows': [[42]], 'columns': ['count']}
    print(format_results(demo_result, 'How many users signed up?'))
    demo_result2 = {'rows': [], 'columns': ['id']}
    print(format_results(demo_result2, 'Any orders today?'))

لماذا يصعب NL-to-SQL: الغموض

يمثل الغموض أكبر التحديات. تأمل السؤال التالي: «اعرض لي أفضل العملاء.»

  • الأفضل من حيث الإيرادات؟ أم عدد الطلبات؟ أم حداثة الطلب؟
  • خلال الشهر الماضي؟ أم على الإطلاق؟
  • أفضل 10؟ أم أفضل 100؟

يفهم البشر السياق، بينما تضع نماذج LLM افتراضات. وتحتاج الوكلاء إلى استراتيجيات للتعامل مع الأسئلة الغامضة أو طلب توضيحها.

AMBIGUITY_PROMPT = '''If the question is ambiguous, respond with JSON:
{"needs_clarification": true, "question": "your clarifying question"}

If clear, respond with the SQL query directly.

User question: {question}
'''

def generate_or_clarify(question, schema):
    response = llm_call(AMBIGUITY_PROMPT.format(
        question=question, schema=schema
    ))
    if '"needs_clarification"' in response:
        import json
        return json.loads(response)
    return {'sql': response}

لماذا يصعب NL-to-SQL: حجم المخطط

قد تحتوي قواعد بيانات المؤسسات على مئات الجداول وآلاف الأعمدة. وسيؤدي إدخال المخطط كاملًا إلى تجاوز نافذة سياق LLM.

تشمل الحلول: البحث في المخطط (تضمين أوصاف الجداول واسترجاع الجداول ذات الصلة)، وتصفية الجداول (سؤال LLM أولًا عن الجداول المطلوبة)، وضغط المخطط (حذف أعمدة الفهارس والتدقيق).

# Two-phase approach for large schemas
def get_relevant_tables(question, all_tables):
    prompt = f'''Given these tables: {all_tables}
Which 3-5 tables are most relevant to answer: "{question}"?
Return a JSON list of table names only.'''
    response = llm_call(prompt)
    import json
    return json.loads(response)

def nl_to_sql_large_db(question, conn):
    all_tables = list_all_tables(conn)  # just names
    relevant = get_relevant_tables(question, all_tables)
    schema = get_schema_for_tables(conn, relevant)
    return llm_generate_sql(question, schema)

لماذا يصعب NL-to-SQL: اختلاف لهجات SQL

لغة SQL ليست موحّدة في كل مكان. يتحول LIMIT في PostgreSQL أو MySQL إلى TOP في SQL Server. كما تختلف دوال التاريخ بين قواعد البيانات. لذلك يجب أن يعرف LLM اللهجة التي ينبغي استخدامها.

أدرج اللهجة المستهدفة دائمًا في تلقين النظام، وفكّر في إضافة أمثلة خاصة بكل لهجة ضمن التلقين بأمثلة few-shot.

DIALECT_EXAMPLES = {
    'postgresql': 'Use LIMIT for row limits. Use NOW() for current time.',
    'mysql': 'Use LIMIT for row limits. Use NOW() for current time.',
    'sqlite': 'Use LIMIT. Use datetime("now") for current time.',
    'mssql': 'Use TOP N for row limits. Use GETDATE() for current time.',
    'bigquery': 'Use LIMIT. Use CURRENT_TIMESTAMP() for current time. Use backtick for table names.'
}

def get_dialect_hint(dialect):
    return DIALECT_EXAMPLES.get(dialect.lower(), '')

if __name__ == '__main__':
    for dialect in ['postgresql', 'sqlite', 'mssql']:
        print(f'{dialect}: {get_dialect_hint(dialect)}')

حلقة استرداد الأخطاء

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

حدّد عدد المحاولات باثنتين أو ثلاث لتجنب الحلقات اللانهائية عند التعامل مع استعلامات يتعذر إصلاحها.

def nl_to_sql_with_retry(question, schema, conn, max_retries=3):
    sql = llm_generate_sql(question, schema)
    for attempt in range(max_retries):
        result = execute_query(conn, sql)
        if 'error' not in result:
            return format_results(result, question)
        # Ask LLM to fix the error
        fix_prompt = f'The SQL query failed with error: {result["error"]}\n'\
                     f'Original SQL: {sql}\n'\
                     f'Please fix the SQL query.'
        sql = llm_call(fix_prompt)
        print(f'Retry {attempt + 1} with fixed SQL')
    return 'Could not generate a valid query after retries.'

جمع المكونات كلها

يجمع وكيل NL-to-SQL المخصص للإنتاج جميع الأجزاء: استرجاع المخطط، وإنشاء التلقين، وتوليد SQL، والتحقق من صحته، والتنفيذ، واسترداد الأخطاء، وتنسيق النتائج.

يؤدي إضافة التخزين المؤقت للاستعلامات (السؤال نفسه → SQL نفسه) إلى تقليل زمن الاستجابة وتكاليف LLM بشكل كبير في الاستعلامات المتكررة.

import hashlib

query_cache = {}

def cached_nl_to_sql(question, schema_hash, conn):
    cache_key = hashlib.md5((question + schema_hash).encode()).hexdigest()
    if cache_key in query_cache:
        print('Cache hit!')
        sql = query_cache[cache_key]
    else:
        schema = get_schema(conn)
        sql = llm_generate_sql(question, schema)
        query_cache[cache_key] = sql

    result = execute_query(conn, sql)
    return format_results(result, question)

اختبار المعرفة

ما الترتيب الصحيح للخطوات في مسار معالجة وكيل NL-to-SQL؟

مراجعة: بنية NL-to-SQL

تحوّل وكلاء NL-to-SQL الأسئلة المكتوبة باللغة الطبيعية إلى استعلامات SQL قابلة للتنفيذ عبر مسار منظم: حقن المخطط → توليد SQL → التنفيذ → التنسيق → الإعادة.

تتمثل التحديات الرئيسية في غموض أسئلة المستخدمين، وكِبَر أحجام المخططات بما يتجاوز نوافذ السياق، واختلاف لهجات SQL بين قواعد البيانات. وتعالج حلقات استرداد الأخطاء حالات فشل SQL الذي يولّده LLM عند التنفيذ الأول.

الأسئلة الشائعة

هل درس «كيف تعمل وكلاء NL-to-SQL؟» مجاني؟

نعم — نص درس «كيف تعمل وكلاء NL-to-SQL؟» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة AI Agents، انتقل إلى CoddyKit PRO. تتضمن دورة AI Agents 4 دروس في المجموع.

ماذا ستتعلم في «كيف تعمل وكلاء NL-to-SQL؟»؟

حقن المخطط، وإنشاء الاستعلامات، وتنفيذها، وتنسيق النتائج تتمرن على AI Agents مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.

هل أحتاج إلى خبرة سابقة لأبدأ AI Agents؟

لا تُشترط خبرة سابقة. AI Agents على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 1 من أصل 4.

كم من الوقت يستغرق درس «كيف تعمل وكلاء NL-to-SQL؟»؟

معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.

هل يمكنني كتابة وتشغيل أكواد في درس AI Agents هذا؟

نعم. كل درس في AI Agents يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.

جميع الدروس في هذه الدورة

  1. كيف تعمل وكلاء NL-to-SQL؟
  2. فهم المخطط وحقنه
  3. إنشاء استعلامات SQL والتحقق منها
  4. التعامل مع أسئلة قواعد البيانات الملتبسة
← العودة إلى AI Agents