0Pricing
AI Agents · Lekcja

Jak działają agenci NL-to-SQL

Wstrzykiwanie schematu, generowanie zapytań, ich wykonywanie i formatowanie wyników.

Jak działają agenci NL-to-SQL to bezpłatna lekcja AI Agents na CoddyKit. To lekcja 1 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej AI Agents, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs AI Agents zawiera 4 lekcji w sumie.

Czym jest agent NL-to-SQL?

Agent przetwarzający język naturalny na SQL tłumaczy pytania zadawane zwykłym językiem na zapytania SQL, wykonuje je w bazie danych i zwraca odpowiedzi zrozumiałe dla człowieka.

Zamiast pisać SELECT COUNT(*) FROM orders WHERE status='pending', użytkownicy po prostu pytają: "Ile mamy oczekujących zamówień?"

Podstawowa architektura

Każdy agent NL-to-SQL korzysta z tego samego potoku:

  1. Wstrzyknięcie schematu — wstrzyknięcie struktury bazy danych do promptu
  2. LLM generuje SQL — model tworzy zapytanie
  3. Wykonanie — uruchomienie zapytania w bazie danych
  4. Formatowanie wyników — przekształcenie wierszy w czytelny tekst
  5. Zwrócenie odpowiedzi — odpowiedź dla użytkownika
# 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

Wstrzykiwanie schematu — wyjaśnienie

LLM nie zna struktury Państwa bazy danych. Należy wstrzykiwać schemat do każdego promptu, aby model wiedział, jakie tabele i kolumny istnieją.

Zwarty opis schematu informuje model: "Tabela orders ma kolumny: 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))

Monit do generowania SQL przez LLM

Monit musi przekazywać LLM-owi trzy informacje: schemat, pytanie oraz wyraźne polecenie, aby zwrócił wyłącznie poprawny SQL.

Wyraźne wskazanie, że dozwolone są wyłącznie zapytania SELECT, oraz określenie docelowego dialektu SQL (PostgreSQL, MySQL, SQLite) ma kluczowe znaczenie dla bezpieczeństwa i poprawności.

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()

Wykonywanie wygenerowanego SQL

Po zwróceniu zapytania SQL przez LLM należy wykonać je w rzeczywistej bazie danych. W miarę możliwości należy używać zapytań parametryzowanych i zawsze przechwytywać wyjątki — LLM może wygenerować niepoprawny SQL.

Umieszczenie wykonania w bloku try/except umożliwia ponowienie próby z podpowiedzią dotyczącą błędu przekazaną z powrotem do LLM-a.

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}

Formatowanie wyników dla użytkownika

Surowe wiersze z bazy danych nie są przyjazne dla użytkownika. Agent musi przekształcić je w odpowiedź w języku naturalnym.

W przypadku niewielkich zbiorów wyników należy przekazać wiersze z powrotem do LLM-a w celu ich interpretacji. W przypadku dużych zbiorów należy najpierw obliczyć statystyki podsumowujące.

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?'))

Dlaczego NL-to-SQL jest trudne: niejednoznaczność

Niejednoznaczność jest największym wyzwaniem. Rozważmy pytanie: "Pokaż najlepszych klientów."

  • Najlepszych według przychodu, liczby zamówień czy daty ostatniej aktywności?
  • Z ostatniego miesiąca czy z całego okresu?
  • 10 najlepszych czy 100 najlepszych?

Ludzie rozumieją kontekst, natomiast LLM-y opierają się na założeniach. Agenci potrzebują strategii obsługi niejednoznacznych pytań lub ich doprecyzowywania.

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}

Dlaczego NL-to-SQL jest trudne: rozmiar schematu

Firmowe bazy danych mogą zawierać setki tabel i tysiące kolumn. Wstrzyknięcie całego schematu przekroczyłoby okno kontekstu LLM-a.

Możliwe rozwiązania to: wyszukiwanie schematu (osadzanie opisów tabel i pobieranie istotnych opisów), filtrowanie tabel (najpierw zapytanie LLM-a, które tabele są potrzebne) oraz kompresja schematu (pomijanie kolumn indeksów i audytu).

# 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)

Dlaczego NL-to-SQL jest trudne: różnice w dialektach SQL

SQL nie jest uniwersalny. LIMIT w PostgreSQL/MySQL staje się TOP w SQL Server. Funkcje dat różnią się w poszczególnych bazach danych. LLM musi wiedzieć, którego dialektu użyć.

Docelowy dialekt należy zawsze uwzględniać w prompcie systemowym, a także rozważyć dodanie przykładów właściwych dla danego dialektu w promptingu 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)}')

Pętla obsługi błędów

Wygenerowany SQL często kończy się niepowodzeniem przy pierwszej próbie. Solidny agent implementuje pętlę obsługi błędów: wysyła nieudany SQL i komunikat błędu z powrotem do LLM-a oraz prosi go o poprawienie zapytania.

Liczbę ponowień należy ograniczyć do 2–3, aby uniknąć nieskończonych pętli w przypadku zapytań, których nie da się naprawić.

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.'

Połączenie wszystkich elementów

Produkcyjny agent NL-to-SQL łączy wszystkie elementy: pobieranie schematu, konstruowanie promptu, generowanie SQL, walidację, wykonywanie, obsługę błędów i formatowanie wyników.

Dodanie pamięci podręcznej zapytań (to samo pytanie → ten sam SQL) znacząco zmniejsza opóźnienia i koszty LLM-a w przypadku powtarzających się zapytań.

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)

Sprawdzenie wiedzy

Jaka jest prawidłowa kolejność kroków w potoku agenta NL-to-SQL?

Podsumowanie: architektura NL-to-SQL

Agenci NL-to-SQL przekształcają pytania w języku naturalnym w wykonywalne zapytania SQL za pomocą uporządkowanego potoku: wstrzyknięcie schematu → generowanie SQL → wykonanie → formatowanie → zwrócenie.

Główne wyzwania to niejednoznaczność pytań użytkowników, duże schematy przekraczające okna kontekstu oraz różnice między dialektami SQL w poszczególnych bazach danych. Pętle obsługi błędów radzą sobie z SQL-em wygenerowanym przez LLM, który kończy się niepowodzeniem przy pierwszym wykonaniu.

Często zadawane pytania

Czy lekcja „Jak działają agenci NL-to-SQL” jest bezpłatna?

Tak — pełny tekst „Jak działają agenci NL-to-SQL” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu AI Agents, przejdź na CoddyKit PRO. Kurs AI Agents zawiera 4 lekcji w sumie.

Co nauczysz się w „Jak działają agenci NL-to-SQL”?

Wstrzykiwanie schematu, generowanie zapytań, ich wykonywanie i formatowanie wyników. Ćwiczysz AI Agents z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.

Czy potrzebuję doświadczenia, aby zacząć AI Agents?

Nie wymagamy żadnego doświadczenia. AI Agents w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 1 z 4.

Ile czasu zajmuje lekcja „Jak działają agenci NL-to-SQL”?

Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.

Czy mogę pisać i uruchamiać kod w tej lekcji AI Agents?

Tak. Każda lekcja AI Agents zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.

Wszystkie lekcje w tym kursie

  1. Jak działają agenci NL-to-SQL
  2. Rozumienie i wstrzykiwanie schematu
  3. Generowanie i walidowanie zapytań SQL
  4. Obsługa niejednoznacznych pytań dotyczących baz danych
← Powrót do AI Agents