Velkommen til andre del i bloggserien om å chatte med kode24-timen. Her skal vi dykke litt dypere i det tekniske og jeg skal fortelle deg hvordan den er laget. Konseptet er ikke nytt, men det som derimot er nytt er at vi nå kan få til dette ganske så bra også norsk takket være Nasjonalbibliotekets nye tale til tekst modell NBWhisper.
Chat med kode24-podcasten her: https://kode24chat.novacare.no/
Hente og transkribere podcasten
Finne og laste ned mp3-filene
Dataene her er jo mp3-filene fra podcasten. Disse finner man enkelt, sammen med lenke, tittel og beskrivelse, i rss-feeden hos Acast. Jeg skrev et lite python script for å laste ned mp3-filene og strukturere titler og sammendrag.
Transkribering med NBWhisper
Jeg har naturligvis ikke transkribert alle episodene for hånd. Til det har jeg brukt NBWhisper-large fra Nasjonalbiblioteket som pr nå er den desidert beste tale til tekst-modellen på norsk. Den er har en fin lisens og du kan også bruke den kommersielt om du ønsker det. Tale til tekst har jo aldri vært særlig bra på norsk om man ikke snakker meget pent bokmål med tydelig uttale og perfekt lydkvalitet, men NBWhisper tar dette til et nytt nivå og gjør en ganske så god jobb.
Men selv om den er god, så er den ikke perfekt, og den er nok ikke trent på så mye data som ligner sjargongen og termene som brukes i kode24-timen. Navn og stavemåter er selvfølgelig også vanskelig. Vår egen Tomas får for eksempel navnet sitt transkribert som Thomas. Så hvis vi da spør "Er Tomas Gulla nevnt i noen episoder?" så vil den ikke nødvendigvis finne riktig svar. I tillegg er det jo ikke alltid Jørgen og Ole Petter snakker med perfekt diksjon så den stakkars modellen får kjørt seg.
Eksempler på vanskelige transkripsjoner som ikke nødvendigvis blir riktig:
- Norges Tivoli-laks, dette.
- Hvor tror du folk hører på College 4-timene?
- Vi skal snakke om Metas nye Cold Lama
Så er det situasjoner der man tror ting har blitt feil, men faktisk var riktig transkribert. Som da Novacare beskrives som et selskap som lager stellebord i 100 % resirkulerbart tremateriale.
Speaker diarization og forced alignment
Speaker diarization er egentlig bare et flottingord for å identifisere hvilke personer som snakker når i lydfilen og "tagge" forskjellige tidskoder med en id. who spoke when?
Forced alignment er egentlig bare et flottingord for å matche den transkriberte teksten med tidskoder slik at vi vet nøyaktig når hver setning blir sagt. NBWhisper kommer også med tidskoder, men etter forced aligment-prosesseringen blir presisjonen på disse enda mer nøyaktige.
Til disse stegene har jeg brukt python-rammeverket WhisperX som pakker inn diverse andre maskinlæringsmodeller og gjør dem veldig enkle i bruk. Enten som et kommandolinjeverktøy, eller som et rammeverk man kan ta i bruk i koden din.
Så når disse stegene er gjennomført har vi konvertert alle mp3-filene til tekst som er et format de store språkmodellene kan håndtere.
Retrieval Augmented Generation (RAG)
De store språkmodellene (LLM) er gode på språk og å skrive overbevisende tekst. De har også en hel del kunnskap, men de er prisgitt dataene de er trent på og vi kan egentlig ikke stole så mye på svarene de gir grunnet tendensen til å hallisunere. Store språkmodeller er trent på enorme mengder data, men en ting tror jeg vi kan slå fast og det er at de ikke er trent på innholdet i kode24-timen. Så hvordan kan man gi språkmodellene tilgang til innholdet så vi kan chatte over det? Enter RAG.
RAG står for Retrieval Augmented Generation og veldig enkelt forklart kan du tenke på det som en programmatisk måte å skrive prompten du skriver inn i ChatGPT på. Denne teknikken består av følgende steg:
Forberede vektordatabasen
Chunking
Man deler opp teksten, i vårt tilfelle podcast-transkripsjonene, i passe tekstbolker. Feks. i 300 karakterer. Målet med dette er å dele opp teksten i små nok biter til at de har en entydig og klar semantisk mening, samtidig som det er kontekst nok til å kunne svare på spørsmålet brukeren har.
Embedding og lagring
Embedding er en prosess der man bruker en maskinlæringsmodell til å konvertere tekst til en array, eller en vektor, med tall. For eksempel [-0.0342062488, -0.0200805124, 0.097511895, -0.00888398848, ...]
. Poenget her er at man da kan regne ut likheten, eller distansen, mellom tekst. Hvis man puttet alle ord i en graf, basert på embedding vektoren, så ville man for eksempel se at blomst og hvitveis ligger veldig nærme hverandre siden de er semantisk like. React native
ville derimot ligge et helt annet sted på grafen.
Jeg bruker OpenAI sin text-embedding-3-small
modell. Denne har 1536 dimensjoner, som vil si at lengden på en embedding/vektor/array er 1536.
For å søke i disse embeddingene så er det en ny type database som har eksplodert i popularitet de siste årene. Nemlig vektordatabasen. Disse er spesialiserte til å kunne lagre og raskt slå opp slike vektorer i en database. Jeg bruker Pinecone som er beskrevet litt mer lengre ned i posten.
Retrieval
Dette steget handler om å klare å hente ut den relevante konteksten fra vektordatabasen. Så hvordan gjør man det? Jo når du spør kode24-chatten om Hvorfor ga Jørgen seg selv latterkrampe?
så gjør vi en embedding på spørsmålet og ber vektordatabasen returnere de 5 tekst-chunkene som er semantisk mest like. Så enkelt og så vanskelig er det. Her er det et vell av avanserte og mindre avanserte teknikker for å få ut de mest relevante chunkene, men det er litt ut over scopet her.
Merk at dette steget ikke nødvendigvis må bruke embeddings mot en vektordatabase. Det kan for eksempel være et web-søk, slik som Vercel eller ChatGPT gjør, oppslag i en SQL-base, osv.
Augment
Husker du jeg skrev at RAG forenklet sett er en programmatisk måte å lage en prompt på? Vel det skjer her. Det er her man syr sammen teksten fra Retrieval-steget sammen med spørsmålet fra brukeren og ber LLMen svare på spørsmålet basert på informasjonen som ligger i konteksten.
Eksempel på en RAG-prompt-template:
Answer the user's question based on the context.
CONTEXT
{context}
QUESTION
{question}
Eksempelet over er veldig simpelt, men det er også her du bestemmer hvordan du vil at LLMen skal svare. kode24-time-chatten har for eksempel blitt instruert til å svare i samme stil som kode24-timen.
Generate
Dette er helt enkelt å sende prompten du laget i Augment-steget til en LLM for å få generert et svar. kode24-timen-chatten bruker OpenAI sitt api og GPT-4-turbo til å generere svaret. GPT-35-turbo er mye billigere, men GPT-4 var mye bedre til å fange stilen og sjargongen til Ole Petter og Jørgen, så derfor er det GPT-4 som er brukt.
Merk at dette beskriver en veldig enkel RAG-pipeline. Dette er et felt i voldsom utvikling og det kommer nye og avanserte teknikker hele tiden.
Rammeverk/verktøy
LangChain
Det finnes etterhvert en del rammeverk for å lage LLM-applikasjoner, men de to største er nok LangChain og LlamaIndex.
Langchain er den største og den jeg har brukt her.
OpenAI-api
Bruker gpt-4-turbo
til å generere svaret til brukeren og text-embedding-3-small
for å generere embeddings.
Prøvde først med Azure OpenAI som har fungert bra tidligere, men nå var det plutselig blitt veldig tregt og "hakkete". Mest sannsynlig på grunn av Content Filter som gjør at responsen blir tregere og har større batch-sizes på streamingen tilbake til klienten. I tillegg ble en del innhold filtrert bort på grunn av seksuelt innhold 🙈
Pinecone vektordatabase
Pinecone er en fully hosted vektor-database som er veldig enkel i bruk. De har en gratis index på opp til 100 000 dokumenter for å teste og komme i gang.
Streamlit
GUIet er laget med Streamlit - et python-bibliotek for å veldig raskt lage/prototype web-apper.
Konklusjon
Jeg skjønner at noen kanskje begynner å få litt AI-fatigue etterhvert, for det har vært ganske intenst en stund nå. Men jeg håper dette kan være et praktisk eksempel som drar den svevende AI-hypen ned til det konkrete. Det jeg også håper du sitter igjen med, er at mulighetene blir større og større også innenfor det norske domenet, og det synes i alle fall jeg er veldig gøy. For det er vel hva vi kan bruke teknologien til som faktisk gjør den spennende?