สร้าง Chatbot AI ให้พูดเหมือนตัวละครโปรดของคุณบน Discord
Published: 2023-04-10 04:27:12

ถ้าคุณอยากมีบอทไว้คุยโดยพูดเหมือนตัวละครหรือบุคคลที่คุณชื่นชอบ ไม่ว่าจะเป็นตัวละครสมมติหรือบุคคลที่มีอยู่จริงๆก็ตาม หรือแม้กระทั่งสร้างใหมันพูดเหมือนเป็นตัวคุณเองก็ยังได้

โดยเราจะเริ่มตามขั้นตอนด้านล่างนี้ซึ่่งจะมีรายละเอียดด้านล่างลิสต์นี้อีกที
1. รวบรวมข้อมูลสำหรับตัวละครคุณ จะใช้เซ็ตข้อมูลที่มีอยู่หรือทำขึ้นมาเองโดยใช้ประโยคสนทนาดิบๆ
2. ฝึกโมเดลบอทของคุณใน Google Colab และทดสอบใน Hugging Face
3. สร้างบอทสำหรับโปรแกรม Discord โดยสามารถใช้ Python หรือ JavaScript ก็ได้
4. ตั้งค่าการอณุญาติให้บอทเพื่อป้องกันไม่ให้ไปสแปมข้อความที่ช่องทางอื่น
5. โฮสบอทใน Repl.it และให้ทำงานต่อเนื่องโดยใช้ Uptime Robot


**เตรียมข้อมูลอย่างไร**
ข้อมูลที่เราต้องการจะต้องเป็นรูปแบบของการสนทนา ซึ่งจำเป็นมากสำหรับสร้าง Chatbot ในการตอบโต้ข้อความต่างๆ ยกตัวอย่างตัวละครจาก [Rick and Morty](https://www.kaggle.com/datasets/andradaolteanu/rickmorty-scripts) หรือ [Harry Potter](https://www.kaggle.com/datasets/gulsahdemiryurek/harry-potter-dataset?select=Harry+Potter+1.csv) ซึ่งเราหาตัวอย่างบทสนทนาได้จากเว็บไซต์ [Kaggle](https://www.kaggle.com/) โดยเราต้องการแค่ชื่อตัวละคร(Character name)กับบทสนทนา(dialogue line)

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/m6d9gxdbrvyerrhq88gd.PNG)

และถ้าไม่มีข้อมูลบน Kaggle สามารถเช็คได้อีกที่คือ Transcript Wiki 
(Note เพิ่มเติม: เว็บไซต์ Transcript Wiki ปิดตัวลงแล้ว)


**ฝึกโมเดลของคุณอย่างไร**
ซึ่งในที่นี้โมเดลของเราจะเป็นแบบ Generative Pre-trained Transfomer (GPT) ที่เป็นตัวโมเดลภาษาที่นิยมสูงสุดในตอนนี้ โดยเราจะใช้  [Microsoft's pre-trained GPT](https://huggingface.co/microsoft/DialoGPT-small), DialoGPT-small และปรับแต่งเพิมเติมโดยใช้ข้อมูลของเรา โดยขั้นตอนเป็นตามนี้
1. อัพโหลดไฟล์ไปยัง Google Colab (อย่าลืมปรับ runtime เป็น GPU ด้วยเพื่อความรวดเร็วในการฝึกโมเดล)
2. เปลี่ยนเซ็ตข้อมูลและเป้าหมายตัวละครในโค้ดตามนี้

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/i2x1nslmmj3vh0lz403q.PNG)

ในขั้นตอนนี้คาดว่าใช้เวลาน้อยกว่าครึ่งชั่วโมงเป็นอย่างมาก ตัวอย่างเช่นเรามีทั้งหมด 700 บรรทัดซึ่งใช้เวลาทั้งหมดไม่ถึง 10 นาทีด้ยซ้ำโดยโมเดลจะถูกเก็บในโฟล์เดอร์ที่ชื่อว่า output-small

แหละหากว่าต้องการโมเดลที่ฉลาดและพูดได้เหมือนจริงมากขึ้น แนะนำให้ใช้โมเดลที่ใหญ่ขึ้นอย่าง DialoGPT-medium หรือ DialoGPT-large ซึ่งขนาาดที่ใหญ่ขึ้นหมายถึงมีพารามิเตอร์ที่เยอะขึ้นเพื่อรองรับข้อมูลที่ซับซ้อนมากขึ้น

และเรายังสามารถฝึกโมเดลเหล่านี้ได้เยอะขึ้นโดยหา num_train_epochs ในโน้ตบุ้คของโค้ดโดยตัวเลขเหล่านั้นคือจำนวนครั้งที่ตัวโมเดลจะถูกเทรนผ่านข้อมูชุดนั้นๆ และแน่นอนว่ายิ่งฝึกตัวโมเดลจะฉลาดมากขึ้น


**วิธีโฮสต์ตัวโมเดล**
เราจะใช้ [Hugging Face](https://huggingface.co/) และหลักจากสมัครแอคเค้าท์แล้วให้กดที่ New model รับ API token โดยไปที่ Edit profile > API Tokens ซึ่งเราจะใช้ในการสร้างบอทดิสคอดและอย่าลืมใส่แท็กเป็น conversational ในตัวการ์ดของโมเดล(หรือ README.md)


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9ssbdi26ve4eg0ebwebl.PNG)


**สร้าง Discord bot อย่างไร**
ไปยัง [Discord Developer's page](https://discord.com/developers/applications) สร้างแอพลิเคชั่นขึ้นมาจากนั้นค่อยเพิ่มบอทลงไป และปรับ Text Permissions เป็น Send Messgaes เพราะว่า Chatbot ของเราจะทำหน้าที่แค่ตอบข้อความผู้ใช้เท่านั้น จากนั้นคัดลอก API token ของบอทเพื่อเตรียมใช้ในภายหลัง

สมัครแอคเค้าท์หรือล็อคอินเข้า [Repl.it](https://replit.com/) จากนั้นสร้าง Repl ใหม่ขึ้นมาซึ่งสามารถใช้ได้ทั้ง Python และ Node.js ขึ้นอยู่กับความสะดวกของคุณเลย

ก่อนอื่นเริ่มจากนำ API tokens ของ Hugging Face และ Discord มาใส่ในช่อง environment variables และตั้งชื่อว่า HUGGINGFACE_TOKEN และ DISCORD_TOKEN ตามภาพนี้

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kn14npvgfn6s176c7kb4.png)

จากนั้นสามารถใช้โค้ดตามนี้ได้เลย
[Python](https://github.com/RuolinZheng08/twewy-discord-chatbot/blob/main/discord_bot.py)
```
# the os module helps us access environment variables
# i.e., our API keys
import os

# these modules are for querying the Hugging Face model
import json
import requests

# the Discord Python API
import discord

# this is my Hugging Face profile link
API_URL = 'https://api-inference.huggingface.co/models/r3dhummingbird/'

class MyClient(discord.Client):
    def __init__(self, model_name):
        # adding intents module to prevent intents error in __init__ method in newer versions of Discord.py
        intents = discord.Intents.default() # Select all the intents in your bot settings as it's easier
        intents.message_content = True
        super().__init__(intents=intents)
        self.api_endpoint = API_URL + model_name
        # retrieve the secret API token from the system environment
        huggingface_token = os.environ['HUGGINGFACE_TOKEN']
        # format the header in our request to Hugging Face
        self.request_headers = {
            'Authorization': 'Bearer {}'.format(huggingface_token)
        }

    def query(self, payload):
        """
        make request to the Hugging Face model API
        """
        data = json.dumps(payload)
        response = requests.request('POST',
                                    self.api_endpoint,
                                    headers=self.request_headers,
                                    data=data)
        ret = json.loads(response.content.decode('utf-8'))
        return ret

    async def on_ready(self):
        # print out information when the bot wakes up
        print('Logged in as')
        print(self.user.name)
        print(self.user.id)
        print('------')
        # send a request to the model without caring about the response
        # just so that the model wakes up and starts loading
        self.query({'inputs': {'text': 'Hello!'}})

    async def on_message(self, message):
        """
        this function is called whenever the bot sees a message in a channel
        """
        # ignore the message if it comes from the bot itself
        if message.author.id == self.user.id:
            return

        # form query payload with the content of the message
        payload = {'inputs': {'text': message.content}}

        # while the bot is waiting on a response from the model
        # set the its status as typing for user-friendliness
        async with message.channel.typing():
          response = self.query(payload)
        bot_response = response.get('generated_text', None)
        
        # we may get ill-formed response if the model hasn't fully loaded
        # or has timed out
        if not bot_response:
            if 'error' in response:
                bot_response = '`Error: {}`'.format(response['error'])
            else:
                bot_response = 'Hmm... something is not right.'

        # send the model's response to the Discord channel
        await message.channel.send(bot_response)

def main():
    # DialoGPT-medium-joshua is my model name
    client = MyClient('DialoGPT-medium-joshua')
    client.run(os.environ['DISCORD_TOKEN'])

if __name__ == '__main__':
  main()
```

[JS script](https://github.com/RuolinZheng08/twewy-discord-chatbot/blob/main/discord_bot.js)

```
// discord.js import
const Discord = require('discord.js');
// node-fetch for making HTTP requests
const fetch = require('node-fetch');

// initialize client
const client = new Discord.Client();
// my model URL
API_URL = 'https://api-inference.huggingface.co/models/r3dhummingbird/DialoGPT-medium-joshua';

// log out some info
client.on('ready', () => {
    console.log(`Logged in as ${client.user.tag}!`);
});

// when the bot receives a message
// need async message because we are making HTTP requests
client.on('message', async message => {
    // ignore messages from the bot itself
    if (message.author.bot) {
        return;
    }
    // form the payload
    const payload = {
        inputs: {
            text: message.content
        }
    };
    // form the request headers with Hugging Face API key
    const headers = {
        'Authorization': 'Bearer ' + process.env.HUGGINGFACE_TOKEN
    };
    
    // set status to typing
    message.channel.startTyping();
    // query the server
    const response = await fetch(API_URL, {
        method: 'post',
        body: JSON.stringify(payload),
        headers: headers
    });
    const data = await response.json();
    let botResponse = '';
    if (data.hasOwnProperty('generated_text')) {
        botResponse = data.generated_text;
    } else if (data.hasOwnProperty('error')) { // error condition
        botResponse = data.error;
    }
    // stop typing
    message.channel.stopTyping();
    // send message to channel as a reply
    message.reply(botResponse);
})

client.login(process.env.DISCORD_TOKEN);
```
เพิ่มเติมสำหรับคนนที่ใช้ JS เนื่องจากว่าเวอร์ชั่นไม่เข้ากันระหว่าง Repl.it's node กับ NPM เราจำเป็นต้องระบุเวอร์ชั่นที่เก่ากกว่าของ Discord API ใน package.json ตามนี้

```
"dependencies": {
    "discord.js": "^12.5.3",
}
```

และนี่คือตัวอย่างการใช้งานครับ

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/w6agpa5cmk5pcd41lpyp.gif)


**ทำให้บอททำงานตลอดเวลา**
ส่วนสุดท้ายนี่เพราะว่าบอทเหล่านี้จะหยุดทำงานถ้าเราปิดการทำงานของ Repl ซึ่งวิธีแก้คือตั้งเว็บเซิฟเวอร์สำหรับเก็บโค้ดของบิทโดยใช้ [Uptime Robot](https://uptimerobot.com/) และบันทึกเซิฟเวอร์ Discord ของเราไว้ทำให้ทำงานตลอดเวลา

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zl7q33hshg1ldjbjr0cs.png)


**สรุป**
แชทบอทเหล่านี้ประโยชน์ส่วนใหญ่คือใช้สร้างความบันเทิงผ่านการพิมพ์ และเราสามารถดัดแปลงให้บอทพวกนี้เลียนแบบตัวละครตัวไหนก็ได้ที่เราอยากขอแค่มีประโยคบทสนทนาที่เพียงพอที่จะฝึกโมเดลบอทพวกนี้ได้


Reference:
https://www.freecodecamp.org/news/discord-ai-chatbot/