Creazione di un RAG agenziale per le applicazioni di Text-to-SQL

La combinazione di generazione assistita da recupero (RAG) e modelli AI generativi ha portato cambiamenti al processing del linguaggio naturale migliorando le risposte alle query. Nel campo di Agentic RAG, questo metodo convenzionale di affidarsi a un modello monolitico per compiti è stato potenziato introducendo modularità e autonomia. Suddividendo il processo di risoluzione dei problemi in strumenti integrati all’interno di un agente, Agentic RAG fornisce vantaggi come precisione, trasparenza, scalabilità e capacità di debug.

La Visione dietro Agentic RAG per Text-to-SQL

I sistemi RAG tradizionali spesso recuperano documenti rilevanti e si affidano a un unico modello monolitico per generare risposte. Anche se questo è un metodo efficace in alcuni casi, quando si tratta di output strutturali come nel caso della generazione di SQL, questo approccio potrebbe non essere il più efficace. Qui possiamo sfruttare il potere del framework Agentic RAG, dove noi:

  1. Dividiamo i compiti in strumenti più piccoli e gestibili all’interno di un agente
  2. Miglioriamo la precisione assegnando compiti a strumenti specializzati
  3. Incrementiamo la trasparenza tracciando il ragionamento e il flusso di lavoro di ciascuno strumento
  4. Semplifichiamo la scalabilità e il debug tramite un design modulare

Parliamo di come funziona questo strumento e del ruolo di ciascun componente nel trasformare le domande degli utenti in query SQL accurate.

Panoramica dell’Architettura

La struttura comprende un agente che utilizza strumenti all’interno del flusso di lavoro text-to-SQL. Il processo può essere riassunto come segue:

User Query → Strumento di Trasformazione della Query → Strumento di Prompting a Pochi Esempi → Strumento di Ricerca Ibrida → Strumento di Re-Ranking → Strumento di Recupero Tabelle → Strumento di Creazione di Prompt → Strumento di Esecuzione LLM → Strumento di Esecuzione SQL → Output Finale

1. Strumento di Trasformazione della Query Utente

Questo strumento comporta l’elaborazione della query dell’utente per una migliore comprensione del LLM. Affronta ambiguità, riformula le domande degli utenti, traduce le abbreviazioni nelle loro forme e fornisce contesto quando necessario.

Miglioramenti

  • Gestire riferimenti temporali. Mappare termini come “a partire da oggi” o “fino ad ora” a date esplicite.
  • Sostituire parole ambigue. Ad esempio, “recenti” potrebbe essere sostituito da “ultimi 7 giorni.”
  • Collegare abbreviazioni o sigle ai loro nomi.

Esempio

Input: “Mostrami le vendite recenti MTD.”

Query trasformata: “Recupera i dati di vendita per gli ultimi 7 giorni (Mese fino ad oggi).”

Python

 

from datetime import date, timedelta

def transform_query(user_query):
    # Gestire riferimenti temporali aperti
    today = date.today()
    transformations = {
        "as of today": f"up to {today}",
        "till now": f"up to {today}",
        "recent": "last 7 days",
        "last week": f"from {today - timedelta(days=7)} to {today}",
    }
    
    for key, value in transformations.items():
        user_query = user_query.replace(key, value)

    # Mappare abbreviazioni comuni
    abbreviations = {
        "MTD": "Month to Date",
        "YTD": "Year to Date",
    }
    for abbr, full_form in abbreviations.items():
        user_query = user_query.replace(abbr, full_form)

    return user_query

query_transform_tool = Tool(
    name="Query Transformer",
    func=transform_query,
    description="Refines user queries for clarity and specificity, handles abbreviations and open-ended terms."
)

2. Strumento di Prompting a Pochi Esempi

Questo strumento chiama il LLM per identificare una domanda di un certo tipo da un insieme (possiamo anche dire che corrisponde al modello). La domanda corrispondente arricchisce il prompt con un esempio di query SQL.

Esempio di Flusso di Lavoro

1. Domanda di input: “Mostrami le vendite totali per prodotto per gli ultimi 7 giorni.”

2. Modelli predefiniti:

  • “Mostra le vendite raggruppate per regione.” → Esempio SQL; SELECT regione, SUM(vendite) …
  • “Mostra le vendite totali per prodotto.” → Esempio SQL; SELECT nome_prodotto, SUM(vendite) …

3. Domanda più simile: “Mostra le vendite totali per prodotto.”

4. Esempio di output SQL: SELECT nome_prodotto, SUM(vendite) FROM …

Python

 

from langchain.chat_models import ChatOpenAI

llm = ChatOpenAI(model="gpt-4")

predefined_examples = {
    "Show sales grouped by region": "SELECT region, SUM(sales) FROM sales_data GROUP BY region;",
    "Show total sales by product": "SELECT product_name, SUM(sales) FROM sales_data GROUP BY product_name;",
}

def find_similar_question(user_query):
    prompt = "Find the most similar question type for the following user query:\n"
    prompt += f"User Query: {user_query}\n\nOptions:\n"
    for example in predefined_examples.keys():
        prompt += f"- {example}\n"
    prompt += "\nRespond with the closest match."

    response = llm.call_as_function(prompt)
    most_similar = response['content']
    return predefined_examples.get(most_similar, "")

few_shot_tool = Tool(
    name="Few-Shot Prompting",
    func=find_similar_question,
    description="Finds the most similar question type using an additional LLM call and retrieves the corresponding example SQL."
)

3. Strumento di Ricerca Ibrida

Per un recupero robusto, questo strumento combina la ricerca semantica, la ricerca basata su parole chiave BM25 e il mapping basato su parole chiave. I risultati della ricerca di questi metodi vengono uniti utilizzando la fusione del rank reciproco.

Come si mette tutto insieme?

Mappatura Tabella Parole Chiave

Questo approccio mappa le tabelle alle parole chiave contenute nella query. Per esempio:

  • La presenza di “vendite” porta alla preselezione della tabella delle vendite.
  • La presenza di “prodotto” porta alla preselezione della tabella dei prodotti. 

Mappatura Sovrapposizione Parole Chiave (BM25)

Questo è un metodo di ricerca basato sulla sovrapposizione delle parole chiave che preseleziona le tabelle in base alla rilevanza. Per questo, applicheremo la tecnica BM25. Questo ordina i documenti in base alla rilevanza per una ricerca dell’utente. Questa tecnica di ricerca considera la saturazione del termine in vista e TF-IDF (Frequenza del Termine-Inverso della Frequenza del Documento). 

La Frequenza del Termine (TF) aiuta a misurare la frequenza di un termine in un dato documento. L’approccio della Frequenza del Documento Inverso (IDF) sottolinea le parole che appaiono in ogni documento riducendo l’importanza.

La normalizzazione tiene conto della lunghezza del documento per evitare qualsiasi pregiudizio verso documenti più lunghi.

Dato:

  • sales_data: Contiene termini come “vendite,” “data,” “prodotto.”
  • prodotti: Contiene termini come “prodotto,” “categoria.”
  • ordini: Contiene termini come “ordine,” “data,” “cliente.”
  • finanziari: Contiene termini come “ricavo,” “profitto,” “spesa.”

Query dell’utente: “Mostra le vendite totali per prodotto.”

  • Identifica i termini nella query dell’utente: [“vendite,” “prodotto”].
  • Ordina ogni documento (in base alla frequenza e alla rilevanza di questi termini) in DataBaseTable.

Rilevanza dei documenti:

  • vendite: Alta rilevanza a causa sia di “vendite” che di “prodotto.”
  • prodotti: Alta rilevanza a causa di “prodotto.”
  • ordini: Rilevanza inferiore a causa della presenza solo di “vendite.”
  • finanziari: Non rilevante.

Output:

Lista classificata: [prodotti, sales_data, ordini, finanziari]

Ricerca Semantica

In questo metodo di ricerca, come suggerisce il nome, troviamo tabelle semanticamente simili utilizzando vettori di embeddamento. Otteniamo ciò calcolando un punteggio di similarità, come la similarità del coseno, tra i vettori del documento (tabelle) e i vettori della query dell’utente.

Fusione del Rango Reciproco

Combina i risultati di ricerca BM25 e semantica utilizzando la strategia di fusione del rango reciproco, che viene spiegata un po’ più in dettaglio di seguito:

Fusione del rango reciproco (RRF) che combina BM25 e ricerca semantica:

RRF è un metodo per combinare i risultati di più algoritmi di ranking (ad esempio, BM25 e ricerca semantica). Assegna un punteggio a ciascun documento in base al suo rango nei singoli metodi, assegnando punteggi più alti ai documenti posizionati più in alto in più metodi.

Formula RRF:

RRF(d) = Σ(r ∈ R) 1 / (k + r(d))

Dove:

  • d è un documento
  • R è l’insieme di classificatori (metodi di ricerca)
  • k è una costante (tipicamente 60)
  • r(d) è il rango del documento d nel metodo di ricerca r

Esempio passo dopo passo

Dati di input.

1. Risultati di classificazione BM25:

  • prodotti (Rango 1)
  • dati_vendita (Rango 2)
  • ordini (Rango 3)

2. Risultati di classificazione della ricerca semantica:

  • dati_vendita (Rango 1)
  • finanziamenti (Rango 2)
  • prodotti (Rango 3)

Fusione passo dopo passo

Per ciascuna tabella, calcola il punteggio:

1. dati_vendita

  • Rango BM25 = 2, Rango Semantico = 1
  • Punteggio RRF = (1/60+2 ) + (1/60+1) = 0,03252

2. prodotti

  • Rango BM25 = 1, Rango Semantico = 3
  • Punteggio RRF = (1/60+1) + (1/60+3)= .03226

3. ordini

  • BM25 Rank = 3, Semantic Rank = Non classificato
  • Punteggio RRF = (1/60+3) = 0,01587

4. dati finanziari

  • BM25 Rank = Non classificato, Semantic Rank = 2
  • Punteggio RRF = (1/60+2) = 0,01613

5. Ordina per punteggio RRF

  • sales_data (punteggio più alto dovuto al rango superiore nella ricerca semantica).
  • prodotti (punteggio elevato da BM25).
  • ordini (relevanza complessiva inferiore).
  • dati finanziari (sovrapposizione limitata).

Output finale: [‘sales_data’, ‘prodotti,’ ‘dati finanziari,’ ‘ordini’]

Tabelle recuperate utilizzando il mapping del Tabella delle parole chiave sono sempre incluse.

Python

 

from rank_bm25 import BM25Okapi

def hybrid_search(query):
    # Mappatura basata su parole chiave
    keyword_to_table = {
        "sales": "sales_data",
        "product": "products",
    }
    keyword_results = [table for keyword, table in keyword_to_table.items() if keyword in query.lower()]

    # Ricerca BM25
    bm25 = BM25Okapi(["sales_data", "products", "orders", "financials"])
    bm25_results = bm25.get_top_n(query.split(), bm25.corpus, n=5)

    # Ricerca semantica
    semantic_results = vector_store.similarity_search(query, k=5)

    # Fusione del rango reciproco
    def reciprocal_rank_fusion(results):
        rank_map = {}
        for rank, table in enumerate(results):
            rank_map[table] = rank_map.get(table, 0) + 1 / (1 + rank)
        return sorted(rank_map, key=rank_map.get, reverse=True)

    combined_results = reciprocal_rank_fusion(bm25_results + semantic_results)

    return list(set(keyword_results + combined_results))

hybrid_search_tool = Tool(
    name="Hybrid Search",
    func=hybrid_search,
    description="Combines keyword mapping, BM25, and semantic search with RRF for table retrieval."
)

4. Strumento di riclassificazione

Questo strumento garantisce che le tabelle più rilevanti siano prioritarie.

Esempio

  • Tabelle di input: [“sales_data,” “prodotti,” “dati finanziari”]
  • Logica di riclassificazione
    • Per ogni tabella, calcola un punteggio di rilevanza concatenando la query e la descrizione della tabella.
    • Ordina per punteggio di rilevanza.
  • Output: [“sales_data,” “prodotti”]

Un po’ più nel dettaglio della logica di riordinamento:

Il crossencoder calcola un punteggio di rilevanza analizzando la query concatenata e la descrizione della tabella come una singola coppia di input. Questo processo coinvolge:

  • Input di coppia. La query e ciascuna descrizione della tabella vengono accoppiate e passate in input al cross-encoder.
  • Codifica congiunta. A differenza degli encoder separati (ad esempio, bi-encoder), il cross-encoder codifica congiuntamente la coppia, consentendogli di catturare meglio il contesto e le dipendenze tra la query e la descrizione della tabella.
  • Assegnazione di punteggio. Il modello restituisce un punteggio di rilevanza per ciascuna coppia, indicando quanto bene la tabella corrisponda alla query.
Python

 

from transformers import pipeline

reranker = pipeline("text-classification", model="cross-encoder/ms-marco-TinyBERT-L-2")

def re_rank_context(query, results):
    scores = [(doc, reranker(query + " " + doc)[0]['score']) for doc in results]
    return [doc for doc, score in sorted(scores, key=lambda x: x[1], reverse=True)]

re_rank_tool = Tool(
    name="Re-Ranker",
    func=re_rank_context,
    description="Re-ranks the retrieved tables based on relevance to the query."
)

5. Strumento per la Creazione di Promemoria

Questo strumento costruisce un promemoria dettagliato per il modello linguistico, incorporando la query raffinata dell’utente, lo schema recuperato e esempi dallo Strumento di Prompting Few-Shot.

Supponiamo che tu sia qualcuno in grado di generare query SQL. Genera una query SQL per: Recuperare le vendite totali raggruppate per prodotto degli ultimi 7 giorni.

Tabelle rilevanti:

  1. sales_data: Contiene le colonne [vendite, data, product_id].
  2. products: Contiene le colonne [product_id, product_name].

Esempio SQL:

Plain Text

 

SELECT product_name, SUM(sales) FROM sales_data JOIN products ON sales_data.product_id = products.product_id GROUP BY product_name;

Perspettive Future

Pur utilizzando un singolo agente con più strumenti per semplificare la modularità e ridurre la complessità, in futuro potrebbe essere esplorato un framework multi-agente. Potremmo esplorare eventualmente quanto segue:

  1. Agenti dedicati per il recupero del contesto. Agenti separati per ricerche semantiche e per parole chiave.
  2. Agenti specifici per compiti. Agenti specializzati nella validazione o ottimizzazione SQL.
  3. Collaborazione tra agenti. Utilizzo di un agente di coordinamento per gestire la delega dei compiti.

Questo approccio potrebbe migliorare la scalabilità e consentire flussi di lavoro più sofisticati, specialmente nelle implementazioni di livello enterprise.

Conclusioni

Agentic RAG per le applicazioni testo-SQL offre un approccio scalabile e modulare per risolvere compiti di query strutturata. Incorporando la ricerca ibrida, il re-ranking, l’interrogazione few-shot e la costruzione dinamica delle richieste all’interno di un framework a singolo agente, questo sistema garantisce precisione, trasparenza ed estensibilità. Questo flusso di lavoro potenziato dimostra un potente modello per trasformare domande in linguaggio naturale in query SQL azionabili.

Source:
https://dzone.com/articles/creating-an-agentic-rag-for-text-to-sql-applications