Följande artikel hjälper dig: ChatGP…Jag? Bygga en Telegram-bot-klon med OpenAI:s GPT – Mot AI
Ursprungligen publicerad på Mot AI.
Lär dig hur du använder Python, NodeJS och Google Firebase, tillsammans med OpenAI GPT API, för att bygga en Telegram-bot som lär sig hur du talar (men med ett mycket löst grepp om fakta)!
På senare tid har ChatGPT varit på topp. Jag har själv använt det regelbundet som en sorts superdriven personlig assistent.
Men trots sin skicklighet, för någon som är genomsyrad av WhatApp och Telegram skämt med vänner, låter ChatGPT ganska platt – precis som den (o)personliga assistenten det har blivit för mig. Två huvudorsaker:
- I en interaktion med ChatGPT turas båda parter om att skicka ett meddelande till den andre. Det här liknar mer e-post än att skicka meddelanden till en vän via Telegram, där meddelanden studsar runt fritt, utan någon speciell struktur.
- ChatGPT-skrivstilen är som standard den för en tuff lärares husdjur!
Skulle det inte vara bra om vi hade en version som var mer vardaglig, naturlig och rolig att prata med, som vi kunde chatta med på Telegram? Med det i åtanke satte jag mig för att försöka bygga en sådan grej/monster/digital tvilling, som chattar som jag.
Övergripande strategi
Planen var att finjustera en av modellerna från GPT-3-familjen med mina egna Telegram-chattar. För den här övningen valde jag bara en chatt med en vän vars konversationer med mig låg på den tamare änden av spektrumet men förhoppningsvis innehöll tillräckligt många meddelanden (48 000) för att tillräckligt lära modellen min talstil (och livshistoria).
För finjusteringsprocessen följde jag i stort sett strategin som anges i OpenAI:s kundsupport chatbot-fallstudie, med några modifieringar. Finjusteringen gjordes i Python.
För servering snurrade jag upp en Firebase-applikation, skriven i NodeJS, för att 1) lagra konversationshistorik och 2) servera modellen via Telegram genom att svara på Telegram HTTP-webhooks. Mer detaljerade steg följer, och du kan få både finjustering och serveringskod på detta förråd.
De kommande delarna blir lite tekniska. Om du inte är sugen på dessa implementeringsdetaljer, känn efter free att hoppa till observationer!
Finjustering
Koncepten bakom finjustering av GPT-modeller behandlas i detalj här. I ett nötskal måste vi visa GPT-modellerna ett antal exempel, där varje exempel innehåller en prompt (vad som matas in i modellen) och dess motsvarande komplettering (vad modellen returnerar). Utmaningen är hur vi förvandlar vår Telegram-konversationshistorik till en serie av sådana prompt-kompletteringspar, där uppmaningarna och avslutningarna är konstruerade för att uppfylla vårt mål.
Hämta konversationshistoriken från Telegram
Ganska trivialt, följ bara Telegrams instruktioner och ladda ner historiken i JSON-format. Läs data för att få en lista över meddelanden.
med open(‘result.json’, encoding=”utf8″) som json_file:data = json.load(json_file)
meddelanden = data[“messages”] #meddelanden är en listaprint(len(meddelanden)) #47902 meddelandenpprint(meddelanden[0]) #exempel dataformat enligt nedan
# {‘date’: ‘2021-01-07T19:00:52’,# ‘date_unixtime’: ‘1610017252’,# ‘from’: ‘Redagerad’,# ‘from_id’: ‘user135884720’,# ‘id’: 13405,# ‘text’: ‘Hej!’,# ‘text_entities’: [{‘text’: ‘Hello!’,# ‘type’: ‘plain’}],# ‘typ’: ‘meddelande’}
Förvandla meddelandena till meningsfulla uppmaningar-slutför par
OpenAIs finjusteringsexempel för kundchatbot föreslår att man utformar uppmaningar enligt följande:
{“prompt”:”Kund: \nAgent: \nKund: \nAgent:”, “completion”:” \n”}{“prompt”:”Kund: \nOmbud: \nKund: \nOmbud: \nKund: \nOmbud:”, “komplettering”:” \n “}
På engelska betyder det att denna (simulerade) konversation med en agent…
… skulle ge 3 prompt-kompletteringspar. De två första är följande:
Uppmaning 1:
Kund: Hej, jag skulle vilja säga upp mitt kreditkortOmbud:
Slutförande 1:
Självklart! Jag hjälper dig gärna att annullera ditt kreditkort. Kan jag få ditt namn och de fyra sista siffrorna på kortet du vill avbryta, tack?
Uppmaning 2:
Kund: Hej, jag skulle vilja säga upp mitt kreditkortAgent: Självklart! Jag hjälper dig gärna att annullera ditt kreditkort. Kan jag få ditt namn och de fyra sista siffrorna på kortet du vill avbryta, tack?Kund: Bing Wen, 1111Ombud:
Slutförande 2:
Tack, Bing. Får jag fråga varför du vill säga upp ditt kort?
Detta prompt-kompletteringsschema är snyggt men resulterar fortfarande i beteendet med enstaka svar per meddelande som vi vill undvika. För att komma till rätta med detta ändrar vi schemat något. Låt oss säga att vi har den här Telegram-konversationen:
Detta kommer att ge 2 exempel (den första uppsättningen meddelanden, initierad av mig, räknas inte som en komplettering eftersom den slutliga boten inte kommer att initieras slumpmässigt, till skillnad från sin irl-motsvarighet)
Uppmaning 1:
Jag: Jag skriver mitt medium inlägg på chatbingar nuJag: Tar längre tid än väntatDe: Oj heheDe: Ser fram emot det!De: Jag ska mäta snickeri idagMig:
Slutförande 1:
Skärmdumpen av den här chatten kommer in i inläggetJag: Så välj dina nästa ord noggrantJag: HAHAHA
Observera att det första meddelandet i kompletteringen inte kommer med “mig”. Detta beror på att det redan ingår i prompten. Genom att göra det tvingar vi GPT att svara som “jag”. Kundtjänstens chatbot-exempel gör detta också. Jag lade också till en -token för att signalera slutet på en serie svar. Detta lär GPT att även avge denna signal i slutet av sina svar när den används live.
Uppmaning 2:
Jag: Jag skriver mitt medium inlägg på chatbingar nuJag: Tar längre tid än väntatDe: Oj heheDe: Ser fram emot det!De: Jag ska mäta snickeri idagJag: Skärmdumpen av den här chatten kommer in i inläggetJag: Så välj dina nästa ord noggrantJag: HAHAHADe: Åh kära duDe: HAHAMig:
Slutförande 2:
Ok det räcker HAHAHAJag: Måste bara förklara hur finjusteringen fungerar
Med denna mekanism lär sig GPT så småningom att svara som “jag”, och kan också förstå och svara i uppsättningar av meddelanden, i motsats till bara ett meddelande per tur.
Den sista idén att introducera är hur man segmenterar meddelandehistoriken i konversationer. Som framgår av uppmaningarna ovan skickar vi tidigare meddelanden tidigare i konversationen så att GPT kan upprätthålla konversationskontexten. Men i samband med Telegram-chattar skulle det inte vara meningsfullt att göra detta på obestämd tid upp i meddelandehistoriken eftersom en meddelandehistorik är uppdelad i flera, distinkta konversationer, som mestadels bör vara oberoende av varandra. Vi kommer att behöva något sätt att programmatiskt dela upp meddelandehistoriken till sådana konversationer.
Jag bestämde mig för att flagga starten på en ny konversation varje gång det hade gått minst 1 timme utan ytterligare meddelanden från mig.
Med all den teorin ur vägen, här är koden för att skapa konversationerna.
new_convo_threshold_seconds = 3600 #en ny konversation startar om 1 timme utan ytterligare meddelanden går efter det senaste meddelandet från migtelegram_name = “Bing Wen” #mitt namn
#få datum för meddelandenför meddelande i meddelanden:meddelande[“datetime”] = datetime.strptime(meddelande[‘date’]’%Y-%m-%dT%H:%M:%S’)
def check_new_convo(previous_message, current_message):return (previous_message[“from”] == telegram_name och (aktuellt_meddelande[“datetime”] – föregående_meddelande[“datetime”]).sekunder > new_convo_threshold_seconds)
#this loop skapar en lista med konversationerkonversationer = []för idx, meddelande i enumerate(meddelanden):if (idx == 0) eller check_new_convo(meddelanden[idx-1],meddelanden[idx]):om idx > 0:konversationer.append(ny_konversation)ny_konversation = []new_conversation.append(meddelande)
konversationer = lista(filter(lambda x: len(x) > 1, konversationer)) #ettmeddelandekonversationer är inte konversationerprint(len(konversationer))
Med konversationerna korrekt segmenterade skapar vi sedan prompt-kompletteringsparen.
för konversation i konversationer:för idx, meddelande i enumerate(konversation):om idx == (len(konversation)-1):Fortsättaif idx == 0: #if start av konvomeddelande[“prompt_start”] = Sant #det här är början på en uppmaningmeddelande[“completion_start”] = Falsktom meddelande[“from”] != telegram_name och konversation[idx+1][“from”] == telegram_name: #om detta är slutet på den andra partens meddelandenmeddelande[“prompt_end”] = Sant #det är slutet av promptenkonversation[idx+1][“completion_start”] = Sant #och början på ett slutförandeannan:meddelande[“prompt_end”] = Falsktkonversation[idx+1][“completion_start”] = Falsktom meddelande[“from”] == telegram_name och konversation[idx+1][“from”] != telegram_name: #om detta är slutet på en sträng av mina meddelandenmeddelande[“completion_end”] = Sant #det är slutet på en avslutningkonversation[idx+1][“prompt_start”] = Sant #och nästa rad är början på en ny promptannan:meddelande[“completion_end”] = Falsktkonversation[idx+1][“prompt_start”] = Falskt
träningspar = []
def get_line(meddelande): #denna funktion föregår om meddelande[“from”] == telegram_name:namn = “Jag”annan:namn = “De”om ‘foto’ i meddelandet: #handle bildmeddelandentext = ‘‘annan:text = meddelande[“text”]om text:försök: #hantera några konstiga situationer där det finns webbadresser/entiteter i textenif isinstance(text, lista):textStr = “”för saker i text:if isinstance(stuff, dict):textStr += saker[“text”]annan:textStr += sakertext = textStrbortsett från:print (text)returnera f”{namn}:{text}\n”annan:returnera Falskt
#denna loop skapar flera träningsexempel från varje exempel för konversation i konversationer:seed_pair = {“prompt”: “”, “completion”:””}för meddelande i konversation:om meddelande[“prompt_start”]:nyckel = “prompt”elif meddelande[“completion_start”]:nyckel = “slutförande”ny_linje = get_line(meddelande)if new_line:seed_pair[key] += get_line(meddelande)if message.get(“completion_end”,True):training_pairs.append(seed_pair.copy())seed_pair[“prompt”] += fröpar[“completion”]seed_pair[“completion”] = “”
#strippa av dessa par utan att slutföra demträningspar = [pair for pair in training_pairs if len((pair[“completion”].rstrip())) > 0]
#efterbehandlingstop_sequence = ““me_token = “Jag:”acceptable_char_length = 1400min_prompt_length = 1400
def truncate_prompt(prompt, komplettering):if (len(prompt) + len(komplettering)) > acceptable_char_length:length_for_prompt = max(acceptable_char_length – len(completion), min_prompt_length)new_prompt = prompt[-length_for_prompt:]lägre = min(new_prompt.find(“\nJag:”),new_prompt.find(“\nDe:”))ny_prompt = ny_prompt[lower+1:]returnera ny_promptannan:returnera uppmaning
char_counter = 0
för par i träningspar:# nästa två rader tar bort den första jag i kompletteringen och lägger till den i prompten iställetpar[‘prompt’] += me_tokenpar[‘completion’] = ” “+me_token.join(par[‘completion’].split(me_token)[1:])+stoppsekvensom len(par[‘prompt’]) + len(par[‘completion’]) > acceptable_char_length:par[‘prompt’] = truncate_prompt(par[‘prompt’],par[‘completion’]) #truncates prompt om konversationen är för lång och de senaste meddelandena behållschar_counter += (len(par[‘prompt’]) + len(par[‘completion’]))
print(f”{len(training_pairs)} träningspar”) #9865 träningsparpprint(training_pairs[29])# {‘completion’: ‘ HAHA omg VBA\n’# ‘Jag: om du gillar sånt där kan vi göra mycket med VBA här’# ‘för\n’# ‘‘,# ‘prompt’: ‘De: Vissa proffs är verkligen ganska roliga haha\n’# ‘Me:ya haha BL själv är ganska rolig\n’# ‘De: Påminner mig om min ekonomiska modelleringsproff\n’# ‘Jag: vad studerade du igen ah\n’# ‘De: Han gjorde ett arbetsblad för hur du tränar din drake\n’# ‘De:På VBA\n’# ‘De:\n’# ‘De: Jag var ekonom och ekonom!\n’# ‘Jag:’}
Inled finjustering
Med träningsexemplen redo installerar vi nu OpenAI kommandoradsgränssnitt (CLI).
pip installation –uppgradera openai
Vi är redo att förbereda den finjusterande JSONL-filen som OpenAI förväntar sig.
df = pd.DataFrame(training_pairs)df.to_json(“fine_tuning.jsonl”, orient=’records’, lines=True)## notera – detta kördes i Jupyter Notebook, därför är nästa rad att köra skalkommandot!openai-verktyg fine_tunes.prepare_data -f fine_tuning.jsonl -q
Slutligen anropar vi finjusteringsändpunkten via CLI. Här väljer vi att finjustera standardmodellen, Curie. Curie är den 2:a bästa av de fyra OpenAI-textmodeller som för närvarande finns tillgängliga. Prestandan skulle sannolikt förbättras genom att använda Davinci, den bästa, men den kostar också 10 gånger mer. Som det var, kostade finjusteringen på Curie mig $47 USD, så Davinci skulle ha varit lite för dyrt för magen, även i vetenskapens namn.
importera osos.environ[“OPENAI_API_KEY”] = “” #set env-variabler!openai api fine_tunes.create -t ”fine_tuning.jsonl” #call shell-kommando i Jupyter Notebook
Testar den finjusterade modellen i Python
Innan vi serverar modellen via Telegram testar vi den på Python. Initial testning visade att boten upprepade sig väldigt ofta. Jag justerade hyperparametrarna frequency_penalty och presence_penalty för att minska repetitioner medan jag lekte med temperaturen för att justera botens fantasifullhet. Så småningom följde detta:
importera osimportera openaiopenai.organization = ““openai.api_key = ““
def get_chatbings_response(text):prompt = “De:” + text + “\nJag:”stop_sequence = ““respons = openai.Completion.create(model=”curie:ft–2023-01-23-07-14-12″, prompt=prompt, temperatur=0,2, #injicerar mer slumpmässighet i modellen, vilket gör dens fantasi vildaremax_tokens=100, #kapar svarslängdenfrequency_penalty=0.6, #bestraffar upprepningar närvaro_penalty=0,6, #bestraffar upprepningarstopp = stoppsekvens)return response.choices[0].text
print(get_chatbings_response(“Vad är meningen med livet?”))
#HAHAHAHA# Jag: Jag är inte säker på om jag har ett definitivt svar på det# Jag: Men jag tycker att det är viktigt att leva livet med mening och mening
Djup.
Det var nu dags att servera modellen i Telegram!
Servering
Vi har nu en modell som tar in n ≥1 tidigare Telegram-meddelande som en prompt och spottar ut m ≥ 0 meddelanden som svar (ja boten skulle teoretiskt sett bara kunna ignorera dig). Den breda strategin bakom att tjäna denna modell som en Telegram-bot är att:
- Lagra alla meddelanden från användare och från boten i en databas
- Utforma någon mekanism eller regel genom vilken modellen triggas (kom ihåg att vi vill undvika att trigga den vid varje meddelande – det skulle göra konversationen verkligen onaturlig och konstig!).
- Skriv lite logik för att svara på ovannämnda trigger genom att:a. kompilera alla tidigare meddelanden i den aktuella konversationenb. genererar prompten och skickar den till modellens API-slutpunkt, c. skicka den returnerade kompletteringen tillbaka till användaren via Telegrams API
- Installera den nödvändiga logiken bakom en HTTP-slutpunkt och ställ in Telegram-botens webhook.
Huvudpunkten i den här artikeln handlar om finjustering och snabb konstruktionsprocess, så jag kommer att täcka serveringsaspekterna i mindre detalj. Se serveringsrepoet för mer detaljerade instruktioner!
Arkitektur
För att uppnå allt detta använde jag Google Clouds Firebase-plattform. Meddelanden skulle lagras i Cloud Firestore, medan en HTTP-utlöst molnfunktion skulle lyssna på Telegrams webhooks och utföra den nödvändiga logiken.
Utlösande mekanism
Efter att ha funderat på hur jag skulle designa ett tillståndslöst, serverlöst system som bara skulle trigga modellen om minst x sekunder hade förflutit sedan det senaste meddelandet, bestämde jag mig för att hoppa över all denna komplexitet och istället låta användaren trigga botens svar genom att skicka in ett “X”. Enkelhet ftw!
Molnfunktion
Med ovanstående förenkling räckte bara en funktion, skriven i NodeJS, för att hantera allt! Denna funktion körs varje gång ett telegrammeddelande skickas till boten. Den gör följande:
Hela funktionen hittar du här. Läs mer om hur du distribuerar hela arkitekturen på Google Cloud!
Telegramsaker
För att få igång en bot måste vi först skapa en Telegram-bot via en-och-bara Botfather (kom ihåg att spara din bot-token). När funktionen ovan har distribuerats till Firebase måste du ställa in botens webhook så att den pekar på HTTP-slutpunkten som Google just skapat åt dig. Detta kan göras via Postman eller helt enkelt curl.
Och där har du det! En Telegram-bot som förhoppningsvis pratar som du.
Observationer
Efter att ha låtit några närmare vänner och kollegor leka med boten (döpt till ChatBings), här är några av vad jag har lärt mig om den (och dem).
- Det plockade upp min språkliga stil och egenheter ganska bra…
- Det är hemskt vid småprat (förmodligen lärt mig av mig), och ofta blåser folk av (förhoppningsvis inte lärt sig av mig…)
- Även om det ganska mycket härmar (och förstärker) min stiljag kan inte säga detsamma för innehåll. Det är i grunden en fantast med en skrämmande fantasi som helt klart har gått långt utanför räckvidden för den konversationshistorik som den finjusterades på. Dessutom är det inkonsekvent över konversationer, och hittar på saker allt eftersom. Ibland är jag singel, ibland fäst, ibland gillar jag tjejer, ibland killar – du förstår.
- Det är särskilt fantasifullt och pratsamt när man pratar om relationer och kärlek. Jag antar att det beror på att internet är fullt av sådana texter eftersom det verkligen inte hämtade detta från finjusteringsdata…
- Det kommer upp med ett och annat memeable citat.
- Mina vänner har en undertryckt lust att fråga om mitt kärleksliv.
- Jag använder “HAHAHA” och 😂 alldeles för mycket i chattar. HAHAHA.
Bortsett från skämt, resultatet överträffade verkligen mina förväntningar. Det är lagom som jag, åtminstone tills man börjar gräva lite djupare.
Sista tankar och slutsatser
Allt som allt var hela processen med att bygga denna bot verkligen roligt. Det lärde mig massor om snabb konstruktion och GPTs hyperparametrar, och slutprodukten var förvånansvärt legitim, även utan att använda den dyrare Davinci-modellen. Det var helt klart roligt för mina vänner som interagerade med det också (på bekostnad av mina OpenAI- och GCP-krediter). Och nu har jag något att visa upp på sociala sammankomster!
Som sagt, samtidigt som du är en excellent gimmick, boten är fortfarande (åtminstone enligt de som provat den), underlägsen som en chattkompis till den verkliga affären. Jag antar att jag (och vi?) borde ta hjärtat av det…? Dessutom är jag inte övertygad om nyttan av att göra något sådant i affärssammanhang. Modellens förmåga att hallucinera bortom vad den var finjusterad på är alldeles för farlig, särskilt för kritiska domäner. Kanske med framtida förbättringar och/eller med en bättre finjustering/snabb ingenjörsstrategi kan detta ändå förändras.
Ändå, även idag, kan något sådant här potentiellt användas för vissa nischsituationer. En möjlighet jag har funderat på är att väcka nära och kära tillbaka till livet på Telegram – hur ihålig chatten än kan vara, kan det ändå ge en känsla av tröstande förtrogenhet. Å andra sidan är jag också ganska oroad över att något sådant här används för kärleksbedrägerier i industriell skala, med tanke på botens tydliga förkärlek för romantik.
Och med det är det slutet på det här missivet! Har du idéer om hur detta skulle kunna användas eller förbättras? Eller vill du prata med den riktiga jag? Slå mig på LinkedIn!
Publicerad via Mot AI