Ga naar hoofdinhoud

Contextbewuste chatbots bouwen: het LangChain-framework benutten voor ChatGPT

Ontdek hoe je contextbewuste chatbots bouwt met het ChatGPT- en LangChain-framework.
· 15 min lezen

Verken met AI

In het snelle digitale landschap van vandaag, met de opkomst van Large Language Models (LLM’s), zijn conversatie-applicaties enorm populair geworden. Chatbots hebben de manier waarop we in het dagelijks leven met apps, websites en zelfs klantenservicekanalen omgaan, veranderd.

De mogelijkheid om je eigen chatbot te bouwen is een gamechanger: het stelt bedrijven en individuen in staat om gepersonaliseerde, efficiënte en boeiende conversatie-interfaces te creëren die zijn afgestemd op hun specifieke behoeften.

Dit artikel laat zien hoe je een chatbot in Python bouwt met de kracht van LLM’s. We verkennen hoe we onze eigen chatbot met ChatGPT maken en hoe we die verder optimaliseren met het populaire LangChain-framework.

Ben je nieuw met ChatGPT en LangChain? Bekijk dan hieronder onze Introductie tot Large Language Models met GPT & LangChain AI code-along: 

Dit artikel behandelt alle cruciale aspecten van de ontwikkeling van een geoptimaliseerde chatbot die onder de motorkap ChatGPT gebruikt. We werken in een Jupyter Notebook met de libraries openai en langchain geïnstalleerd. Je kunt meekijken in deze DataLab-workbook.

In de volgende secties bekijken we hoe je de OpenAI API gebruikt om basis-API-calls naar ChatGPT te doen als bouwstenen voor onze conversatie. Daarnaast implementeren we contextbewustzijn voor onze chatbot met eenvoudige Python-structuren. Tot slot optimaliseren we zowel de chatbotimplementatie als de gespreksgeschiedenis met LangChain en verkennen we de geheugenfeatures.

Basis-API-calls naar ChatGPT

We gebruiken ChatGPT als het model achter onze chatbot. Daarom bestaat onze eerste hands-on taak uit het schrijven van een eenvoudige maar complete API-call naar ChatGPT, zodat we een eerste interactie kunnen opzetten.

De volgende Python-functie bevat onze gewenste API-call naar ChatGPT met alleen de openai-library.

import os
import openai

openai_api_key = os.environ["OPENAI_API_KEY"]

def chatgpt_call(prompt, model="gpt-3.5-turbo"):
   response = openai.ChatCompletion.create(
       model=model,
       messages=[{"role": "user", "content": prompt}]
   )
   return response.choices[0].message["content"]

Merk op dat we in dit geval het gpt-3.5-turbo-model gebruiken, omdat dit het meest capabele GPT-3.5-model is en geoptimaliseerd is voor chat, en bovendien een van de goedkoopste modellen. Meer informatie over de verschillende beschikbare modellen vind je in de OpenAI-documentatie.

Hulp nodig bij het verkrijgen van je OpenAI API-sleutels? Dan is de training Getting Started with the OpenAI API and ChatGPT iets voor jou.

Laten we onze implementatie stap voor stap analyseren!

De functie chatgpt_call() is een wrapper rond de oorspronkelijke OpenAI-functiecall ChatCompletion.create die een chatcompletion-verzoek aanmaakt.

Gegeven de gewenste prompt retourneert de functie de bijbehorende ChatGPT-completion. Voor een geslaagde completion-call hoef je alleen de ID van het te gebruiken model (model) en de gewenste prompt (prompt) mee te geven.

De daadwerkelijke completion die ChatGPT terugstuurt, bevat metadata samen met het antwoord zelf. De completion zelf staat echter altijd in het veld response.choices[0].message[“content”]. Meer informatie over de volledige completion-inhoud vind je in de OpenAI-documentatie.

Met deze implementatie kunnen we ChatGPT in één regel prompten:

prompt = "My name is Andrea"
response = chatgpt_call(prompt)
print(response)

# Output: Nice to meet you, Andrea! How can I assist you today?

De call als op zichzelf staande interactie

Zie je een addertje onder het gras in de bovenstaande implementatie?

Om je een hint te geven, doen we een vervolginteractie:

prompt = "Do you remember my name?"
response = chatgpt_call(prompt)
print(response)

# Output: I'm sorry, as an AI language model, I don't have the ability to remember specific information about individual users.

Precies, zoals we zien, is elke API-call een op zichzelf staande interactie. Dit betekent dat het model geen geheugen heeft van eerdere inputprompts of gegeven completions. Telkens wanneer we de functie chatgpt_call() aanroepen, beginnen we een compleet nieuw gesprek.

Dat is natuurlijk een probleem als je een chatbot wilt bouwen, want het is onmogelijk om een gesprek te voeren als het model niet onthoudt waar we het over hebben gehad. Daarom is onze volgende stap om het model bewust te maken van onze eerdere interacties.

Contextbewustzijn

Contextbewustzijn stelt chatbots in staat om op basis van een lopend gesprek te begrijpen en passend te reageren.

Door een context op te bouwen, kunnen toekomstige interacties worden gepersonaliseerd op basis van eerdere, blijft de samenhang in het gesprek behouden en worden herhalingen vermeden. Bovendien kan de gebruiker met contextbewuste chatbots proactief verduidelijkende vragen stellen of suggesties doen om misverstanden recht te zetten.

Al met al verhoogt contextbewustzijn de natuurlijkheid, effectiviteit en gebruikerservaring van chatbots. Vanaf nu verwijzen we naar de verzameling van eerdere interacties als gespreksgeschiedenis of geheugen.

De originele OpenAI API voor ChatGPT biedt al een manier om onze gespreksgeschiedenis samen met elke nieuwe prompt naar het model te sturen. Daarnaast biedt het LangChain-framework ook een manier om het geheugen van ChatGPT effectiever en kostengeoptimaliseerd te beheren.

System-, Assistant- en User-berichten begrijpen

Structuur van ChatGPT’s gespreksgeschiedenis

Op dit punt kunnen we de methode chatgpt_call() direct gebruiken door alleen de gewenste prompt mee te geven. Als we beter naar de implementatie kijken, zien we dat de inputprompt is opgemaakt als onderdeel van de messages-lijst. Deze lijst is bedoeld om de gespreksgeschiedenis bij te houden en moet daarom op een heel specifieke manier worden geformatteerd.

Laten we eens beter kijken!

Tot nu toe hebben we de berichtenlijst in chatgpt_call() als volgt opgemaakt:

messages = [{"role": "user", "content": prompt}]

De messages-lijst slaat de gespreksgeschiedenis op als een verzameling dictionaries. Elke dictionary in de lijst staat voor een bericht in het gesprek en gebruikt twee key-valueparen.

  • De sleutel role met de waarde user geeft aan dat het bericht door de gebruiker is verstuurd.
  • De sleutel content met de waarde prompt bevat de daadwerkelijke gebruikersprompt.

Dit is een heel eenvoudig voorbeeld dat slechts één interactie bevat. Hoewel de sleutel content altijd ofwel de gebruikersprompt of de completion van de chatbot bevat, is de sleutel role veel veelzijdiger.

System-, Assistant- en User-rollen

De sleutel role geeft de eigenaar van het bericht in content aan. Bij ChatGPT kunnen we tot drie rollen hebben:

  • User. Zoals we al bespraken, komt de rol user overeen met de daadwerkelijke gebruiker die met de chatbot interageert.
  • Assistant. De rol assistant komt overeen met de chatbot.
  • System. De rol system wordt gebruikt om instructies, richtlijnen en context aan de chatbot te geven. Systemberichten helpen gebruikersinteracties te sturen, het gedrag van de chatbot te bepalen, contextuele informatie te bieden en specifieke interacties in het gesprek af te handelen. Je kunt systemberichten zien als hoog-niveau-instructies voor de chatbot, transparant voor de gebruiker.

Zelfgemaakte afbeelding. Interactie van de verschillende rollen in de conversatieketen bij het bouwen van een chatbot met chatGPT.

ZELFGEMAAKTE afbeelding. Interactie van de verschillende rollen in de conversatieketen bij het bouwen van een chatbot met chatGPT.

Een gespreksgeschiedenis opbouwen

We kunnen onze gespreksgeschiedenis opbouwen met gebruik van de drie rollen user, assistant en system in onze messages-lijst.

Gegeven onze testinteracties zou een correct geformatteerde gespreksgeschiedenis met gebruik van de drie genoemde rollen er als volgt uit kunnen zien:

messages =  [ 
{'role':'system', 'content':'You are friendly chatbot.'},
{'role':'user', 'content':'Hi, my name is Andrea'},
{'role':'assistant', 'content': "Nice to meet you, Andrea! How can I assist you today?"},
{'role':'user', 'content':'Do you remember my name?'}  ]

We kunnen onze functie call_chatgpt() licht aanpassen om de gespreksgeschiedenis als invoerparameter op te nemen:

def call_chatgpt_with_memory(messages, model="gpt-3.5-turbo"):
   response = openai.ChatCompletion.create(
       model=model,
       messages=messages,
   )
   return response.choices[0].message["content"]

Laten we deze nieuwe implementatie proberen!

messages =  [ 
{'role':'system', 'content':'You are friendly chatbot.'},
{'role':'user', 'content':'Hi, my name is Andrea'},
{'role':'assistant', 'content': "Nice to meet you, Andrea! How can I assist you today?"},
{'role':'user', 'content':'Do you remember my name?'}  ]

response = call_chatgpt_with_memory(messages)
print(response)

# Output: Yes, your name is Andrea.

Mooi! We hebben het model bewust kunnen houden van onze eerdere interacties zodat we daadwerkelijk een gesprek kunnen voeren. We moeten echter nog een manier vinden om onze messages-lijst automatisch bij te werken.

Automatische gespreksgeschiedenis

We kunnen het automatisch verzamelen van berichten bereiken door onze API-callfunctie iets verder uit te werken:

def chatgpt_conversation(prompt):
   context.append({'role':'user', 'content':f"{prompt}"})
   response = call_chatgpt_with_memory(context)
   context.append({'role':'assistant',
   'content':f"{response}"})
   return response

Proberen maar!

chatgpt_conversation("Hello, my name is Andrea")

# Output: 'Nice to meet you, Andrea! How can I assist you today?'

En een vervolginteractie:

chatgpt_conversation("Do you remember my name?")

# Output: 'Yes, your name is Andrea.'

Nu wordt de gespreksgeschiedenis automatisch bijgewerkt. Tot slot: je kunt de gespreksgeschiedenis — en de structuur ervan — op elk moment bekijken door de lijst messages te printen.

print(messages)

# Output:
[{'role': 'user', 'content': 'Hello, my name is Andrea'},
{'role': 'assistant', 'content': 'Nice to meet you, Andrea! How can I assist you today?'},
{'role': 'user', 'content': 'Do you remember my name?'},
{'role': 'assistant', 'content': 'Yes, your name is Andrea.'}]

Het LangChain-framework

Tot nu toe zullen we het erover eens zijn dat onze implementatie leidt tot een bruikbare chatbot, maar waarschijnlijk niet de meest geoptimaliseerde versie.

Het continu meesturen van elk eerder bericht en elke completion kan snel leiden tot een enorme hoeveelheid tokens, die ChatGPT allemaal moet verwerken voordat de nieuwste completion wordt teruggestuurd.

Tot nu toe hebben we .append-operaties gebruikt om eerdere interacties te bundelen zodat de chatbot een soort geheugen heeft. Het geheugenbeheer kan echter effectiever worden gedaan met speciale pakketten zoals LangChain.

LangChain is een framework voor het ontwikkelen van applicaties die worden aangedreven door taalmodellen. Omdat chatbots tegenwoordig een van de populairste toepassingen zijn, biedt LangChain een schonere implementatie om de gespreksgeschiedenis van de chatbot op te bouwen en alternatieven om die efficiënter te behandelen.

Voor meer informatie over het LangChain-framework zelf raad ik het introductieartikel Introduction to LangChain for Data Engineering & Data Applications echt aan.

Eenvoudige gespreksgeschiedenis met LangChain

In deze sectie verkennen we hoe je in plaats daarvan het LangChain-framework gebruikt om een chatbot te bouwen.

De langchain-library fungeert als vervanging voor de hierboven gebruikte openai-library, omdat de module langchain.llms je al in staat stelt om in twee regels code met ChatGPT te communiceren:

# OpenAI key from environment
from langchain.llms import OpenAI
llm = OpenAI()

Zodra het llm-model is geladen, importeren we het memory-object dat onze gespreksgeschiedenis zal opslaan. Voor het opslaan van elke eerdere interactie met de chatbot biedt LangChain de zogenaamde ConversationBufferMemory:

from langchain.memory import ConversationBufferMemory
memory = ConversationBufferMemory()

Nu is het tijd om de zogenaamde ConversationChain te importeren, een wrapper die llm en memory gebruikt om de gebruikersprompt aan ChatGPT te voeren en de completions terug te geven:

from langchain.chains import ConversationChain

conversation = ConversationChain(
   llm=llm,
   memory=memory
)

Tot slot kunnen we om een gebruikersprompt naar ChatGPT te sturen, de methode .predict als volgt gebruiken:

conversation.predict(input="Hello, my name is Andrea")

# Output: "Hi Andrea, I'm an AI created to help you with whatever you may need. What can I do for you today?"

Laten we verbose=True instellen om te zien welke informatie het model in overweging neemt om een completion te geven. Laten we het proberen in een vervolginteractie:

Schermafbeelding van de context die beschikbaar is voor ChatGPT bij het maken van een completion. Verbose-uitvoer in groen.

Schermafbeelding van de context die beschikbaar is voor ChatGPT bij het maken van een completion. Verbose-uitvoer in groen.

Controleer tot slot dat eerdere interacties perfect worden onthouden:

Schermafbeelding van de context die beschikbaar is voor ChatGPT bij het maken van een completion. Verbose-uitvoer in groen.

Schermafbeelding van de context die beschikbaar is voor ChatGPT bij het maken van een completion. Verbose-uitvoer in groen.

De gespreksgeschiedenis vullen

We kunnen extra context aan de chatbot meegeven met de methode memory.save_context(). Zo kunnen we onze chatbot bewust maken van gewenste inhoud zonder deze informatie in een gesprek te hoeven aanreiken. In LangChain zijn de sleutelwoorden hiervoor input en output. Bijvoorbeeld,

memory.save_context({"input": "Hi"},
                   {"output": "What's up"})
memory.save_context({"input": "Not much, just hanging"},
                   {"output": "Cool"})

Daarnaast kunnen we de geheugeninhoud op elk moment raadplegen door print(memory.buffer) of memory.load_memory_variables({}) uit te voeren.

LangChain-geheugentypen

LangChain implementeert verschillende soorten geheugen voor ChatGPT. De hierboven gebruikte ConversationBufferMemory houdt elke afzonderlijke interactie met ChatGPT bij. We hebben echter al besproken dat het prompten van ChatGPT met de volledige gespreksgeschiedenis snel kan oplopen tot een groot aantal tokens die bij elke nieuwe interactie verwerkt moeten worden.

Houd er rekening mee dat ChatGPT naast een tokenlimiet per interactie ook kosten rekent op basis van het aantal tokens. Het telkens verwerken van de volledige gespreksgeschiedenis zal na verloop van tijd waarschijnlijk duur worden.

Om het continue verwerken van de gespreksgeschiedenis te voorkomen, implementeert LangChain andere geoptimaliseerde manieren om het geheugen van ChatGPT te beheren. We verkennen in de volgende sectie een van de meest geavanceerde typen.

Geavanceerde gespreksgeschiedenis met LangChain

Een geavanceerde manier om het geheugen te beheren zonder elke eerdere interactie op te slaan, is door in plaats daarvan een samenvatting van de interacties op te slaan.

Dit type geheugen is mijn favoriet, omdat het echt kan leiden tot geoptimaliseerd geheugenbeheer zonder ook maar één eerdere interactie te verliezen.

Gaan we doen!

# OpenAI key from environment
from langchain.llms import OpenAI
llm = OpenAI()

from langchain.memory import ConversationSummaryBufferMemory
memory = ConversationSummaryBufferMemory(llm=llm, max_token_limit=100)

Let er wel op dat dit geheugentype het gedefinieerde llm gebruikt om de samenvatting van eerdere interacties te genereren.

Daarnaast kan dit geheugen volledige interacties opslaan tot een maximum aantal tokens (max_token_limit). Boven deze limiet verwerkt het de informatie van die berichten in de samenvatting.

Om het te testen maken we een interactie met een groot aantal tokens. Neem de volgende schedule:

schedule = "There is a meeting at 8am with your product team. \
You will need your powerpoint presentation prepared. \
9am-12pm have time to work on your LangChain \
project which will go quickly because Langchain is such a powerful tool. \
At Noon, lunch at the italian resturant with a customer who is driving \
from over an hour away to meet you to understand the latest in AI. \
Be sure to bring your laptop to show the latest LLM demo."

memory = ConversationSummaryBufferMemory(llm=llm, max_token_limit=100)
memory.save_context({"input": "Hello"}, {"output": "What's up"})
memory.save_context({"input": "Not much, just hanging"}, {"output": "Cool"})
memory.save_context({"input": "What is on the schedule today?"}, {"output": f"{schedule}"})

Nu hoeven we alleen nog een nieuwe conversatieketen te declareren met het llm en het memory, zoals in de vorige voorbeelden.

conversation = ConversationChain(
   llm=llm,
   memory=memory,
   verbose=True
)

We kunnen .predict gebruiken om ChatGPT te prompten:

Schermafbeelding van de context die beschikbaar is voor ChatGPT bij het maken van een completion. Verbose-uitvoer in groen.

Schermafbeelding van de context die beschikbaar is voor ChatGPT bij het maken van een completion. Verbose-uitvoer in groen.

Zoals we zien, houdt de chatbot een samenvatting bij van de oorspronkelijke gespreksgeschiedenis, waardoor het aantal tokens dat ChatGPT bij elke vervolginteractie verwerkt, afneemt.

Dat was het! Nu kunnen we urenlang met onze geoptimaliseerde chatbot praten!

Zet verbose=False voor een prettigere vraag-en-antwoord-ervaring in je notebook.

Conclusie

In dit artikel hebben we gezien hoe je een contextbewuste chatbot bouwt met het bekende ChatGPT-model.

We hebben gezien hoe het bouwen van je eigen contextbewuste chatbot kan leiden tot doorlopende interacties. Deze iteratieve aanpak zorgt voor een constante feedbacklus, waardoor je behoeften en voorkeuren van gebruikers gaandeweg in het gesprek kunt meenemen.

We hebben twee hoofdbenaderingen gezien om de gespreksgeschiedenis van de chatbot te beheren. Ten eerste met de originele ChatGPT API, wat leidt tot een functionele, native chatbot. Ten tweede met het speciale LangChain-framework en een geoptimaliseerde geheugenimplementatie.

Nu is het jouw beurt! Het is tijd om met de bouwstenen uit dit artikel een chatbot te maken die is afgestemd op jouw specifieke usecase!

Ben je geïnteresseerd in het bouwen van andere applicaties met zowel LangChain als ChatGPT, bekijk dan het webinar Building AI Applications with LangChain and GPT. Wil je snel up-to-speed komen met NLP? Bekijk dan de modules over Natural Language Processing in Python!

Andrea Valenzuela

Auteur

Andrea Valenzuela

Andrea Valenzuela werkt momenteel aan het CMS-experiment bij de deeltjesversneller (CERN) in Genève, Zwitserland. Met zes jaar ervaring in data-engineering en -analyse omvatten haar taken data-analyse en softwareontwikkeling. Ze zet zich nu in voor het democratiseren van het leren van datagerelateerde technologieën via de Medium-publicatie ForCode'Sake.

Ze behaalde een bachelor in Engineering Physics aan de Polytechnische Universiteit van Catalonië en een master in Intelligent Interactive Systems aan de Pompeu Fabra Universiteit. Haar onderzoekservaring omvat professioneel werk met eerdere OpenAI-algoritmes voor beeldgeneratie, zoals Normalizing Flows.

Onderwerpen

Gerelateerd