Als BI specialisten hadden wij de volgende droom:
“Wat als we met een chatbot met ons datawarehouse zouden kunnen praten…?”
Bijvoorbeeld:
Human : “Hoeveel nieuwe klanten zijn er afgelopen maand bij gekomen?”
AI : “De vorige maand zijn er 1234 nieuwe klanten bijgekomen.”
Human : “Zijn dat er meer dan vorig jaar rond deze tijd?”
AI : “Ten opzichte van vorig jaar is dat een stijging van 7%”
Dus, gingen we hiermee aan de slag.
Joep, Hugo, Marcel en Marcel-Jan praten in onze podcast over chatten met je database. Klik hier om hem te beluisteren op Spotify.
We hebben een database voorbeeld genomen van Kaggle Pizza Place en gekoppeld aan een Large Language Model (LLM) van OpenAI.
Entity Relationship Diagram van Kaggle Pizza Place:
Het ERD is niet helemaal een netjes gemodelleerde data mart, maar dat is voor deze test wellicht een interessant gegeven.
Het blijkt dat Large Language Models zoals gebruikt in ChatGPT met de juiste tools zelf op zoek kunnen gaan welke data (database tabellen) beschikbaar zijn in een database. Ook heeft de chatbot begrip van die tabellen en hoe ze gekoppeld zijn. Een Large Language Model begrijpt wat je bedoelt als je vraagt “bereken de omzet” of “is dit resultaat beter dan vorig jaar?”. De bot vertaalt deze uitdrukkingen in valide SQL op het onderliggende datamodel. We zien ook interessante dingen op het gebied van metadata en master data, datadefinities lijken de chatbot te ontgaan, de bot puzzelt net zo lang totdat er een valide SQL query uitrolt maar stoort zich verder niet aan mogelijke datadefinities, die natuurlijk niet expliciet in het datamodel zijn opgenomen.
Zo hadden we de volgende “conversatie” met onze pizza place database en waren we aangenaam verrast met de resultaten.
Human : “Show me the total revenue in Q2 2015.”
AI: “The total revenue in Q2 2015 was $ 208369.75.”
We hebben al deze resultaten natuurlijk gecontroleerd met SQL queries die we wel zelf geschreven hebben. Want we willen zeker weten dat het Large Language Model dit niet ”hallucineert”.
Onze conclusie is dat de resultaten kloppen, ook als het datamodel niet “all over” het internet zwerft (zoals de Chinook database) . Dit betekent dat de Large Language Model daadwerkelijk SQL als taal snapt, net zoals Github CoPilot Python begrijpt. En we hebben sterke aanknopingspunten dat ook SQL als taal bruikbaar is binnen een Large Language Model chatbot zoals ChatGPT. De echte kracht zit natuurlijk in de combinatie van SQL en natuurlijke taal, hier zullen we nog een paar mooie voorbeelden van zien!
Human : “Show revenue for pizzas with ingredient pineapple in March 2015.”
AI: “The revenue for pizzas with ingredient pineapple in March 2015 is 6609.25.”
SQL generated:
SELECT SUM(price) AS revenue FROM orders o
INNER JOIN order_details od ON o.order_id = od.order_id
INNER JOIN pizzas p ON od.pizza_id = p.pizza_id
INNER JOIN pizza_types pt ON p.pizza_type_id = pt.pizza_type_id
WHERE strftime('%m', o.date) = '03'
AND strftime('%Y', o.date) = '2015'
AND pt.ingredients LIKE '%pineapple%';
SQLResult: [(6609.25,)]
Human: “Show the top 5 months of sales in 2015.”
(Hier zie je een mooi voorbeeld van een definitie kwestie : “sales” is niet gedefinieerd, wordt hier aantal of omzet bedoeld … ?)
AI: “The top 5 months of sales in 2015 were July, May, November, March, and January.”
(De chatbot vertaalt het maandnummer naar de maandnaam, en bedenkt zelf wat je met “sales” zou kunnen bedoelen …)
SQL generated:
SELECT strftime('%m', date) AS month, SUM(quantity) AS total_sales
FROM orders
INNER JOIN order_details
ON orders.order_id = order_details.order_id
WHERE strftime(‘%Y’, date) = ‘2015’
GROUP BY month
ORDER BY total_sales DESC
LIMIT 5;
Human : “Show the revenue per month in 2015 for category veggie.”
AI: ‘The revenue per month in 2015 for category veggie was 16679.19 in January, 15349.94 in February, 17040.14 in March, 15521.99 in April, 16622.39 in May, 15683.39 in June, 17293.64 in July, 15801.19 in August, 14808.94 in September, 14220.14 in October, 15948.84 in November, and 15228.19 in December.’
SQL generated:
SELECT strftime('%m', date) AS month, SUM(price) AS revenue
FROM orders
INNER JOIN order_details ON orders.order_id = order_details.order_id
INNER JOIN pizzas ON order_details.pizza_id = pizzas.pizza_id
INNER JOIN pizza_types ON pizzas.pizza_type_id = pizza_types.pizza_type_id
WHERE strftime('%Y', date) = '2015' AND category = 'Veggie'
GROUP BY month
ORDER BY month ASC;
SQLResult: [('01', 16679.19), ('02', 15349.94), ('03', 17040.14), ('04', 15521.99), ('05', 16622.39), ('06', 15683.39), ('07', 17293.64), ('08', 15801.19), ('09', 14808.94), ('10', 14220.14), ('11', 15948.84), ('12', 15228.19)]
Eerst even een paar concepten uitleggen die handig zijn om te begrijpen wat we nu gemaakt hebben, geheel in conversatie stijl.
Human : “What is an agent?“
AI : “An agent, in the context of programming, usually refers to a software program or component that acts on behalf of another program or user to perform specific tasks or provide services. It is authorized to act on behalf of another entity, known as the principal. The agent may have decision-making authority and is responsible for carrying out actions and achieving results for the principal. ”
De LangChain SQL agent antwoordt aan de hand van het ReAct framework (paper).
Hoewel grote taalmodellen (Large Language Models) indrukwekkende capaciteiten hebben laten zien in taken op het gebied van taalbegrip en interactieve besluitvorming, zijn hun capaciteiten voor redeneren (bijv. chain-of-thought prompting) en handelen (bijvoorbeeld het genereren van actieplannen) voornamelijk bestudeerd als afzonderlijke onderwerpen.
Een Reasoning Action (ReAct) prompt bestaat uit een taakoplosschema: op basis van door mensen geschreven tekst genereert de agent een keten van “gedachten” en acties, evenals omgevingsobservaties als reactie op acties.
ReAct prompting is intuïtief en flexibel te ontwerpen, en bereikt state-of-the-art prestaties in een verscheidenheid aan taken, van het beantwoorden van vragen tot online winkelen!
Human: Wat is een large language model (LLM)?
AI : “Een groot taalmodel is een geautomatiseerd taalmodel dat bestaat uit een kunstmatig neuraal netwerk met veel parameters, getraind op grote hoeveelheden ongelabelde tekst met behulp van self-supervised leren of semi-supervised leren. Sinds ongeveer 2018 zijn LLM’s uitgegroeid tot een krachtig hulpmiddel in het onderzoek naar de verwerking van natuurlijke taal. Het zijn modellen voor algemeen gebruik die uitblinken in een breed scala aan taken, in tegenstelling tot modellen die getraind zijn voor één specifieke taak zoals sentimentanalyse of named entity recognition.”
We nemen hieronder een voorbeeld door. De gebruiker stelt een simpele vraag en we kijken als het ware mee met de interne gedachten van de agent die het large language model aanstuurt. In de rechterkolom vroegen we ons eigen commentaar toe waar er interessante dingen gebeuren. De agent heeft de beschikking over de SQL toolkit en gebruikt deze om SQL te genereren die uitgevoerd wordt tegen de Pizza database. De resultaten zijn dus niet “verzonnen” door het LLM maar zijn daadwerkelijk resultaten uit werkende queries op de database.
De interne monoloog van LangChain met het LLM van OpenAI (in dit geval ChatGPT 3.5 Turbo)
Human: “Show me the total revenue in Q2 2015.”
AI interne Reason – Action logica | Onze “observaties” |
Action: list_tables_sql_db Observation: order_details, pizzas, pizza_types, orders | Het eerste wat de Agent doet is de lijst van beschikbare tabellen uitvragen. |
Thought: I should query the schema of the orders table to see what columns I can use. | En trekt de conclusie dat ‘orders’ waarschijnlijk de relevante data bevat. |
Action: schema_sql_db Action Input: “orders” | Daarna vraagt de agent de structuur van de orders tabel op. |
Observation: CREATE TABLE orders ( order_id INTEGER, date TEXT, time TEXT ) /* 3 rows from orders table: order_id date time 1 2015-01-01 11:38:36 2 2015-01-01 11:57:40 3 2015-01-01 12:12:28 */ | |
Thought: I should query the order_details table to get the total revenue for Q2 2015. | De agent gaat ervan uit dat revenue in de orders tabel zit. |
Action: query_sql_db Action Input: SELECT SUM(price) FROM order_details WHERE date BETWEEN ‘2015-04-01’ AND ‘2015-06-30’; Observation: Error: (sqlite3.OperationalError) no such column: price | En genereert een SQL query met daarin de kolomnaam ‘price’. Price komt wel voor in het schema (in de tabel Pizza) maar niet in de orders tabel… dit gaat dus fout… |
Thought: I should query the schema of the order_details table to see what columns I can use. | Daarna bedenkt de bot dat de revenue data misschien in de details tabel zou kunnen zitten. |
Action: schema_sql_db Action Input: “order_details” Observation: CREATE TABLE order_details ( order_details_id INTEGER, order_id INTEGER, pizza_id INTEGER, quantity INTEGER )
/* 3 rows from order_details table: order_details_id order_id pizza_id quantity 1 1 hawaiian_m 1 2 2 classic_dlx_m 1 3 2 five_cheese_l 1 */ | |
Thought: I should query the order_details table to get the total revenue for Q2 2015. | Nu gaat hij een query bedenken om revenue uit order_details te halen |
Action: query_sql_db Action Input: SELECT SUM(quantity * price) FROM order_details INNER JOIN pizzas ON order_details.pizza_id = pizzas.pizza_id WHERE date BETWEEN ‘2015-04-01’ AND ‘2015-06-30’; Observation: Error: (sqlite3.OperationalError) no such column: date | Oeps, ‘date’ zat in orders en niet in order-details. Maar wat hier al wel goed gaat is de vertaling van Revenue naar quantity x price . |
Thought: I should query the schema of the orders table to see what columns I can use. Action: schema_sql_db Action Input: “orders” Observation: CREATE TABLE orders ( order_id INTEGER, date TEXT, time TEXT )
/* 3 rows from orders table: order_id date time 1 2015-01-01 11:38:36 2 2015-01-01 11:57:40 3 2015-01-01 12:12:28 */ | Nog maar een keer in de orders tabel kijken… |
Thought: I should query the order_details table to get the total revenue for Q2 2015. Action: query_sql_db Action Input: SELECT SUM(quantity * price) FROM order_details INNER JOIN pizzas ON order_details.pizza_id = pizzas.pizza_id INNER JOIN orders ON order_details.order_id = orders.order_id WHERE orders.date BETWEEN ‘2015-04-01’ AND ‘2015-06-30’; Observation: [(208369.75000000035,)] | Resultaat is een beetje genest, maar uiteindelijk komt er een werkende query uit , met het beoogde resultaat! |
Thought: I now know the final answer Final Answer: The total revenue in Q2 2015 was $208369.75. |
De voorzichtige conclusie is dat het kan! Een vraag stellen in natuurlijke taal die automagisch wordt vertaald in werkende SQL code, het resultaat daarvan wordt weer terug vertaald in natuurlijke taal.
Wat ook intrigerend is, is dat het zoekproces voor een werkende query best veel lijkt op het denkproces van een Human BI expert… De vertaling van omzet naar prijs x aantal bijvoorbeeld.
Dat is ook niet gek als je je bedenkt dat de eerste regel in de prompt die naar het LLM wordt gestuurd begint met:
Prompt : Doe alsof je een SQL expert bent….
Het is zo dat natuurlijke taal ambigu is. Het zal dus altijd zo zijn dat er ruimte blijft voor interpretatie, wanneer je een chatbot natuurlijke taal laat vertalen naar SQL, en weer terug.
Hoe zou jij bijvoorbeeld de volgende vraag oplossen:
“ Hoeveel vegetarische pizza’s zijn er?”
Er is in dit datamodel een categorie genaamd “Veggie” en er is een lijst met ingrediënten.
Onze agent bedacht een creatieve oplossing:
pizza.ingredients NOT LIKE ‘%chicken%, AND NOT LIKE %pork%, AND NOT LIKE %beef%’
We onderkennen dat de chatbot creatieve oplossingen verzint, maar dat doen wij mensen ook. Er lijkt vaak een data definitie (of het gebrek daaraan) ten grondslag te liggen aan de creatieve SQL die wordt gegenereerd, des te meer reden om aandacht voor datakwaliteit, naamgeving en leesbaarheid van datamodellen in databases voor te staan.
Vaak zijn die creatieve oplossingen het gevolg van het niet eenduidig stellen van de vraag, Dit is een kenmerk van natuurlijke taal, niet per se van een slecht ontworpen datamodel.
Het lijkt er op dat de SQL agent geen resultaten verzint, maar net zo lang probeert een werkende SQL query te vinden tot er een resultaat terug komt uit een werkende query. Het blijft de taak van de ‘human’ om het resultaat op waarde te schatten. De vraag of een dergelijke bot de werkdruk bij de BI-afdeling kan verlagen en de efficiëntie van self-service BI een boost kan geven zal de toekomst uitwijzen.
We zijn begonnen met het werken met de muziekdatabase ‘Chinook’. Hiervan bleken zoveel voorbeeldvragen op het internet te vinden die ook in het LLM naar boven kwamen dat we de resultaten op basis van dat datamodel niet vertrouwde. De Pizza database van Kaggle bleek veel minder bekend, en ook werd deze niet gevonden in de LLM van OpenAI. Het LLM van OpenAI bleek geen actieve herinneringen te hebben aan dit datamodel. Dus dit lijkt een bruikbare database voor de validatie van de onderzochte functionaliteit. Ook denken we dat als het datamodel een zuiver ‘ster-schema’ zou hebben, dat de database robuuster is voor complexere queries en de agent minder moeite zou hebben met het vinden van de juiste query.
Ervaringen met het fameuze “hallucineren” zijn we wel degelijk tegen gekomen, maar slechts zeer beperkt. De LangChain redeneer logica zorgt er in voldoende mate voor dat de interactie met de database daadwerkelijk de kern is van de interactie met de buitenwereld (de database). De agent verwerkt daarna die observaties (de resultaten uit de query) ook zonder meer in de juiste context. Het LLM verzint geen database uitkomsten (wat natuurlijk prima zou kunnen, en in onze experimenten met de Chinook database in een aantal gevallen ook daadwerkelijk is gebeurd) en rapporteert dus de exacte data uit de query. Datadefinities blijven een belangrijke rol spelen. Dat is natuurlijk niet verwonderlijk omdat SQL een meer formele taal is dan onze natuurlijke taal en zoals al eerder aangegeven de natuurlijke taal vaak voor meerdere uitleg vatbaar is. Het zal nog een interessante ontwikkeling worden om het datamodel zo af te stellen (schema en tabel en kolomnamen) zodat de agent het beste om kan gaan met de vertaling van natuurlijke taal naar SQL.
Privacy : We hebben in dit voorbeeld resultaten laten zien op basis van OpenAI GPT 3.5 turbo. We zijn druk bezig de resultaten te valideren op basis van open source varianten zoals beschikbaar via GPT4ALL, daarmee kunnen we op korte termijn een volledige ‘In-house’ oplossing aanbieden aan onze klanten.
Gaat dit ook werken op een serieus datawarehouse met een paar honderd tabellen? We denken dat het wellicht in de toekomst zo gaat zijn dat de tabelnamen een belangrijkere rol gaan spelen in het “leesbaar” maken van het datamodel voor een chatbot en dat dit een positief effect heeft op de data kwaliteit voor zowel mens als machine.
In het volgende blog kijken we onder de motorkap en gaan we met werkende code aan de slag! We laten ook zien hoe je met een opensource LLM van GPT4all.org kunt gebruiken om het geheel inhouse te kunnen draaien.
Schrijf je hier in voor onze nieuwsbrief om als eerste op de hoogte te zijn.
Kun je niet wachten en wil je nu al een demo van de mogelijkheden van Chat4BI? Neem dan contact op met Hugo Koopmans.