Herkese merhaba! Son zamanlarda, uzun zamandır denemek istediğim ilginç bir çözümü pratiğim sırasında uyguladım ve şimdi benzer bir şeyi başka herhangi bir görev için nasıl yaratabileceğinizi açıklamaya hazırım. Uzunluğu bilgi isteminin boyutuyla sınırlı olmayan geniş bir bilgi tabanını hesaba katarak soruları yanıtlayan özelleştirilmiş bir ChatGPT sürümü oluşturmaktan bahsedeceğiz (yani her bilginin önüne tüm bilgileri ekleyemezsiniz). ChatGPT'ye soru).
Bunu başarmak için OpenAI'den (bilgi tabanından ilgili soruların gerçekten yüksek kalitede aranması için) ve ChatGPT API'sinden (cevapları doğal insan dilinde biçimlendirmek için) bağlamsal yerleştirmeleri kullanacağız.
Ayrıca asistanın sadece açıkça belirtilen Soru-Cevap sorularını değil aynı zamanda Soru-Cevap konusunda bilgili bir kişinin cevaplayabileceği soruları da yanıtlayabildiği varsayılmaktadır. Geniş bir bilgi tabanını kullanarak yanıt veren basit botların nasıl oluşturulacağını öğrenmek istiyorsanız ayrıntılara hoş geldiniz.
Bu görevi bir çerçeve şeklinde çözmeye çalışan bazı kütüphane projeleri olduğunu belirtmek isterim, örneğin LangChain ve ben de onu kullanmayı denedim. Ancak, gelişiminin erken aşamasında olan herhangi bir çerçeve gibi, bazı durumlarda işleri basitleştirmek yerine sınırlama eğilimindedir. Özellikle, bu görevi çözmenin en başından beri, verilerle ne yapmak istediğimi anladım ve bunu kendi başıma nasıl yapacağımı biliyordum (bağlama dayalı arama, istemlerde doğru bağlamı ayarlama, bilgi kaynaklarını birleştirme dahil).
Ancak çerçeveyi kabul edilebilir bir kalite düzeyinde tam olarak bunu yapacak şekilde yapılandıramadım ve çerçevedeki hataları ayıklamak bu görev için aşırıya kaçma gibi görünüyordu. Sonunda kendi standart kodumu oluşturdum ve bu yaklaşımdan memnun kaldım.
Üzerinde çalıştığım görevi kısaca anlatayım, siz de aynı kodu kendi görevlerinizde, veri kaynaklarını ve istemlerini size uygun olanlarla değiştirerek kullanabilirsiniz . Botun mantığı üzerinde hâlâ tam kontrole sahip olacaksınız.
Kod yazarken sıklıkla ChatGPT kullanıyorum (ve bundan utanmıyorum🙂). Ancak 2022+ yılına ait veri eksikliği nedeniyle bazen nispeten yeni teknolojilerde sorunlar yaşanabilmektedir.
Özellikle, The Graph protokolü için alt grafikler geliştirirken (EVM uyumlu blok zincirlerden indekslenmiş verileri almak için ETL oluşturmanın en popüler yolu, bunun hakkında daha fazla bilgiyi önceki makalelerimde [ 1 ] ve [ 2 ] okuyabilirsiniz), kütüphanelerin kendileri birçok uyumluluk değişikliğine uğradı. ChatGPT'den gelen "eski" yanıtlar artık yardımcı olmuyor ve doğru yanıtları ya kıt belgelerde ya da en kötü durumda geliştiricilerin Discord'unda aramam gerekiyor ki bu pek uygun değil (StackOverflow'a benzemiyor).
Sorunun ikinci kısmı, her seferinde sohbet bağlamını doğru bir şekilde sağlamanız gerektiğidir çünkü ChatGPT sıklıkla alt grafikler konusundan saparak GraphQL, SQL veya daha yüksek matematiğe ("Grafik", "alt grafikler" vb.) atlar. benzersiz terimler değildir ve birçok farklı yorum ve konuya sahiptir).
Bu nedenle, alt grafik kodundaki hataları düzeltmek için ChatGPT ile kısa bir süre uğraştıktan sonra, her zaman doğru bağlamda olacak ve bilgi tabanını ve geliştiricilerin anlaşmazlıklarını dikkate alarak yanıt vermeye çalışacak kendi SubgraphGPT botumu oluşturmaya karar verdim. .
PS. Web3 altyapı sağlayıcısı Chainstack.com'da baş ürün yöneticisi olarak çalışıyorum ve alt grafik barındırma hizmetinin geliştirilmesinden sorumluyum. Bu yüzden kullanıcıların bu nispeten yeni teknolojiyi anlamalarına yardımcı olmak için alt grafiklerle çok çalışmam gerekiyor.
Sonunda bu sorunu çözmek için iki kaynak kullanmaya karar verdim:
Yarı-kör modda seçilen, manuel olarak derlenmiş soru ve cevaplardan oluşan bir bilgi tabanı (çoğunlukla soru olarak dokümantasyondaki konu başlığını ve cevap olarak bilgi paragrafının tamamını aldım).
Protokol geliştiricileri Discord'un son 2 yıla ait mesajları dışa aktarıldı (2021 sonundan itibaren eksik olan dönemi kapatmak için).
Daha sonra, ChatGPT API'sine istek oluşturmak amacıyla her kaynak için farklı yaklaşımlar kullanıldı; özellikle:
Manuel olarak derlenen Soru-Cevap için,
her soru için metin gömme-ada-002 modeliyle elde edilen bağlamsal bir yerleştirme oluşturulur (bu soruyu çok boyutlu bir durumda tanımlayan bir vektör),
daha sonra kosinüs mesafesi arama fonksiyonu kullanılarak bilgi tabanından en benzer ilk 3 soru bulunur (3 yerine veri kümenize en uygun sayı kullanılabilir),
bu 3 sorunun yanıtları, " Bu Soru-Cevap pasajını yalnızca verilen soruyla alakalıysa kullanın " şeklinde yaklaşık bir açıklamayla birlikte son soruya eklenir.
Discord'dan dışa aktarılan mesajlar için aşağıdaki algoritma kullanıldı:
Soru işareti içeren her mesaj için bağlamsal bir yerleştirme de oluşturulur (aynı model kullanılarak),
daha sonra benzer şekilde en çok benzeyen ilk 5 soru seçilir,
ve cevabın bağlamı olarak, sorunun cevabını içerme ihtimalinin belirli olduğu varsayılan, o soruyu takip eden 20 mesaj eklenir,
ve bu bilgi son bilgi istemine yaklaşık olarak şu şekilde eklendi: "Ekteki Soru-Cevap bölümünde soruya açık bir cevap bulamadıysanız, geliştirici tarafından hazırlanan aşağıdaki sohbet parçaları orijinal soruyu cevaplamak için işinize yarayabilir.. "
Ayrıca konu açıkça belirtilmemişse Soru-Cevap parçacıklarının ve sohbetlerin varlığı yanıtlarda belirsizliğe yol açabilir ve bu yanıtlar örneğin aşağıdaki gibi görünebilir:
Yani sorunun bağlamdan kopuk olduğunu, cevabın da bağlamdan kopuk kabul edildiğini anlıyor. Daha sonra bu tür verilerin kullanılabileceği söylendi ve özetle şöyle:
Bunu önlemek için, açıkça tanımlanan ve bilgi isteminin başına eklenen konu kavramını şu şekilde tanıtıyoruz:
"'Grafik alt grafiği geliştirme' konusuyla ilgili bir sorunun cevabını almam gerekiyor: {{{alt grafik nedir?}}}"
Ayrıca son cümleye şunu da ekliyorum:
Son olarak eğer yukarıdaki bilgiler yeterli değilse 'Grafik alt grafiği geliştirme' konusundaki bilginizi soruyu cevaplamak için kullanabilirsiniz.
Sonunda istemin tamamı (sohbetlerden elde edilen kısım hariç) aşağıdaki gibi görünür:
==I need to get an answer to the question related to the topic of "The Graph subgraph development": {{{what is a subgraph?}}}.== ==Possibly, you might find an answer in these Q&As \[use the information only if it is actually relevant and useful for the question answering\]:== ==Q: <What is a subgraph?>== ==A: <A subgraph is a custom API built on blockchain data. Subgraphs are queried using the GraphQL query language and are deployed to a Graph Node using the Graph CLI. Once deployed and published to The Graph's decentralized network, Indexers process subgraphs and make them available to be queried by subgraph consumers.>== ==Q: <Am I still able to create a subgraph if my smart contracts don't have events?>== ==A: <It is highly recommended that you structure your smart contracts to have events associated with data you are interested in querying. Event handlers in the subgraph are triggered by contract events and are by far the fastest way to retrieve useful data. If the contracts you are working with do not contain events, your subgraph can use call and block handlers to trigger indexing. Although this is not recommended, as performance will be significantly slower.>== ==Q: <How do I call a contract function or access a public state variable from my subgraph mappings?>== ==A: <Take a look at Access to smart contract state inside the section AssemblyScript API. https://thegraph.com/docs/en/developing/assemblyscript-api/>== ==Finally, only if the information above was not enough you can use your knowledge in the topic of "The Graph subgraph development" to answer the question.==
Yukarıdaki isteğe, girişte yarı otomatik olarak oluşturulan istemle verilen yanıt, baştan itibaren doğru görünüyor:
Bu durumda bot hemen doğru anahtarla yanıt verir ve daha alakalı bilgiler ekler, böylece yanıt Soru-Cevap'taki kadar basit görünmez (bu sorunun tam olarak soru ve yanıt listesinde olduğunu hatırlatırım), ancak Aşağıdaki sorulara kısmen yanıt veren makul açıklamalar.
En sonunda depoya bir bağlantı olacağını hemen belirtmeliyim, böylece botu olduğu gibi çalıştırabilir, "konu"yu kendi dosyanızla, Soru-Cevap bilgi tabanı dosyasını kendi dosyanızla değiştirebilir ve kendi API anahtarlarınızı sağlayabilirsiniz. OpenAI ve Telegram botu için. Dolayısıyla buradaki açıklamanın GitHub'daki kaynak koduna tam olarak karşılık gelmesi amaçlanmamıştır; bunun yerine kodun ana yönlerini vurgulamak amaçlanmıştır.
Yeni bir sanal ortam oluşturalım ve gereksinimler.txt dosyasından bağımlılıkları yükleyelim:
virtualenv -p python3.8 .venv source .venv/bin/activate pip install -r requirements.txt
Yukarıda bahsedildiği gibi, bu durumda aşağıdaki türde bir Excel dosyası formatında bir soru ve cevap listesi olduğu varsayılmaktadır:
Verilen soruya en çok benzeyen soruyu bulmak için, bu dosyanın her satırına sorunun bir gömmesini (durum uzayında çok boyutlu bir vektör) eklememiz gerekir. Bunun için add_embeddings.py dosyasını kullanacağız. Senaryo birkaç basit bölümden oluşuyor.
Kitaplıkları içe aktarma ve komut satırı bağımsız değişkenlerini okuma:
import pandas as pd import openai import argparse # Create an Argument Parser object parser = argparse.ArgumentParser(description='Adding embeddings for each line of csv file') # Add the arguments parser.add_argument('--openai_api_key', type=str, help='API KEY of OpenAI API to create contextual embeddings for each line') parser.add_argument('--file', type=str, help='A source CSV file with the text data') parser.add_argument('--colname', type=str, help='Column name with the texts') # Parse the command-line arguments args = parser.parse_args() # Access the argument values openai.api_key = args.openai_api_key file = args.file colname = args.colname
Daha sonra, dosyayı bir panda veri çerçevesinde okumak ve soruları soru işaretinin varlığına göre filtrelemek. Bu kod pasajı, Discord'dan gelen ham mesaj akışlarının yanı sıra bir bilgi tabanını yönetmek için de yaygındır, bu nedenle soruların sıklıkla kopyalandığını varsayarak, bu kadar basit, kaba soru içermeyen filtreleme yöntemini kullanmaya karar verdim.
if file[-4:] == '.csv': df = pd.read_csv(file) else: df = pd.read_excel(file) # filter NAs df = df[~df[colname].isna()] # Keep only questions df = df[df[colname].str.contains('\?')]
Ve son olarak - text-embedding-ada-002 modelinin API'sini çağırarak bir yerleştirme oluşturmak için bir işlev, API ara sıra aşırı yüklenebileceğinden ve bir hatayla yanıt verebileceğinden birkaç tekrarlanan istek ve bu işlevi her satıra uygulamak veri çerçevesinin.
def get_embedding(text, model="text-embedding-ada-002"): i = 0 max_try = 3 # to avoid random OpenAI API fails: while i < max_try: try: text = text.replace("\n", " ") result = openai.Embedding.create(input=[text], model=model)['data'][0]['embedding'] return result except: i += 1 def process_row(x): return get_embedding(x, model='text-embedding-ada-002') df['ada_embedding'] = df[colname].apply(process_row) df.to_csv(file[:-4]+'_question_embed.csv', index=False)
Sonunda bu betik aşağıdaki komutla çağrılabilir:
python add_embeddings.py \ --openai_api_key="xxx" \ --file="./subgraphs_faq.xlsx" \ --colname="Question"
OpenAI API anahtarını, bilgi tabanını içeren dosyayı ve soru metninin bulunduğu sütunun adını ayarlama. Oluşturulan son dosya subgraphs_faq._question_embed.csv, "Soru", "Cevap" ve "ada_embedding " sütunlarını içerir.
Yalnızca manuel olarak toplanan bilgi tabanına dayalı olarak yanıt veren basit bir botla ilgileniyorsanız, bunu ve sonraki bölümü atlayabilirsiniz. Ancak burada hem Discord kanalından hem de Telegram grubundan veri toplamak için kısaca kod örnekleri vereceğim. discord-channel-data-collection.py dosyası iki bölümden oluşur. İlk bölüm kitaplıkların içe aktarılmasını ve komut satırı bağımsız değişkenlerinin başlatılmasını içerir:
import requests import json import pandas as pd import argparse # Create an Argument Parser object parser = argparse.ArgumentParser(description='Discord Channel Data Collection Script') # Add the arguments parser.add_argument('--channel_id', type=str, help='Channel ID from the URL of a channel in browser https://discord.com/channels/xxx/{CHANNEL_ID}') parser.add_argument('--authorization_key', type=str, help='Authorization Key. Being on the discord channel page, start typing anything, then open developer tools -> Network -> Find "typing" -> Headers -> Authorization.') # Parse the command-line arguments args = parser.parse_args() # Access the argument values channel_id = args.channel_id authorization_key = args.authorization_key
İkincisi, kanaldan veri alma ve onu bir panda veri çerçevesine kaydetme ve ayrıca belirtilen parametrelerle çağrı yapma işlevidir.
def retrieve_messages(channel_id, authorization_key): num = 0 limit = 100 headers = { 'authorization': authorization_key } last_message_id = None # Create a pandas DataFrame df = pd.DataFrame(columns=['id', 'dt', 'text', 'author_id', 'author_username', 'is_bot', 'is_reply', 'id_reply']) while True: query_parameters = f'limit={limit}' if last_message_id is not None: query_parameters += f'&before={last_message_id}' r = requests.get( f'https://discord.com/api/v9/channels/{channel_id}/messages?{query_parameters}', headers=headers ) jsonn = json.loads(r.text) if len(jsonn) == 0: break for value in jsonn: is_reply = False id_reply = '0' if 'message_reference' in value and value['message_reference'] is not None: if 'message_id' in value['message_reference'].keys(): is_reply = True id_reply = value['message_reference']['message_id'] text = value['content'] if 'embeds' in value.keys(): if len(value['embeds'])>0: for x in value['embeds']: if 'description' in x.keys(): if text != '': text += ' ' + x['description'] else: text = x['description'] df_t = pd.DataFrame({ 'id': value['id'], 'dt': value['timestamp'], 'text': text, 'author_id': value['author']['id'], 'author_username': value['author']['username'], 'is_bot': value['author']['bot'] if 'bot' in value['author'].keys() else False, 'is_reply': is_reply, 'id_reply': id_reply, }, index=[0]) if len(df) == 0: df = df_t.copy() else: df = pd.concat([df, df_t], ignore_index=True) last_message_id = value['id'] num = num + 1 print('number of messages we collected is', num) # Save DataFrame to a CSV file df.to_csv(f'../discord_messages_{channel_id}.csv', index=False) if __name__ == '__main__': retrieve_messages(channel_id, authorization_key)
Buradaki faydalı bilgiler arasında her ihtiyaç duyduğumda bulamadığım bir detay var; yetkilendirme anahtarı almak. Channel_id'nin , tarayıcıda açılan Discord kanalının URL'sinden (bağlantıdaki son uzun sayı) alınabileceği göz önüne alındığında, yetkilendirme_anahtarını yalnızca kanala bir mesaj yazmaya başlayıp ardından geliştirici araçlarını kullanarak bularak bulabilirsiniz. Ağ bölümünde " yazma " adlı olayı seçin ve parametreyi başlıktan çıkarın.
Bu parametreleri aldıktan sonra kanaldaki tüm mesajları toplamak için aşağıdaki komutu çalıştırabilirsiniz (kendi değerlerinizi değiştirin):
python discord-channel-data-collection.py \ --channel_id=123456 \ --authorization_key="123456qwerty"
Telegram'daki sohbetlerden/kanallardan sıklıkla çeşitli veriler indirdiğimden, bunun için de benzer bir formatta ( add_embeddings.py betiği açısından uyumlu) CSV dosyası oluşturan bir kod sağlamaya karar verdim. Yani telegram-group-data-collection.py betiği aşağıdaki gibi görünüyor. Kitaplıkları içe aktarma ve bağımsız değişkenleri komut satırından başlatma:
import pandas as pd import argparse from telethon import TelegramClient # Create an Argument Parser object parser = argparse.ArgumentParser(description='Telegram Group Data Collection Script') # Add the arguments parser.add_argument('--app_id', type=int, help='Telegram APP id from https://my.telegram.org/apps') parser.add_argument('--app_hash', type=str, help='Telegram APP hash from https://my.telegram.org/apps') parser.add_argument('--phone_number', type=str, help='Telegram user phone number with the leading "+"') parser.add_argument('--password', type=str, help='Telegram user password') parser.add_argument('--group_name', type=str, help='Telegram group public name without "@"') parser.add_argument('--limit_messages', type=int, help='Number of last messages to download') # Parse the command-line arguments args = parser.parse_args() # Access the argument values app_id = args.app_id app_hash = args.app_hash phone_number = args.phone_number password = args.password group_name = args.group_name limit_messages = args.limit_messages
Gördüğünüz gibi, kendinizi birinci kişi olarak yetkilendirmeden sohbetteki tüm mesajları indiremezsiniz. Başka bir deyişle, https://my.telegram.org/apps aracılığıyla bir uygulama oluşturmanın (APP_ID ve APP_HASH edinme) yanı sıra, Telethon kütüphanesinden TelegramClient sınıfının bir örneğini oluşturmak için de telefon numaranızı ve şifrenizi kullanmanız gerekecektir. .
Ek olarak, Telegram sohbetinin genel grup_adı'na ihtiyacınız olacak ve alınacak en son mesajların sayısını açıkça belirtmeniz gerekecek. Genel olarak, bir hesaptan çok sık mesaj gönderilmesinin aksine, Telegram API'sinden herhangi bir geçici veya kalıcı yasak almadan, herhangi bir sayıda dışa aktarılan mesajla bu prosedürü birçok kez yaptım.
Komut dosyasının ikinci kısmı, mesajları dışa aktarmaya yönelik asıl işlevi ve yürütülmesini içerir (toplamayı yarı yolda bırakacak kritik hataları önlemek için gerekli filtrelemeyle birlikte):
async def main(): messages = await client.get_messages(group_name, limit=limit_messages) df = pd.DataFrame(columns=['date', 'user_id', 'raw_text', 'views', 'forwards', 'text', 'chan', 'id']) for m in messages: if m is not None: if 'from_id' in m.__dict__.keys(): if m.from_id is not None: if 'user_id' in m.from_id.__dict__.keys(): df = pd.concat([df, pd.DataFrame([{'date': m.date, 'user_id': m.from_id.user_id, 'raw_text': m.raw_text, 'views': m.views, 'forwards': m.forwards, 'text': m.text, 'chan': group_name, 'id': m.id}])], ignore_index=True) df = df[~df['user_id'].isna()] df = df[~df['text'].isna()] df['date'] = pd.to_datetime(df['date']) df = df.sort_values('date').reset_index(drop=True) df.to_csv(f'../telegram_messages_{group_name}.csv', index=False) client = TelegramClient('session', app_id, app_hash) client.start(phone=phone_number, password=password) with client: client.loop.run_until_complete(main())
Sonunda, bu komut dosyası aşağıdaki komutla çalıştırılabilir (değerleri kendi değerlerinizle değiştirin):
python telegram-group-data-collection.py \ --app_id=123456 --app_hash="123456qwerty" \ --phone_number="+xxxxxx" --password="qwerty123" \ --group_name="xxx" --limit_messages=10000
Çoğu zaman, evcil hayvan projelerimi Telegram botlarına sarıyorum çünkü başlatılması çok az çaba gerektiriyor ve potansiyelini anında gösteriyor. Bu durumda ben de aynısını yaptım. Bot kodunun, SubgraphGPT botunun üretim versiyonunda kullandığım tüm köşe kasalarını içermediğini söylemeliyim çünkü başka bir evcil hayvan projemden oldukça fazla devralınmış mantığa sahip. Bunun yerine, ihtiyaçlarınıza göre değiştirilmesi kolay olması gereken minimum miktarda temel kod bıraktım.
Telegram-bot.py betiği birkaç bölümden oluşur. İlk olarak, daha önce olduğu gibi kütüphaneler içe aktarılır ve komut satırı argümanları başlatılır.
import threading import telegram from telegram.ext import Updater, CommandHandler, MessageHandler, Filters import openai from openai.embeddings_utils import cosine_similarity import numpy as np import pandas as pd import argparse import functools # Create an Argument Parser object parser = argparse.ArgumentParser(description='Run the bot which uses prepared knowledge base enriched with contextual embeddings') # Add the arguments parser.add_argument('--openai_api_key', type=str, help='API KEY of OpenAI API to create contextual embeddings for each line') parser.add_argument('--telegram_bot_token', type=str, help='A telegram bot token obtained via @BotFather') parser.add_argument('--file', type=str, help='A source CSV file with the questions, answers and embeddings') parser.add_argument('--topic', type=str, help='Write the topic to add a default context for the bot') parser.add_argument('--start_message', type=str, help="The text that will be shown to the users after they click /start button/command", default="Hello, World!") parser.add_argument('--model', type=str, help='A model of ChatGPT which will be used', default='gpt-3.5-turbo-16k') parser.add_argument('--num_top_qa', type=str, help="The number of top similar questions' answers as a context", default=3) # Parse the command-line arguments args = parser.parse_args() # Access the argument values openai.api_key = args.openai_api_key token = args.telegram_bot_token file = args.file topic = args.topic model = args.model num_top_qa = args.num_top_qa start_message = args.start_message
Lütfen bu durumda bir OpenAI API anahtarına da ihtiyacınız olacağını unutmayın; çünkü kullanıcı tarafından bilgi tabanından az önce girilen soruya en benzer soruyu bulmak için öncelikle bu sorunun yerleştirilmesini arayarak elde etmeniz gerekir. Bilgi tabanının kendisi için yaptığımız gibi API.
Ayrıca şunlara ihtiyacınız olacak:
Daha sonra bilgi tabanı dosyasının yüklenmesi ve soru yerleştirmelerinin başlatılması gelir.
# reading QA file with embeddings df_qa = pd.read_csv(file) df_qa['ada_embedding'] = df_qa.ada_embedding.apply(eval).apply(np.array)
ChatGPT API'sine istekte bulunmak için, bazen aşırı yükleme nedeniyle hatayla yanıt verdiğini bildiğimden, hata durumunda otomatik istek yeniden deneme özelliğine sahip bir işlev kullanıyorum.
def retry_on_error(func): @functools.wraps(func) def wrapper(*args, **kwargs): max_retries = 3 for i in range(max_retries): try: return func(*args, **kwargs) except Exception as e: print(f"Error occurred, retrying ({i+1}/{max_retries} attempts)...") # If all retries failed, raise the last exception raise e return wrapper @retry_on_error def call_chatgpt(*args, **kwargs): return openai.ChatCompletion.create(*args, **kwargs)
OpenAI'nin önerisine göre, metni gömmelere dönüştürmeden önce yeni satırların boşluklarla değiştirilmesi gerekiyor.
def get_embedding(text, model="text-embedding-ada-002"): text = text.replace("\n", " ") return openai.Embedding.create(input=[text], model=model)['data'][0]['embedding']
En benzer soruları aramak için doğrudan openai kütüphanesinden alınan iki sorunun yerleşimleri arasındaki kosinüs mesafesini hesaplıyoruz.
def search_similar(df, question, n=3, pprint=True): embedding = get_embedding(question, model='text-embedding-ada-002') df['similarities'] = df.ada_embedding.apply(lambda x: cosine_similarity(x, embedding)) res = df.sort_values('similarities', ascending=False).head(n) return res
Verilenlere en çok benzeyen soru-cevap çiftlerinin bir listesini aldıktan sonra, bunları tek bir metin halinde derleyebilir ve bunu ChatGPT'nin neyin ne olduğunu açıkça belirleyebileceği şekilde işaretleyebilirsiniz.
def collect_text_qa(df): text = '' for i, row in df.iterrows(): text += f'Q: <'+row['Question'] + '>\nA: <'+ row['Answer'] +'>\n\n' print('len qa', len(text.split(' '))) return text
Bundan sonra zaten makalenin başında anlatılan istemin "parçalarını" tek bir bütün halinde toplamak gerekiyor.
def collect_full_prompt(question, qa_prompt, chat_prompt=None): prompt = f'I need to get an answer to the question related to the topic of "{topic}": ' + "{{{"+ question +"}}}. " prompt += '\n\nPossibly, you might find an answer in these Q&As [use the information only if it is actually relevant and useful for the question answering]: \n\n' + qa_prompt # edit if you need to use this also if chat_prompt is not None: prompt += "---------\nIf you didn't find a clear answer in the Q&As, possibly, these talks from chats might be helpful to answer properly [use the information only if it is actually relevant and useful for the question answering]: \n\n" + chat_prompt prompt += f'\nFinally, only if the information above was not enough you can use your knowledge in the topic of "{topic}" to answer the question.' return prompt
Bu durumda Discord'dan gelen mesajları kullanarak kısmı kaldırdım ama yine de chat_prompt != Yok ise mantığı takip edebilirsiniz.
Ek olarak, ChatGPT API'sinden alınan yanıtı Telegram mesajlarına (en fazla 4096 karakter) bölen bir işleve ihtiyacımız olacak:
def telegram_message_format(text): max_message_length = 4096 if len(text) > max_message_length: parts = [] while len(text) > max_message_length: parts.append(text[:max_message_length]) text = text[max_message_length:] parts.append(text) return parts else: return [text]
Bot, /start komutuyla tetiklenecek iki işlevi atayan ve kullanıcıdan kişisel bir mesaj alan tipik bir adım dizisiyle başlar:
bot = telegram.Bot(token=token) updater = Updater(token=token, use_context=True) dispatcher = updater.dispatcher dispatcher.add_handler(CommandHandler("start", start, filters=Filters.chat_type.private)) dispatcher.add_handler(MessageHandler(~Filters.command & Filters.text, message_handler)) updater.start_polling()
/start'a yanıt verecek kod basittir:
def start(update, context): user = update.effective_user context.bot.send_message(chat_id=user.id, text=start_message)
Serbest biçimli bir mesaja yanıt vermek pek de net değil.
İlk olarak , farklı kullanıcılardan gelen iş parçacıklarının engellenmesini önlemek için, iş parçacığı kitaplığını kullanarak bunları hemen bağımsız işlemlere "ayıralım".
def message_handler(update, context): thread = threading.Thread(target=long_running_task, args=(update, context)) thread.start()
İkinci olarak , tüm mantık long_running_task fonksiyonu içinde gerçekleşecektir. Botun kodunu değiştirirken hataları kolayca yerelleştirmek için ana parçaları kasıtlı olarak try/ hariç olarak sardım.
def long_running_task(update, context): user = update.effective_user context.bot.send_message(chat_id=user.id, text='🕰️⏰🕙⏱️⏳...') try: question = update.message.text.strip() except Exception as e: context.bot.send_message(chat_id=user.id, text=f"🤔It seems like you're sending not text to the bot. Currently, the bot can only work with text requests.") return try: qa_found = search_similar(df_qa, question, n=num_top_qa) qa_prompt = collect_text_qa(qa_found) full_prompt = collect_full_prompt(question, qa_prompt) except Exception as e: context.bot.send_message(chat_id=user.id, text=f"Search failed. Debug needed.") return
Bilgi tabanını ve konuyu kendi bilgi tabanınızla değiştirirken, örneğin biçimlendirme nedeniyle hatalar olabileceğinden, insan tarafından okunabilen bir hata görüntülenir.
Daha sonra istek, ChatGPT API'sine kendini zaten kanıtlamış bir öncü sistem mesajıyla birlikte gönderilir: " Sen yardımsever bir asistansın. " Ortaya çıkan çıktı, gerekirse birden fazla mesaja bölünür ve kullanıcıya geri gönderilir.
try: print(full_prompt) completion = call_chatgpt( model=model, n=1, messages=[{"role": "system", "content": "You are a helpful assistant."}, {"role": "user", "content": full_prompt}] ) result = completion['choices'][0]['message']['content'] except Exception as e: context.bot.send_message(chat_id=user.id, text=f'It seems like the OpenAI service is responding with errors. Try sending the request again.') return parts = telegram_message_format(result) for part in parts: update.message.reply_text(part, reply_to_message_id=update.message.message_id)
Bu, bölümü kodla sonlandırıyor.
Artık böyle bir botun prototipine sınırlı formatta aşağıdaki bağlantıdan ulaşılabilir. API ücretli olduğu için günde en fazla 3 istekte bulunabilirsiniz, ancak bunun kimseyi sınırlayacağını düşünmüyorum, çünkü en ilginç olanı dar bir konuya odaklanmış özel bir bot değil, AnyGPT projesinin kodudur. Bu örneğe dayalı olarak bilgi tabanınızla özel görevinizi çözmek için kendi botunuzu nasıl oluşturacağınıza dair kısa bir talimat içeren GitHub'da mevcuttur. Eğer sonuna kadar okuduysanız umarım bu yazı size faydalı olmuştur.