✒️Door: @Jan Willem Altink
📅Datum: 11 maart 2024
🕜Leestijd: 11 minuten
Inleiding
RAG is een architectuur die ervoor zorgt dat de meest relevante en belangrijke data aan het taalmodel van een AI toepassing wordt toegevoegd wanneer deze taken uitvoert. Door het toevoegen van deze gegevens verbeteren de nauwkeurigheid en de prestaties van de toepassing. Je kunt, met behulp van een aantal fantastische library’s zoals LlamaIndex en Langchain een RAG applicatie opzetten met een paar regels code, maar tegelijkertijd vergt het op de juiste manier inzetten van RAG en alle beslissingen die daar bij komen kijken een stuk meer moeite. In deze blog probeer ik een simpel te begrijpen inleiding te geven, een stukje theorie die handig is om te snappen wanneer je van plan bent een AI toepassing te maken die gebruik maakt van externe kennisbronnen.
Beperkingen LLMs
Op dit moment schieten de toepassingen die gebouwd zijn met de taalmodellen ChatGPT van OpenAI, Gemini van Google, Claude van Anthropic of 1 van de vele open source modellen als paddestoelen uit de grond. Logisch, de modellen hebben met hun veelzijdige toepasbaarheid onze manier van werken nu al drastisch veranderd. Hoewel ze zeer krachtig zijn hebben ze ook een aantal nadelen:
- Te weinig specifieke domeinkennis - LLMs zijn getraind op gigantische datasets, maar bedrijfsspecifieke specialistische kennis ontbreekt vaak.
- Ze zijn statisch - “Out of the box” zijn modellen maar getraind met data die gaat tot een bepaalde datum. Alle informatie die we na die datum hebben verkregen, maakt geen onderdeel uit van de “kennis” van het LLM.
- Gebrekkige broninformatie - het kan best lastig zijn om vanuit een LLM te achterhalen wat de bron was van de specifieke informatie die het verstrekt. Erg onhandig, zeker gezien het feit dat een LLM chatbot nog regelmatig wat verzint.
Deze beperkingen zijn met name voor bedrijven met requirements die wat verder gaan dan de gemiddelde chatbot demo te serieus om de LLM zomaar te integreren in hun AI gedreven toepassingen.
Een voorbeeldje
Ik werk op dit moment voor Esdec, een bedrijf dat montagesystemen voor zonnepanelen ontwikkelt. Als ik een chatbot zou bouwen die, zonder context, het antwoord op bedrijfsspecifieke vragen geeft, zal dat niet de antwoorden geven die ik verwacht.
In het bovenstaande voorbeeld vraag ik ChatGPT naar het voordeel van Flatfix, Flatfix is in de context van Esdec een platdak systeem voor zonnepanelen montage. ChatGPT is echter getraind op een dataset die bestond uit gegevens waar Flatfix meestal een merk was voor repareren van banden.
Voor dit specifieke voorbeeld is daar waarschijnlijk nog wel iets op te verzinnen. Stel dat ik op de Esdec website een chatbot zou plaatsen die gedreven wordt door GPT3.5, dan zou ik het prompt kunnen tweaken om te zorgen dat ChatGPT antwoord geeft op een manier die beter past bij de situatie.
Echter: dit is niet altijd een oplossing. Elk scenario dat je op deze manier aan de context window van het LLM toevoegt kost tokens, en een LLM kan maar een bepaald aantal tokens verwerken zonder dat de kwaliteit van het antwoord minder wordt of zelfs helemaal niet meer te verwerken is voor het LLM. Hier meer informatie daarover, maar de moraal van het verhaal is dat de context window van het LLM op dit moment nog kostbaar is, en dat je die alleen wil vullen met de meest relevante stukjes informatie die nodig zijn om antwoord te geven op de vraag.
RAG - of Retrieval Augmented Generation - betekent dat je up-to-date of (gegeven de context) meer relevante gegevens uit een externe database haalt en deze gegevens beschikbaar maakt voor het LLM wanneer het je vraag beantwoort. Elke keer als het LLM een vraag moet beantwoorden voeg je precies die stukjes context toe die in relatie tot de vraag het meest relevant zijn.
Semantic Search
De vraag die dan natuurlijk in je opkomt is: hoe kan ik de relevantie van bepaalde context voor een bepaalde vraag snel en effectief bepalen? Om dat uit te leggen is het handig om de term “semantic search” toe te lichten.
Stel dat je een LLM wilt voeden met relevante context over de vraag: “hoe plaats ik het Flatfix Fusion systeem op mijn dak?” dan zou je misschien een systeem kunnen bedenken waarbij je door een database met al je bedrijfskennis loopt en probeert te zoeken op bepaalde trefwoorden, bijvoorbeeld “Flatfix Fusion” en “plaatsen”, maar daarbij loop je in de praktijk al snel tegen lastige puzzels aan. Want hoe weet je welke woorden uit een klantvraag je moet kiezen? en hoe ga je ermee om als iemand niet het woord “plaatsen” gebruikt maar “monteren”? En waarschijnlijk, als jij als bedrijf het product Flatfix verkoopt, heb je enorme aantallen documenten waarin dat woord voorkomt.
Met semantic search probeer je de “echte betekenis” van een bepaalde vraag of zin te achterhalen en op basis daarvan relevante resultaten te tonen, niet alleen op basis van exacte woord overeenkomsten.
Vector Embeddings
Om te begrijpen hoe je de echte betekenis van een document, afbeelding of video kunt representeren op een manier die voor een computer snel en effectief te doorzoeken en te vergelijken is, kun je je de situatie voorstellen waarbij je een nieuwe telefoon zoekt omdat je oude telefoon stuk is gegaan. Stel dat je een telefoon wilt die lijkt op je huidige telefoon en je hebt na wat research onderstaande tabel gemaakt:
Dan kies je als je iets zoekt dat lijkt op je huidige product waarschijnlijk voor product C.
Stel dat je ook wil dat je je telefoon redelijk snel hebt, dan zou je nog een derde variabele kunnen toevoegen, bijvoorbeeld “levertijd”. Bij deze ruimtelijke schematische weergave van producten geplot op basis van hun prijs, kwaliteit en levertijd kun je voor elk product een coordinaat bepalen en snel berekenen welke coordinaten het dichtst bij je huidige product liggen, dat zijn de producten waar je je in wilt verdiepen.
Bij een vector embedding doe je eigenlijk hetzelfde, alleen heb je dan geen ruimte met 3 dimensies, maar met een paar duizend dimensies. En elk coordinaat op elk van die dimensies is super precies gedefinieerd. Voor ons is zo’n multidimensionale ruimte visueel niet voor te stellen, maar voor een computer maakt het niet zoveel uit of de afstand berekend moet worden op basis van 3 of een paar duizend dimensies.
Embeddings modellen
Daarmee ben je er natuurlijk nog niet. Want hoe bepaal je de verschillende assen en de scores van een bepaald stukje data op elk van die assen? Gelukkig zijn er voor dat deel van de puzzel inmiddels Embeddings modellen. zij vertalen bepaalde data naar vector representaties van die data:
Bron: OpenAI
Elk model doet dat op een andere manier, dus je kunt een embeddings representatie van model A niet vergelijken met die van model B, maar als je hetzelfde model gebruikt kun je de inhoud van 2 door het model gevectoriseerde stukjes data vergelijken door de afstand tussen de coördinaten te bekijken. Hoe dichter ze bij elkaar staan, hoe meer ze op elkaar lijken.
Database
Dat gegeven kun je gebruiken door een vector database te maken van alle gegevens waarvan je wil dat je LLM die gebruikt bij het bepalen van een antwoord. als je vooraf door een model alle data laat vectoriseren, en vervolgens een binnenkomende klantvraag op dezelfde manier door hetzelfde model ook met vector embeddings weergeeft, kun je bijvoorbeeld de 10 meest relevante stukjes data (op basis van hun afstand van de klantvraag) als context aan een LLM toevoegen:
Hierboven zie je een simpele flow van hoe dit in zijn werk gaat. Op dit basis model zijn nog allerlei verbeteringen door te voeren, een veel voorkomende is bijvoorbeeld de inzet van een reranker. Maar voor deze introductie houden we het bij het basis model.
Niet altijd gebruiken
Tegelijkertijd is het goed om te begrijpen dat deze RAG manier van het bepalen van relevantie vooral handig is in bepaalde situaties.
Stel: je bent op zoek naar een nieuwe woning, en je hebt bedacht om een AI systeem te gebruiken om een geschikte woning te vinden, door het AI systeem in een database te laten kijken met gegevens over de woning. Velden in die database waar een uitleg wordt gegeven van de buurt, of waarin een (semantische) uitleg staat over de indeling van het huis, zouden heel geschikt zijn om via vector embeddings op cosine similarity te vergelijken. Het gaat bij dat type velden immers om de achterliggende betekenis, en de een kan die dingen heel anders omschrijven dan een ander dat zou doen. Tegelijkertijd zal die database ook dingen als “prijs” of “plaats” bevatten, en voor dat type velden is het veel logischer om de records met reguliere queries te filteren. Dat is ten eerste accurater, omdat je naar de daadwerkelijke gegevens kijkt, in plaats van hun vector representatie, en ten tweede simpeler, die hele stap van het vectoriseren van dat deel van de klantvraag is immers niet nodig.
Data structuur
Voor het opzetten van je eigen RAG applicatie is misschien wel de belangrijkste vraag die je moet beantwoorden de vraag over hoe je de brondata van je applicatie structureert. Die hoofdvraag kent verschillende subvragen. De eerste behandelden we net al even: welke data vectoriseer je en welke data niet? Een andere, misschien nog belangrijkere vraag is: welke brondata gebruik je uberhoubt? als de context daar niet in gevonden kan worden, kan de LLM ook geen kwalitatief hoogwaardige reaactie geven. Dan zijn er nog alerlei subvragen als hoe je de data opknipt, gezien de beperkte contextwindow van het LLM systeem kun je een PDF bestand van 40 pagina’s niet als 1 document vectoriseren. Tegelijkertijd moet de LLM wel de context van een sub stukje begrijpen, of bijvoorbeeld niet een waarschuwing over een specifiek zinnetje missen die een zin later stond maar net in een ander documentje is beland. Je moet ook nadenken over een helder overzicht en een index van data die onderdeel uitmaakt van je vector database. Het is handiger om te weten dat je die pdf van 45 pagina’s al opgeknipt en gevectoriseerd hebt, en zo ja welke versie, dan dat je dat moet achterhalen door individuele vector embeddings van text chunks met elkaar te vergelijken. Mocht je een nieuwe versie van die PDF hebben wil je dat snel en efficient in je Vector database aanpassen.
Volgende keer de praktijk..
En zo zijn er nog veel meer onderwerpen waar je over na kunt denken, bijv. de retrieval methode, de juiste grootte van het embeddingsmodel, efficiëntie en accuratie tegen elkaar afwegend, etc. Op al die keuzes wil ik in het vervolg op deze blog verder ingaan. Dan duiken we dieper in de praktische kant van RAG; ik zal een gedetailleert code voorbeeld geven waarbij we de volledige reis doorlopen- van het extraheren van gegevens uit PDF's tot het verfijnen van de chunking-methode en de structuur van de vector-database. We zullen elke stap doorlopen tot de uiteindelijke integratie in een LLM-chatbot, compleet met codevoorbeelden en inzichten die je zullen helpen om je eigen RAG-gedreven applicaties te ontwikkelen.
Heb je vragen of opmerkingen over deze blog laat vooral van je horen.
-JW